/* ==========================================================================
   Цены аптек. Дизайн-система.

   Один файл на весь сервис. Ни одного обращения наружу: шрифты системные,
   иконки инлайновые, картинок нет. Политика ресурсов сервиса запрещает
   style="" и <style> в разметке, поэтому всё оформление живёт здесь.

   Содержание
   01. Токены
   02. Тёмная тема
   03. Сброс и основа
   04. Типографика
   05. Раскладка
   06. Шапка
   07. Подвал
   08. Кнопки
   09. Формы
   10. Карточки и списки
   11. Уведомления
   12. Таблицы
   13. Кабинет: app-shell
   14. Мобильная навигация и шторка
   15. Служебные классы, движение, печать
   ========================================================================== */



/* Слои каскада. Порядок объявления = порядок применения, поэтому mobile
   побеждает без гонки специфичностей — мобильные правила больше не надо
   «продавливать» лишним классом (C-35).

   Слоёв намеренно мало. Дробить app на components/cabinet/parso нельзя:
   слой бьёт специфичность, а файл на неё опирается — например
   `body.public main :where(p,ul,ol)` (0,1,1) должен перебивать утилиты
   ширины `.narrow`/`.band-note` (0,1,0), и при раздельных слоях это
   переворачивалось. Внутри app всё работает как прежде. */
@layer tokens, base, app, mobile;

@layer tokens {
/* 01. Токены =============================================================== */

:root {
  color-scheme: only light;

  /* Parso Glass, сентябрь 2026. Правила материалов — DESIGN.md.
     Стекло отделяет управление от данных; таблицы остаются непрозрачными.
     Геометрия и плотность сохраняют решения desktop/mobile-аудита. */

  /* Поверхности: страница белая, parchment — чередование и заливки полей */
  --canvas:        #F4F7FF;
  --surface:       #FFFFFF;
  --subtle:        #F1F4F8;

  /* Текст: ink 16.1 к белому; muted 5.3 к белому и 4.9 к parchment */
  --text:          #0E1A33;
  --text-2:        #586579;

  /* Hairline-линии. --border-strong обводит поля: 3.5 к белому. */
  --border:        #D4DDE9;
  --border-soft:   #E6EBF2;
  --border-strong: #7C899A;

  /* Акцент — единственный интерактивный цвет, сочный синий. Белый на нём 5.2. */
  --accent:        #0062F5;
  --accent-hover:  #004ED6;
  --accent-ink:    #FFFFFF;
  --accent-soft:   #E6EFFF;

  /* Фокус: system blue, 3.0+ к белому и parchment */
  --focus:         #0071E3;

  /* Наследие градиента: обе точки — Action Blue, кнопки сплошные.
     Токены сохранены, чтобы не трогать селекторы. */
  --brand-a:       #0062F5;
  --brand-b:       #0062F5;
  /* Фирменные цвета ParsoAI: фиолетовый рядом с акцентом. Кнопок и ссылок
     не касается. Фиолетовый край градиента держит 4.5 к белому. */
  --ai-a:          #0062F5;
  --ai-b:          #7A5AF8;
  --ai-gradient:   linear-gradient(100deg, var(--ai-a), var(--ai-b));
  --ai-gradient-ink: linear-gradient(100deg, #0062F5, #0062F5);
  --btn-ink:       #FFFFFF;
  --ai-glow:       none;

  /* Цветные подложки (решение владельца 13.09.2026: «ярче, насыщеннее»).
     Цвет различает функции и не кликабелен: ссылки и кнопки остаются
     акцентом. Текст на подложках — обычный --text. */
  --tint-blue:       #E3EDFF;
  --tint-blue-ink:   #0052CC;
  --tint-violet:     #EFE9FF;
  --tint-violet-ink: #6641E0;
  --tint-teal:       #DDF5EF;
  --tint-teal-ink:   #047A66;
  --tint-amber:      #FFF1D9;
  --tint-amber-ink:  #9A5B00;
  --hero-glow: radial-gradient(56rem 26rem at 8% 0%, rgb(0 98 245 / .18), transparent 62%),
               radial-gradient(44rem 24rem at 96% 20%, rgb(122 90 248 / .18), transparent 64%);
  --band-bright: linear-gradient(118deg, #0B3FD0 0%, #2A4BEA 46%, #6B47EC 100%);

  /* Состояния: заливки — тинты Parso-слоя, текст — тёмный грейд (AA) */
  --success:       #1B7E33;
  --success-soft:  #E8F5EA;
  --warning:       #B25000;
  --warning-soft:  #FDF1E4;
  --error:         #D70015;
  --error-soft:    #FDEAEA;

  /* Глубина деликатная: читаемые белые поверхности, свет по краю стекла.
     Оpaque fallback используется на телефоне и без backdrop-filter. */
  --shadow-1: 0 2px 4px rgb(29 52 86 / .02), 0 12px 32px rgb(29 52 86 / .045);
  --shadow-2: 0 8px 32px rgb(29 52 86 / .10);
  --glass-fill: #F5F8FC;
  --glass-light: inset 0 1px 0 rgb(255 255 255 / .96), inset 0 -1px 0 rgb(255 255 255 / .55);
  --glass-shadow: 0 4px 24px rgb(29 52 86 / .07), var(--glass-light);

  /* Геометрия: радиусы из шкалы скилла (10/14/pill), высота контролов 44 */
  --radius-control: 12px;
  --radius-card:    20px;
  --radius-pill:    999px;
  --control-h:      44px;
  --control-h-lg:   48px;
  /* Крупная строка-цель: полоса нижней панели, вопрос в FAQ. */
  --row-h-lg:       56px;
  /* Высота липкой шапки. Читают scroll-padding и липкие фильтры кабинета. */
  --header-h:       61px;
  /* Высота плашки кабинета (.app-bar): под неё липнут фильтр и лента /ops. */
  --app-bar-h:      61px;
  /* Место под нижнюю панель. Вне телефона обнуляется, и все компенсации,
     которые от него считаются, исчезают сами. */
  --nav-h:          0px;
  /* Контейнер прозы: 1096 рабочих, внутрь ложится текст 640 + зазор 32 +
     опора 320, запас 104. Замеренная мера строки 43…76 знаков. */
  --wrap:           1160px;
  /* Широкая ступень: публичный хром и данные могут расти до 1600. */
  --wrap-wide:      100rem;
  /* Рабочая ширина кабинета: 1536 рабочих, имя препарата в одну строку. */
  --wrap-app:       var(--wrap-wide);
  /* Текстовая колонка: 61 и 71 знак. Второе — там, где рядом стоит опора. */
  --col-text:       34rem;
  --col-text-lg:    40rem;
  /* Опора второй колонки: одна геометрия вместо прежних 304 и 320. */
  --rail-w:         20rem;
  /* Потолок нерабочих экранов кабинета: держит таблицы тарифов и сеансов. */
  --col-app:        64rem;
  /* На телефоне внутренний бокс остаётся ограниченным; от 860 rem-потолок
     снимается, и рабочая высота следует за окном. */
  --table-h:        min(70dvh, 40rem);
  --table-h-lg:     min(75dvh, 46rem);
  --measure:        68ch;

  --gutter:         clamp(16px, 4vw, 32px);

  /* Шаг сетки */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 24px;
  --s-6: 32px;
  --s-7: 48px;
  --s-8: 64px;
  /* Липкая боковая опора кабинета начинается под плашкой, с одним шагом
     воздуха. Токен общий, чтобы не кодировать высоту плашки второй раз. */
  --rail-top: calc(var(--app-bar-h) + var(--s-5));

  /* Движение: 150–240 мс, ease-out, без bounce */
  --dur-fast: 150ms;
  --dur:      180ms;
  --dur-slow: 240ms;
  --ease:     cubic-bezier(.2, .6, .35, 1);

  /* Системный стек — каноничный носитель SF; наружу ничего не грузится */
  --font: -apple-system, BlinkMacSystemFont, "SF Pro Text", 'Inter',
          system-ui, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-num: var(--font);
}

@media (min-width: 860px) {
  :root {
    --table-h: 70dvh;
    --table-h-lg: 78dvh;
  }
}
@media (min-width: 1280px) {
  :root { --col-app: 80rem; }
}


/* 02. Тёмная тема отключена ================================================ */
/* Направление редизайна — светлый язык; тёмной темы нет (design-direction.md).
   color-scheme: light выше запрещает браузеру самодеятельную инверсию. */


/* 03. Сброс и основа ======================================================= */

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

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  min-block-size: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--canvas);
  color: var(--text);
  font-family: var(--font);
  font-size: 1rem;
  line-height: 1.6;
  /* На 320 px длинные названия препаратов не должны рвать раскладку. */
  overflow-wrap: break-word;
}

main { flex: 1 0 auto; }

img, svg, video { max-inline-size: 100%; height: auto; }
svg { flex: none; }

/* Иконки внутри текста и кнопок красятся текущим цветом. */
.icon {
  inline-size: 1.25em;
  block-size: 1.25em;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}

a { color: var(--accent); }
a:hover { color: var(--accent-hover); }

/* Ссылки в сплошном тексте подчёркнуты: смысл не держится на одном цвете. */
.prose a, .disclaimer a { text-decoration-thickness: 1px; text-underline-offset: .18em; }

/* Видимый фокус на всём, чем можно управлять. Двойное кольцо: светлый
   разрыв отделяет ринг от заливки кнопки, поэтому фокус виден и на акценте. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
  border-radius: var(--radius-control);
}
.btn:focus-visible,
.mobile-nav a:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 3px;
  box-shadow: 0 0 0 3px var(--surface);
}
:focus:not(:focus-visible) { outline: none; }

.skip-link {
  position: absolute;
  inset-inline-start: var(--s-4);
  inset-block-start: -100%;
  z-index: 100;
  display: inline-flex;
  align-items: center;
  min-block-size: var(--control-h);
  padding: 0 var(--s-4);
  border-radius: var(--radius-control);
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: 600;
  text-decoration: none;
  transition: inset-block-start var(--dur) var(--ease);
}
.skip-link:focus {
  inset-block-start: var(--s-2);
  color: var(--accent-ink);
}

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


} /* @layer tokens */

@layer base {
/* 04. Типографика ========================================================== */

h1, h2, h3, h4 {
  margin: 0 0 var(--s-3);
  font-weight: 700;
  line-height: 1.22;
  letter-spacing: -0.012em;
  text-wrap: balance;
}
h1 { font-size: clamp(1.75rem, 1.34rem + 1.9vw, 2.5rem); }
h2 { font-size: clamp(1.375rem, 1.2rem + 0.8vw, 1.75rem); }
h3 { font-size: clamp(1.075rem, 1.02rem + 0.3vw, 1.25rem); }
h4 { font-size: 1rem; }

p { margin: 0 0 var(--s-4); }
p:last-child { margin-block-end: 0; }

/* Chromium переносит короткий хвост абзаца на предыдущую строку без
   ручных неразрывных пробелов. На телефоне не включаем: в узкой колонке
   pretty может добавить строку и изменить уже принятую длину страницы. */
@media (min-width: 1024px) {
  body.public main :where(p, li) { text-wrap: pretty; }
}

.lead {
  font-size: clamp(1.0625rem, 1.02rem + 0.3vw, 1.25rem);
  line-height: 1.5;
  color: var(--text-2);
}
.muted { color: var(--text-2); }
.small { font-size: .875rem; line-height: 1.5; }

/* Числа выравниваем по разрядам везде, где их читают столбиком. */
.num, td.num, th.num, .price, .table td.num, output {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.prose { max-inline-size: var(--measure); }
.prose > * + h2 { margin-block-start: var(--s-6); }
.prose > * + h3 { margin-block-start: var(--s-5); }
.prose ul, .prose ol { margin: 0 0 var(--s-4); padding-inline-start: 1.35em; }
.prose li { margin-block-end: var(--s-2); }
.prose li::marker { color: var(--text-2); }

.eyebrow {
  display: block;
  margin-block-end: var(--s-2);
  font-size: .8125rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--text-2);
}


/* Шкала брейкпоинтов (единый телефонный хром = 860, вариант Б по Р-1:
   эксперимент на 768 не уместил четвёртую ссылку шапки в одну строку).

     база  — телефон;
     480   — две кнопки в ряд, .states, .cards в два столбца;
     600   — описание ParsoAI раскрыто, фильтр ещё может сложиться в столбец;
     860   — конец телефонного хрома: таблицы снова таблицы, шторки —
             панели, нижняя панель исчезает, верхняя навигация появляется;
     1024  — .page-grid и .work-split, рабочие опоры справа;
     1280  — плотные сетки и однострочная .app-bar.

   min-width только этих пяти значений. max-width — их зеркала вида
   N-0.02px. Колонки каталога и доступное место в плашке реагируют на свои
   контейнеры, не на ширину окна. */

/* 05. Раскладка ============================================================ */

.wrap {
  inline-size: 100%;
  max-inline-size: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--s-4);
}

/* После 1280 страницы с широкими данными перестают быть застывшей колонкой
   1160 px. Не распространяем ступень на прозу с rail: опора тогда отрывается
   от текста на 544–576 px. Формула даёт фактические 1280 / 1376 / 1568 /
   1600 px при окнах 1280 / 1440 / 1920 / 2560. */
@media (min-width: 1280px) {
  body.public-wide {
    --wrap: clamp(80rem, calc(50rem + 40vw), var(--wrap-wide));
  }
}

main.wrap { padding-block: var(--s-6) var(--s-8); }
@media (max-width: 859.98px) {
  /* На телефоне 128-176 px пустоты перед подвалом — лишние. */
  main.wrap { padding-block-end: var(--s-6); }
  .site-footer { margin-block-start: var(--s-6); }
}

.section { margin-block-start: var(--s-7); }

.section > h2 { margin-block-end: var(--s-4); }

.stack > * + * { margin-block-start: var(--s-4); }
.stack-s > * + * { margin-block-start: var(--s-2); }

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


.row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  align-items: center;
}


/* 06. Шапка ================================================================ */

.site-header {
  position: sticky;
  inset-block-start: 0;
  z-index: 50;
  background: rgb(255 255 255 / .82);
  border-block-end: 1px solid var(--border);
}
/* Размытие только там, где есть мышь: на телефоне оно роняло кадр при
   прокрутке документов, а rgb(255 255 255/.82) выглядит так же. */
@media (hover: hover) {
  @supports (backdrop-filter: blur(20px)) {
    .site-header { backdrop-filter: saturate(180%) blur(20px); }}}
/* Якорные переходы не прячут заголовок под прижатую шапку. Высота шапки
   живёт в --header-h и используется ещё липкими фильтрами кабинета. */
html { scroll-padding-block-start: calc(var(--header-h) + var(--s-3)); }
/* Снизу экран тоже занят: в кабинете там липкая панель запуска. Без нижнего
   отступа прокрутки элемент, до которого дошли клавишей Tab, оказывается под
   ней вместе со своим кольцом фокуса. */
@media (min-width: 860px) { html { scroll-padding-block-end: 6rem; }}

.header-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3) var(--s-5);
  padding-block: var(--s-3);
}

.brand {
  display: inline-flex;
  align-items: center;
  min-block-size: var(--control-h);
  font-size: 1.0625rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--text);
  text-decoration: none;
}
.site-nav {
  order: 3;
  flex: 1 1 100%;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-1) var(--s-2);
  margin-inline-start: calc(var(--s-3) * -1);
  border-block-start: 1px solid var(--border);
  padding-block-start: var(--s-2);
}
.site-nav a {
  display: inline-flex;
  align-items: center;
  min-block-size: var(--control-h);
  padding-inline: var(--s-3);
  border-radius: var(--radius-control);
  color: var(--text-2);
  font-size: .9375rem;
  font-weight: 400;
  text-decoration: none;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.site-nav a:hover { background: var(--subtle); color: var(--text); }
/* Текущий раздел помечен той же плашкой, что вкладка кабинета. */
.site-nav a[aria-current="page"] {
  color: var(--accent);
  background: var(--accent-soft);
  font-weight: 600;
}

.header-actions {
  margin-inline-start: auto;
  display: flex;
  gap: var(--s-2);
}

@media (min-width: 860px) {
  .header-inner { flex-wrap: nowrap; padding-block: var(--s-2); }
  .site-nav a { font-weight: 600; }
  .site-nav {
    order: 0;
    flex: 0 1 auto;
    border-block-start: 0;
    padding-block-start: 0;
    margin-inline-start: 0;
  }}
@media (min-width: 1280px) {
  body.public .site-nav a { padding-inline: var(--s-2); }
}
/* Ниже 860 px разделы дублирует нижняя навигация, второй ряд не нужен. */
@media (max-width: 859.98px) {
  body.has-mobile-nav .site-nav { display: none; }}


/* 07. Подвал =============================================================== */

.site-footer {
  margin-block-start: var(--s-8);
  background: var(--surface);
  border-block-start: 1px solid var(--border);
}
.footer-inner {
  display: grid;
  gap: var(--s-4);
  padding-block: var(--s-6);
}
@media (min-width: 1024px) {
  .footer-inner { grid-template-columns: minmax(0, 1fr) minmax(0, 22rem); gap: var(--s-6); }}

.disclaimer {
  margin: 0;
  max-inline-size: var(--measure);
  font-size: .875rem;
  line-height: 1.55;
  color: var(--text-2);
}

.footer-nav {
  display: grid;
  /* Две колонки на телефоне: девять ссылок столбиком давали подвал 745 px. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-1) var(--s-4);
}
.footer-docs { grid-template-columns: minmax(0, 1fr); }
.footer-nav a {
  display: inline-flex;
  align-items: center;
  min-block-size: var(--control-h);
  padding-inline: var(--s-2);
  margin-inline-start: calc(var(--s-2) * -1);
  border-radius: var(--radius-control);
  color: var(--text-2);
  font-size: .875rem;
  text-decoration: underline;
  text-underline-offset: .2em;
}
.footer-nav a:hover { color: var(--accent-hover); background: var(--subtle); }


} /* @layer base */

@layer app {
/* 08. Кнопки =============================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-block-size: var(--control-h);
  min-inline-size: var(--control-h);
  padding: var(--s-2) var(--s-4);
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.25;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}
.btn:active { transform: scale(.97); }

.btn-ghost {
  background: #FAFAFC;
  border-color: var(--border);
  color: var(--accent);
}
.btn-ghost:hover { background: var(--subtle); color: var(--accent); }

.btn-quiet {
  background: transparent;
  border-color: transparent;
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: .2em;
}
.btn-quiet:hover { background: var(--subtle); color: var(--accent-hover); }

.btn-lg {
  min-block-size: var(--control-h-lg);
  padding-inline: var(--s-5);
  font-size: 1rem;
}

.btn-block { inline-size: 100%; }

.btn[disabled], .btn[aria-disabled="true"] {
  background: var(--subtle);
  border-color: var(--border-strong);
  color: var(--text-2);
  opacity: 1;
  cursor: not-allowed;
}
/* У настоящей disabled-кнопки браузер сам гасит действие, зато курсор и title
   остаются доступны. aria-disabled на ссылке требует прежней блокировки. */
.btn[aria-disabled="true"]:not(:disabled) {
  pointer-events: none;
}


/* Ниже 380 px две кнопки в ряд не помещаются, разводим по строкам. */


/* 09. Формы ================================================================ */

.form { max-inline-size: 34rem; }


/* .field бывает <label>, <div> и <fieldset>. У <label> display по умолчанию
   inline, и тогда узкое поле, подсказка и кнопка сползаются в одну строку.
   Поэтому блочность задаём явно, а не полагаемся на тег. */
.field { display: block; margin-block-end: var(--s-5); }
.field:last-child { margin-block-end: 0; }

.label {
  display: block;
  margin-block-end: var(--s-2);
  font-size: .9375rem;
  font-weight: 600;
}
.req { color: var(--error); font-weight: 700; }

.hint {
  display: block;
  margin-block-start: var(--s-2);
  font-size: .875rem;
  line-height: 1.5;
  color: var(--text-2);
}

/* Ошибка поля: значок, слово и цвет сразу, а не только красный контур. */
.field-error {
  display: flex;
  gap: var(--s-2);
  margin-block-start: var(--s-2);
  font-size: .875rem;
  font-weight: 600;
  color: var(--error);
}

.input, .textarea, .select {
  inline-size: 100%;
  min-block-size: var(--control-h);
  padding: 10px var(--s-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-control);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 1rem;   /* 16 px: иначе iOS зумит страницу при фокусе */
  line-height: 1.4;
  transition: border-color var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}
/* Подсказку в поле красим сами и по имени элемента, а не по классу: браузер
   рисует её собственным серым, которого в оформлении сервиса нет вообще, а
   поля на новых страницах класса .input не носят. */
input::placeholder,
textarea::placeholder,
.input::placeholder,
.textarea::placeholder {
  color: var(--text-2);
  opacity: .72;
}
.input:hover, .textarea:hover, .select:hover { border-color: var(--text-2); }
.input:focus, .textarea:focus, .select:focus { border-color: var(--accent); }

.textarea { min-block-size: 8.5rem; resize: vertical; }

.select {
  appearance: none;
  padding-inline-end: 2.5rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23475467' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--s-3) center;
  background-size: 1.15rem;
}
.input[aria-invalid="true"], .textarea[aria-invalid="true"] {
  border-color: var(--error);
  border-inline-start-width: 4px;
}

.file {
  inline-size: 100%;
  min-block-size: var(--control-h);
  padding: 6px var(--s-3);
  border: 1px dashed var(--accent);
  border-radius: var(--radius-control);
  background: var(--accent-soft);
  font: inherit;
  font-size: .9375rem;
  color: var(--text);
  cursor: pointer;
}

.file:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

/* Радиокнопки и галочки карточками: цель заведомо больше 44 px. */
.choice-list { display: grid; gap: var(--s-3); }


.choice input[type="radio"],

.choice-title { font-weight: 600; }
.choice-note { display: block; margin-block-start: 2px; font-size: .875rem; color: var(--text-2); }
/* Выбранный вариант держится не на заливке: рамка толще и левая полоса. */


fieldset { margin: 0; padding: 0; border: 0; min-inline-size: 0; }
legend {
  padding: 0;
  margin-block-end: var(--s-2);
  font-size: .9375rem;
  font-weight: 600;
}


/* 10. Карточки и списки ==================================================== */

.card {
  padding: var(--s-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--surface);
  box-shadow: var(--shadow-1);
}
.card > h2, .card > h3 { margin-block-start: 0; }


.hero {
  padding-block: var(--s-6) var(--s-7);
  max-inline-size: 44rem;
}
.hero .lead { margin-block: var(--s-4) var(--s-5); }
.hero-actions { display: flex; flex-wrap: wrap; gap: var(--s-3); align-items: center; }
@media (max-width: 479.98px) {
  /* Кнопки-пары на узком экране: столбиком, каждая во всю ширину. Раньше на
     393/412 это было flex-wrap с двумя кнопками разной ширины и дыркой (A-07). */
  .hero-actions { flex-direction: column; align-items: stretch; }
  .hero-actions > .btn,
  .row > .btn-lg { flex: 1 1 100%; inline-size: 100%; }
}


/* Шаги. Номер это <span>, а не ::before: его читает экранный диктор. */
.steps { counter-reset: step; display: grid; gap: var(--s-4); list-style: none; margin: 0; padding: 0; }
.step {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--s-4);
  align-items: start;
  padding: var(--s-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--surface);
}
.step-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 2.25rem;
  block-size: 2.25rem;
  border-radius: var(--radius-pill);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 1rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.step h3 { margin-block-end: var(--s-2); }
.step p:last-child { margin-block-end: 0; }

/* Список фактов: значок галочки или минуса плюс текст. */


.facts-yes .icon { color: var(--success); }
.facts-no .icon  { color: var(--error); }
.facts-warn .icon { color: var(--warning); }

/* Отдельно стоящая ссылка «читать дальше». В строке текста ссылка может быть
   любой высоты, а вот самостоятельная должна давать цель не меньше 44 px. */
.link-more {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-block-size: var(--control-h);
  padding-inline: var(--s-2);
  margin-inline-start: calc(var(--s-2) * -1);
  border-radius: var(--radius-control);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: .2em;
}
.link-more:hover { background: var(--subtle); }

/* Список ссылок: каждая строка это цель не меньше 44 px по высоте. */
.link-list { list-style: none; margin: 0; padding: 0; }
.link-list a {
  display: inline-flex;
  align-items: center;
  min-block-size: var(--control-h);
  text-decoration: underline;
  text-underline-offset: .2em;
}

.dl { display: grid; gap: var(--s-4); margin: 0; }
.dl div { display: grid; gap: var(--s-1); }
.dl dt { font-weight: 600; }
.dl dd { margin: 0; color: var(--text-2); }


/* Метка состояния. Всегда со словом внутри, не только цветом. Геометрия
   собрана вместе с итоговой заливкой в слое app ниже. */
.badge::before {
  content: "";
  inline-size: .55em;
  block-size: .55em;
  border-radius: var(--radius-pill);
  background: currentColor;
}
.badge-ok    { background: var(--success-soft); border-color: var(--success); color: var(--success); }
.badge-warn  { background: var(--warning-soft); border-color: var(--warning); color: var(--warning); }
.badge-error { background: var(--error-soft);   border-color: var(--error);   color: var(--error); }
.badge-info  { background: var(--accent-soft);  border-color: var(--accent);  color: var(--accent); }

/* Вопрос и ответ на <details>: работает без единой строки скрипта. */
.faq { display: grid; gap: var(--s-3); }


.anchor-target { scroll-margin-block-start: var(--s-5); }

/* Длина строки на публичных страницах. Текст в 130 символов поперёк монитора
   читать невозможно, поэтому сплошной текст держим в пределах 68 символов.
   Внутри карточек, шагов и таблиц ширину задаёт сама раскладка. */
body.public main :where(p, ul, ol) { max-inline-size: var(--measure); }
body.public main :where(.card, .step, .faq-body, .cards, .grid, .dl, table, .table-scroll, .choice) :where(p, ul, ol) {
  max-inline-size: none;
}


/* 11. Уведомления ========================================================== */
/* Вид держится на трёх признаках сразу: значок, толстая левая полоса, цвет.
   Значок нарисован маской из data-URI, наружу за ним ходить не нужно. */

.notice {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--s-2);
  align-items: center;
  /* Плашка меряется своим содержимым. Фиксированная ширина под короткой
     строкой оставляет пустое поле справа, и сообщение читается как
     незаполненная форма. Предел нужен только длинному тексту. */
  inline-size: fit-content;
  max-inline-size: min(100%, 46rem);
  margin-block-end: var(--s-5);
  padding: var(--s-2) var(--s-3);
  border: 1px solid var(--border);
  border-inline-start: 3px solid var(--text-2);
  border-radius: var(--radius-control);
  background: var(--subtle);
  color: var(--text);
  font-size: .9375rem;
  line-height: 1.45;
}
.notice::before {
  content: "";
  inline-size: 1.1rem;
  block-size: 1.1rem;
  background-color: var(--notice-ink, var(--text-2));
  mask: var(--notice-icon) center / contain no-repeat;
  -webkit-mask: var(--notice-icon) center / contain no-repeat;
}
.notice a { color: inherit; }

.notice-info {
  --notice-ink: var(--accent);
  --notice-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M12 11v5M12 8h.01'/%3E%3C/svg%3E");
  border-color: var(--accent);
  background: var(--accent-soft);
}
.notice-success {
  --notice-ink: var(--success);
  --notice-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='m8 12.5 2.6 2.6L16 9.7'/%3E%3C/svg%3E");
  border-color: var(--success);
  background: var(--success-soft);
}
.notice-warn {
  --notice-ink: var(--warning);
  --notice-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10.3 3.8 1.9 18a2 2 0 0 0 1.7 3h16.8a2 2 0 0 0 1.7-3L13.7 3.8a2 2 0 0 0-3.4 0Z'/%3E%3Cpath d='M12 9v4M12 17h.01'/%3E%3C/svg%3E");
  border-color: var(--warning);
  background: var(--warning-soft);
}
.notice-error {
  --notice-ink: var(--error);
  --notice-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='m15 9-6 6M9 9l6 6'/%3E%3C/svg%3E");
  border-color: var(--error);
  background: var(--error-soft);
}

/* Врезка внутри текста: не флеш, а постоянное примечание. */
.callout {
  margin-block: var(--s-4);
  padding: var(--s-4);
  border: 1px solid var(--border);
  border-inline-start: 4px solid var(--accent);
  border-radius: var(--radius-control);
  background: var(--accent-soft);
  max-inline-size: var(--measure);
}
.callout h2, .callout h3 { margin-block-end: var(--s-2); font-size: 1.0625rem; }


/* 12. Таблицы ============================================================== */
/* Шапка липнет к верху скролл-бокса. Для этого таблица кладётся внутрь
   .table-scroll: без своего скролл-бокса sticky не за что зацепиться. */

.table-scroll {
  max-block-size: var(--table-h);
  overflow: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  /* Правый край гаснет в тень, только когда таблица шире бокса: две заливки,
     local прокручивается с содержимым и открывает вторую, scroll-фиксированную
     тень у правого края. Подсказка «можно листать вбок» без JS (D-06). */
  background:
    linear-gradient(to right, var(--surface), var(--surface)) 0 0 / 100% 100% no-repeat,
    linear-gradient(to left, rgb(0 0 0 / .08), rgb(0 0 0 / 0) 1.5rem) right / 2rem 100% no-repeat local;
  background-attachment: local, scroll;
  overscroll-behavior: contain;
}

.table {
  inline-size: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-size: .9375rem;
}
.table caption {
  padding: var(--s-3) var(--s-4);
  text-align: start;
  font-weight: 600;
  color: var(--text);
}
.table th, .table td {
  /* anywhere, а не break-word: только anywhere уменьшает min-content-вклад
     ячейки, а раздвигает страницу именно он. */
  overflow-wrap: anywhere;
  padding: var(--s-3) var(--s-4);
  text-align: start;
  vertical-align: top;
  border-block-end: 1px solid var(--border-soft);
}
.table thead th { border-block-end-color: var(--border); }
.table th {
  font-weight: 600;
  font-size: .8125rem;
  color: var(--text-2);
}
.table:not(:has(thead)) tbody th[scope="row"] {
  font-size: inherit;
  line-height: inherit;
  color: var(--text);
  font-weight: 600;
  white-space: normal;
}
@media (min-width: 1024px) { .table th { white-space: nowrap; }}
/* Числовая ячейка на настольной ширине не переносится: место есть, а перенос
   посреди значения превращает цену в два числа. Ниже 860 таблица
   разворачивается в карточки, и правило туда не достаёт. */
@media (min-width: 860px) {
  .table td.num { white-space: nowrap; overflow-wrap: normal; }
  .offers-table td.price { min-inline-size: 6rem; }
}
/* Дата требует чуть больше ширины, чем цена. На 960 её nowrap отбирает место
   у адреса и поднимает строку с 73 до 97 px; от 1024 уже экономит высоту. */
@media (min-width: 1024px) {
  .offers-table .collected-at { white-space: nowrap; overflow-wrap: normal; }
}
.table tbody tr:last-child td { border-block-end: 0; }
.table:not(:has(thead)) tbody tr:last-child th[scope="row"] { border-block-end: 0; }
.table tbody tr:hover { background: var(--subtle); }
.table td.num, .table th.num { text-align: end; font-variant-numeric: tabular-nums; }

/* На настоящей таблице строка должна читаться слева направо. Hairline
   --border-soft давал 1.14:1 к белому и терялся; --border-strong даёт 3.62:1.
   Зебра удерживает строку взглядом, голубая подсветка не сливается с ней. */
@media (min-width: 860px) {
  .table th, .table td {
    padding-block: var(--s-2);
    line-height: 1.4;
  }
  .table tbody :is(th, td) { border-block-end-color: var(--border-strong); }
  .table tbody tr:nth-child(even):not(.row-best) { background: var(--subtle); }
  .table tbody tr:hover:not(.row-best) { background: var(--accent-soft); }
  .offers-table tr.group-start:not(:first-child) > :is(th, td) {
    border-block-start: 2px solid var(--border-strong);
  }
}

.table-sticky thead th {
  position: sticky;
  inset-block-start: 0;
  z-index: 2;
  background: var(--subtle);
  border-block-end: 1px solid var(--border-strong);
}

/* Сортировка. Кнопка внутри th, направление берётся из aria-sort:
   стрелка и подпись для диктора, а не только оттенок. */
.th-sort {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-block-size: var(--control-h);
  margin: calc(var(--s-3) * -1) calc(var(--s-3) * -1);
  padding: var(--s-2) var(--s-3);
  border: 0;
  border-radius: var(--radius-control);
  background: none;
  color: inherit;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
.th-sort:hover { background: var(--border); }
.th-sort::after {
  content: "";
  inline-size: 1rem;
  block-size: 1rem;
  opacity: .45;
  background-color: currentColor;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m8 9 4-4 4 4M8 15l4 4 4-4' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m8 9 4-4 4 4M8 15l4 4 4-4' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
}
[aria-sort="ascending"] .th-sort::after {
  opacity: 1;
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m6 14 6-6 6 6' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m6 14 6-6 6 6' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
[aria-sort="descending"] .th-sort::after {
  opacity: 1;
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m6 10 6 6 6-6' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m6 10 6 6 6-6' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
[aria-sort]:not([aria-sort="none"]) { background: var(--accent-soft); }

/* Таблица превращается в карточки. Подпись колонки берётся из data-label,
   поэтому у каждого td он обязателен. Роли на элементах сохраняют таблицу
   для экранного диктора: смена display её иначе ломает. */
@media screen and (max-width: 859.98px) {
  .cards-on-mobile { border: 0; }
  .table-scroll:has(.cards-on-mobile) {
    max-block-size: none;
    overflow: visible;
    border: 0;
    background: none;
  }
  .cards-on-mobile thead {
    position: absolute;
    /* inset обязателен: без него шапка шириной со всю таблицу остаётся в
       дереве позиционирования и шумит в замерах переполнения. */
    inset-block-start: 0;
    inset-inline-start: 0;
    inline-size: 1px;
    block-size: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .cards-on-mobile,
  .cards-on-mobile tbody,
  .cards-on-mobile tr,
  .cards-on-mobile td { display: block; inline-size: 100%; }
  .cards-on-mobile tr {
    margin-block-end: var(--s-3);
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    background: var(--surface);
    overflow: hidden;
  }
  .cards-on-mobile tr:hover { background: var(--surface); }
  .cards-on-mobile td {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s-2) var(--s-3);
    padding: var(--s-2) var(--s-4);
    border-block-end: 1px solid var(--border);
  }
  .cards-on-mobile td:last-child { border-block-end: 0; }
  /* Подпись колонки берётся из data-label. Нет атрибута, нет и колонки
     под подпись: ячейка просто становится обычным блоком. */
  .cards-on-mobile td[data-label] { grid-template-columns: minmax(0, 10.5ch) minmax(0, 1fr); }
  .cards-on-mobile td[data-label]::before {
    content: attr(data-label);
    font-size: .8125rem;
    font-weight: 600;
    color: var(--text-2);
  }
  .cards-on-mobile td.num { text-align: start; }
  /* Пилюля статуса — по содержимому, не на всю ширину значения */
  .cards-on-mobile td > .pill { justify-self: start; }
  .cards-on-mobile caption { padding-inline: 0; }}

/* Карточка предложения (job_detail) на телефоне. Наименование — заголовок
   группы, под ним аптека и крупная цена справа, ниже серым адрес, метро и км.
   Сеть скрыта (она в фильтре), «Собрано» скрыто (дата одна на весь мониторинг,
   её место — в statline шапки). Область действия — .offers-table (D-03, D-06). */
@media screen and (max-width: 859.98px) {
  .offers-table tr {
    display: grid;
    margin-block-end: var(--s-2);
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: var(--s-3);
    row-gap: 2px;
    align-items: baseline;
    padding: var(--s-3) var(--s-4);
  }
  .offers-table td { display: block; border: 0; padding: 0; grid-template-columns: none; }
  .offers-table td[data-label]::before { content: none; }
  .offers-table td[data-label="Сеть"],
  .offers-table td[data-label="Собрано"] { display: none; }
  .offers-table td[data-label="Наименование"] {
    grid-column: 1 / -1; grid-row: 1; font-weight: 600;
  }
  .offers-table td[data-label="Аптека"] { grid-column: 1; grid-row: 2; color: var(--text-2); }
  .offers-table td[data-label="Цена, ₽"] {
    grid-column: 2; grid-row: 2;
    text-align: end; align-self: baseline;
    font-size: 1.125rem; font-weight: 700;
    font-variant-numeric: tabular-nums;
  }
  /* Адрес — своя строка, метро и км — следующая, серым. */
  .offers-table td[data-label="Адрес"] {
    grid-column: 1 / -1; grid-row: 3; color: var(--text-2); font-size: .875rem;
  }
  .offers-table td[data-label="Метро"] {
    grid-column: 1; grid-row: 4; color: var(--text-2); font-size: .875rem;
  }
  .offers-table td[data-label="Км до метро"] {
    grid-column: 2; grid-row: 4; color: var(--text-2); font-size: .875rem; text-align: end;
  }
  .offers-table td[data-label="Км до метро"]:not(:empty)::before { content: "· " !important; }
}

/* Списки кабинета на телефоне: вся карточка кликабельна через «Открыть»
   (D-05), «Трафик» скрыт — цифра служебная. */
@media screen and (max-width: 859.98px) {
  .cards-on-mobile tr:has(.row-open) { position: relative; }
  .cards-on-mobile .row-open { border-block-end: 0; }
  .cards-on-mobile .row-open[data-label]::before { content: none; }
  .cards-on-mobile .row-open a::after {
    content: ""; position: absolute; inset: 0;
  }
  .cards-on-mobile td[data-label="Трафик"] { display: none; }
}

/* Каталог на узком экране — не таблица данных, а список для отметки.

   Общая раскладка «подпись слева, значение справа» верна для заявок, истории
   и сеансов, но каталогу она давала 313 px на позицию: треть экрана на один
   товар плюс пустые строки у тех, у кого нет дозировки. Отметить десять
   позиций так невозможно — до кнопки «Запустить сбор» три экрана прокрутки.

   Поэтому у каталога своя карточка: галочка слева обтекается названием,
   под названием одной серой строкой код, форма, дозировка и фасовка. Пустые
   значения не занимают места вовсе. */
@media screen and (max-width: 859.98px) {
  .table-catalog tbody tr {
    display: block;
    padding: var(--s-3) var(--s-4);
  }
  /* Поплавок надо закрыть, иначе карточка не считает его высоту. */
  .table-catalog tbody tr::after { content: ""; display: block; clear: both; }

  .table-catalog td {
    display: block;
    padding: 0;
    border-block-end: 0;
  }
  .table-catalog td:nth-child(1) {
    float: inline-start;
    inline-size: auto;
    margin-inline-end: var(--s-3);
  }
  /* Подписи колонок на карточке лишние: «9 МЕСЯЦЕВ Фолиевая кислота» и без
     слова «Наименование» понятно, что это название, а «таблетки» — что форма. */
  .table-catalog td:nth-child(3)::before,
  .table-catalog td:nth-child(4)::before,
  .table-catalog td:nth-child(5)::before,
  .table-catalog td:nth-child(6)::before { content: none; }

  .table-catalog td:nth-child(2),
  .table-catalog td:nth-child(4),
  .table-catalog td:nth-child(5),
  .table-catalog td:nth-child(6) {
    display: inline;
    color: var(--text-2);
    font-size: .875rem;
  }
  /* Точка нужна только между значениями, а не перед первым. Условие «после
     меня уже есть непустой сосед из этой же тройки» выражается сочетанием
     :not(:empty) и ~ : у первого заполненного такого соседа впереди нет, и
     точки он не получает — какой бы по счёту он ни был. Иначе у позиции без
     формы строка начиналась бы с висящей точки. */
  .table-catalog td:nth-child(n+4):not(.catalog-empty) ~ td:not(.catalog-empty)::before {
    content: " · ";
    color: var(--text-2);
  }
  .table-catalog td:empty,
  .table-catalog td.catalog-empty { display: none; }
  /* Новые подписи трёх метаданных остаются строчными: карточка сохраняет
     прежнюю высоту и одну компактную строку «форма · доза · фасовка». */
  .table-catalog .catalog-meta-label { display: inline; min-block-size: 0; }
}


/* Поле кода. Шесть цифр читают с экрана телефона и набирают по одной,
   поэтому крупно, моноширинно и с разрядкой: так видно, сколько уже введено,
   и не сливаются 0 с O. */
.code-input {
  inline-size: 100%;
  font-size: 1.75rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: .35em;
  text-align: center;
  /* Разрядка добавляет пустоту справа от последней цифры и уводит строку
     из центра. Компенсируем отступом слева. */
  padding-inline-start: calc(var(--s-3) + .35em);
}

/* Три шага на странице ввода кода. Не маркированный список: порядок здесь
   существенный, человек идёт по нему сверху вниз. */
.steps-inline {
  margin-block: var(--s-3) var(--s-4);
  padding-inline-start: var(--s-5);
  color: var(--text-2);
}
.steps-inline li { margin-block-end: var(--s-1); }


/* 13. Кабинет: app-shell =================================================== */

/*
  Кабинет — одна колонка на любой ширине.

  Раньше здесь была вторая колонка под боковое меню. Меню переехало наверх, в
  прилипшую плашку, и колонка осталась пустой: на широком экране она съедала
  240 px, а плашка сообщения, будучи прямым потомком main, падала именно в неё
  и вытягивалась узким столбцом во всю высоту. Одной колонкой этого класса
  проблем не бывает.

  Ширина и отступ те же, что у верхней плашки: иначе знак в шапке и заголовок
  страницы стоят по разным вертикалям.
*/
.app-shell {
  display: grid;
  gap: var(--s-5);
  grid-template-columns: minmax(0, 1fr);
  /* Строки держат высоту своего содержимого, свободное место остаётся внизу.
     Без этого сетка раздаёт остаток между строками: на короткой странице —
     заявка в очереди, пустая история — плашка сообщения растягивалась в
     полтора экрана. На каталоге не было видно, там таблица занимает всё, и
     раздавать нечего. Это та же болезнь, что когда-то тянула плашку узким
     столбцом: сообщение — прямой потомок main и подчиняется его сетке. */
  align-content: start;
  max-inline-size: var(--wrap-app);
  margin-inline: auto;
  padding: var(--s-5) var(--gutter) var(--s-8);
}
/* Текст внутри кабинета всё равно не должен растягиваться на весь монитор:
   строку длиной в полтора метра читать невозможно. Ширину отдаём только
   рабочим экранам с таблицами, они помечены классом work. */
/* Колонка нерабочих экранов кабинета. inline-size: 100% обязателен: .app-shell
   — сетка, и блок с auto-полями в ней перестаёт растягиваться, ширину начинает
   диктовать содержимое (замер дал 320, 569 и 588 на 1920). Прижата к левому
   краю, а не центрирована: центр давал переход «Обзор» → «Каталог» со скачком
   левого края на 128 px, а весь остальной кабинет выровнен по одной вертикали. */
.app-content:not(.work) {
  inline-size: 100%;
  max-inline-size: var(--col-app);
  margin-inline: 0;
}

.app-content { min-inline-size: 0; }
.app-content > :first-child { margin-block-start: 0; }


/* 14. Мобильная навигация и шторка ========================================= */

.mobile-nav {
  position: fixed;
  inset-inline: 0;
  inset-block-end: 0;
  z-index: 40;
  display: flex;
  background: var(--surface);
  border-block-start: 1px solid var(--border);
  padding-block-end: env(safe-area-inset-bottom, 0);
  box-shadow: var(--shadow-2);
}
.mobile-nav a,
.mobile-nav button {
  flex: 1 1 0;
  min-inline-size: 44px;
  min-block-size: var(--row-h-lg);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: var(--s-2) var(--s-1);
  color: var(--text-2);
  /* 12 px — нижняя граница для навигационного текста. Это единственная
     навигация телефона, читать её приходится каждый раз. */
  font-size: .75rem;
  font-weight: 600;
  line-height: 1.15;
  text-align: center;
  text-decoration: none;
}
.mobile-nav a:hover,
.mobile-nav button:hover { color: var(--text); }
.mobile-nav button {
  appearance: none;
  border: 0;
  background: none;
  font-family: inherit;
  cursor: pointer;
}
.mobile-nav a[aria-current="page"],
.mobile-nav button[aria-current="page"] {
  color: var(--accent);
  box-shadow: inset 0 3px 0 var(--accent);
  background: var(--accent-soft);
}
.mobile-nav .icon { inline-size: 22px; block-size: 22px; }

/* На мобильном подвал не должен уезжать под панель. Компенсация живёт
   только здесь: раньше её дублировал .app-shell, и в кабинете внизу
   оставалось 136 px пустоты с двойной safe-area. */
body.has-mobile-nav {
  --nav-h: calc(var(--row-h-lg) + env(safe-area-inset-bottom, 0px));
  padding-block-end: var(--nav-h);
}

@media (min-width: 860px) {
  .mobile-nav { display: none; }
  body.has-mobile-nav { --nav-h: 0px; }}


/* Ссылки внутри шторки — столбиком и с крупной областью нажатия. */
.sheet-links { display: grid; gap: var(--s-1); }
.sheet-links a {
  display: block;
  min-block-size: 48px;
  padding: var(--s-3) var(--s-4);
  border-radius: var(--radius-control);
  color: var(--text);
  font-weight: 600;
  text-decoration: none;
}
.sheet-links a:hover { background: var(--subtle); }
.sheet-links a[aria-current="page"] {
  color: var(--accent);
  background: var(--accent-soft);
}

/* Шторка фильтров. Ниже 768 px это лист снизу, выше обычная панель сбоку. */
.sheet {
  position: fixed;
  inset-inline: 0;
  inset-block-end: 0;
  z-index: 50;
  max-block-size: 82dvh;
  overflow-y: auto;
  margin: 0;
  padding: var(--s-5) var(--s-4) calc(var(--s-5) + env(safe-area-inset-bottom, 0px));
  border: 1px solid var(--border);
  border-start-start-radius: 18px;
  border-start-end-radius: 18px;
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-2);
  transform: translateY(100%);
  transition: transform var(--dur-slow) var(--ease);
}
.sheet[open], .sheet.is-open { transform: none; }
/* Стиль выше писался под обычный блок. У <dialog> в модальном режиме браузер
   ставит свои умолчания — inset: 0, width: fit-content, margin: auto, — и
   шторка прилипала к левому верхнему углу по ширине содержимого. Снимаем
   ровно эти три. */
dialog.sheet {
  inline-size: auto;
  max-inline-size: 100vw;
  inset-block-start: auto;
  margin-block-start: auto;
  /* Ширину задают распорки inset-inline, а отступы и рамка обязаны лечь
     внутрь: иначе шторка выходит за правый край ровно на их сумму. */
  box-sizing: border-box;
}
.sheet:not([open]):not(.is-open) { display: none; }
.sheet::backdrop { background: rgb(16 24 40 / .5); }
.sheet-handle {
  inline-size: 44px;
  block-size: 4px;
  margin: 0 auto var(--s-4);
  border-radius: var(--radius-pill);
  background: var(--border-strong);
}
.sheet-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  margin-block-end: var(--s-4);
}


@media (min-width: 860px) {
  .sheet {
    position: static;
    display: block;
    max-block-size: none;
    padding: var(--s-5);
    border-radius: var(--radius-card);
    box-shadow: none;
    transform: none;
  }
  .sheet-handle, .sheet-close { display: none; }
  }


main#main:focus { outline: none; }

/* 15. Служебные классы, движение, печать =================================== */

.visually-hidden {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.divider { block-size: 1px; background: var(--border); border: 0; margin-block: var(--s-6); }
.nowrap { white-space: nowrap; }


/* Кому анимации мешают, тот их не увидит. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
  .btn:active { transform: none; }}

/* Жёсткий контраст: полагаться на заливки нельзя, добавляем рамки. */
@media (forced-colors: active) {
  .btn, .card, .step, .notice, .badge { border: 1px solid; }
  /* Когда системная тема снимает заливку, главное действие остаётся главным
     за счёт толщины контура, а не цвета. */
  .btn-primary:not(:disabled):not([aria-disabled="true"]),
  .btn-on-dark { border-width: 3px; }
  .notice::before, .th-sort::after { background-color: CanvasText; }
  :where(a, button, input, select, textarea, summary, [tabindex]):focus-visible { outline: 3px solid Highlight; }}

/* Печатная ветка перенесена в конец слоя: см. раздел «Печать» ниже. */


/* 16. Галочки и переключатели ============================================== */
/* Рисуем сами по одной причине: у родного элемента цель 13 x 13 px, и увеличить
   её, не раздув сам квадратик, нельзя. Здесь прозрачный бокс 44 x 44 держит
   палец, а видимый квадрат остаётся 22 px. */

input[type="checkbox"], input[type="radio"] {
  appearance: none;
  -webkit-appearance: none;
  flex: none;
  display: grid;
  place-content: center;
  inline-size: 44px;
  block-size: 44px;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}
input[type="checkbox"]::before, input[type="radio"]::before {
  content: "";
  inline-size: 22px;
  block-size: 22px;
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  background: var(--surface);
  transition: background var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
input[type="radio"]::before { border-radius: var(--radius-pill); }

input[type="checkbox"]:hover::before, input[type="radio"]:hover::before { border-color: var(--accent); }

input[type="checkbox"]:checked::before {
  border-color: var(--accent);
  background: var(--accent) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m5 12.5 4.5 4.5L19 7'/%3E%3C/svg%3E") center / 15px no-repeat;
}
input[type="radio"]:checked::before {
  border-color: var(--accent);
  border-width: 6px;
  background: var(--surface);
}
input[type="checkbox"]:disabled, input[type="radio"]:disabled { cursor: not-allowed; opacity: .5; }
input[type="checkbox"]:disabled:hover::before,
input[type="radio"]:disabled:hover::before { border-color: var(--border-strong); }
input[type="checkbox"]:focus-visible, input[type="radio"]:focus-visible { outline: none; }
input[type="checkbox"]:focus-visible::before, input[type="radio"]:focus-visible::before {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}

/* В плотной таблице бокс не должен раздвигать строку. */
.table td > input[type="checkbox"], .table td > input[type="radio"] { margin: -10px; }
/* Подпись рядом с галочкой это второй крупный вход в тот же чекбокс. */
.table td > label {
  display: flex;
  align-items: center;
  min-block-size: var(--control-h);
  cursor: pointer;
}

/* Своя отрисовка не переживает жёсткий контраст: возвращаем родную. */
@media (forced-colors: active) {
  input[type="checkbox"], input[type="radio"] {
    appearance: auto;
    -webkit-appearance: auto;
    inline-size: 24px;
    block-size: 24px;
  }
  input[type="checkbox"]::before, input[type="radio"]::before { display: none; }}


/* 17. Классы кабинета ====================================================== */
/* Разметку кабинета пишет другой разработчик. Здесь оформление тех классов,
   которые он уже использует, чтобы страницы не оказались голыми. */

.fine { font-size: .875rem; line-height: 1.5; color: var(--text-2); }
.mono {
  overflow-wrap: anywhere;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-variant-numeric: tabular-nums;
  font-size: .9em;
}
@media (min-width: 860px) { .mono { font-size: .875rem; }}
.empty {
  padding: var(--s-6) var(--s-4);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-card);
  background: var(--subtle);
  color: var(--text-2);
  text-align: center;
}
.table td.empty { border: 0; background: none; text-align: center; }

.field-label { display: block; margin-block-end: var(--s-2); font-size: .9375rem; font-weight: 600; }

/* Поля кабинета размечены без класса .input, поэтому ловим их по типу. */
.field :where(input:not([type="checkbox"]):not([type="radio"]):not([type="file"]), select, textarea) {
  inline-size: 100%;
  min-block-size: var(--control-h);
  padding: 10px var(--s-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-control);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 1rem;
  transition: border-color var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}
@media (hover: hover) {
  .field :where(input:not([type="checkbox"]):not([type="radio"]):not([type="file"]), select, textarea):hover {
    border-color: var(--text-2);
  }
}
.field :where(input:not([type="checkbox"]):not([type="radio"]):not([type="file"]), select, textarea):focus {
  border-color: var(--accent);
}
.field :where(input[type="file"]:not(.visually-hidden)) {
  inline-size: 100%;
  min-block-size: var(--control-h);
  padding: 9px var(--s-3);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-control);
  background: var(--subtle);
  font: inherit;
  font-size: .9375rem;
  color: var(--text);
}
.field-inline {
  display: grid;
  gap: 0;
  margin-block-end: 0;
  flex: 1 1 14rem;
  min-inline-size: 0;
}
/* Ряд из подписанных полей и кнопки выравнивается по низу, иначе кнопка
   встаёт по центру подписи. */
.row:has(.field-inline) { align-items: end; }

.toolbar,
.sticky-actions,
.work-actions,
.filterbar {
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--surface);
}

.toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4);
  align-items: end;
  justify-content: space-between;
  margin-block: var(--s-5);
  padding: var(--s-4);
}
.toolbar-right { display: flex; flex-wrap: wrap; gap: var(--s-4); align-items: end; min-inline-size: 0; }
.toolbar .search, .toolbar .upload-form { display: flex; flex-wrap: wrap; gap: var(--s-3); align-items: end; min-inline-size: 0; }
.toolbar .field, .toolbar-right > * { min-inline-size: 0; }

/* Два варианта панели действий делят геометрию. Липкость, внешний отступ и
   тень остаются модификаторами: у них разные места в потоке. */
.sticky-actions,
.work-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  align-items: center;
  justify-content: space-between;
  padding: var(--s-3) var(--s-4);
}
/* Панель с кнопкой запуска липнет к низу, но не закрывает нижнюю навигацию. */
.sticky-actions {
  position: sticky;
  inset-block-end: 0;
  z-index: 20;
  margin-block: var(--s-4);
  box-shadow: var(--shadow-2);
}
.sticky-actions p { margin: 0; }
@media (max-width: 479.98px) {
  .sticky-actions { gap: var(--s-2); }
  .sticky-actions .btn { flex: 1 1 100%; }
}
/* Перебор лимита виден словом в разметке и полосой здесь, не одним цветом. */
.sticky-actions[data-over="yes"] { border-color: var(--error); border-inline-start: 4px solid var(--error); }
@media (max-width: 859.98px) {
  body:has(.mobile-nav) .sticky-actions { inset-block-end: var(--nav-h); }}

.cards { display: grid; gap: var(--s-4); grid-template-columns: minmax(0, 1fr); }
@media (min-width: 480px) { .cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }}
@media (min-width: 1024px) { .cards { grid-template-columns: repeat(3, minmax(0, 1fr)); }}
/* Наборы ровно из четырёх карточек: 2×2 на компактном десктопе и один ряд
   там, где каждой карточке остаётся хотя бы 18rem. Остальные .cards по три. */
@media (min-width: 1024px) {
  .cards.cards-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }}
@media (min-width: 1280px) {
  .cards.cards-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }}
.cards .card { display: flex; flex-direction: column; align-items: start; gap: var(--s-2); }
.cards .card h2 { font-size: 1rem; color: var(--text-2); margin: 0; }
.cards .card .btn { margin-block-start: auto; }

.stat {
  margin: 0;
  font-size: clamp(1.5rem, 1.3rem + 1vw, 2rem);
  font-weight: 700;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
}

/* Состояние заявки. Слово внутри, цвет сверху, слева полоса нужной толщины. */
.pill {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: 3px 10px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  background: var(--subtle);
  font-size: .8125rem;
  font-weight: 600;
  white-space: nowrap;
}
.pill::before {
  content: "";
  inline-size: .55em;
  block-size: .55em;
  border-radius: var(--radius-pill);
  background: currentColor;
}
.pill-created, .pill-queued { background: var(--subtle); border-color: var(--border-strong); color: var(--text-2); }
.pill-running { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
.pill-succeeded { background: var(--success-soft); border-color: var(--success); color: var(--success); }
.pill-partial { background: var(--warning-soft); border-color: var(--warning); color: var(--warning); }
.pill-failed { background: var(--error-soft); border-color: var(--error); color: var(--error); }
.pill-canceled { background: var(--subtle); border-color: var(--border-strong); color: var(--text-2); }
.pill-running::before { border-radius: 2px; }
.pill-partial::before { border-radius: 0; }
.pill-failed::before { inline-size: .6em; block-size: .15em; border-radius: 0; }

.job-head {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  align-items: flex-start;
  justify-content: space-between;
  margin-block-end: var(--s-4);
}
.job-head h1 { margin: 0; }
.job-state {
  flex: 1 1 22rem;
  max-inline-size: 36rem;
  display: grid;
  gap: var(--s-3);
}
.job-outcome {
  display: grid;
  justify-items: start;
  gap: var(--s-3);
  max-inline-size: 40rem;
  margin-block-end: var(--s-4);
}
.job-outcome .empty { inline-size: 100%; text-align: start; }


.progress {
  inline-size: 100%;
  max-inline-size: 22rem;
  block-size: 10px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  background: var(--subtle);
  overflow: hidden;
}
.progress > * , .progress::-webkit-progress-value { background: var(--accent); }
progress.progress { appearance: none; -webkit-appearance: none; }
progress.progress::-webkit-progress-bar { background: var(--subtle); }

/* Пока считать нечего — бегущий огонёк вместо доли.

   Движок печатает прогресс по уникальным карточкам, а не по позициям: десять
   позиций он склеивает, скажем, в семь карточек, и «3 из 7» честной долей от
   десяти не станет. Первые секунды прогресса нет вовсе — заявка в очереди,
   потом открывается браузер. Пустая полоса всё это время выглядит как
   «ничего не происходит», хотя происходит.

   Поэтому неопределённое состояние показывается движением, а не долей. Как
   только приходит первое число, полоса становится обычной: доля полезнее
   анимации, когда её есть чем наполнить. */
.progress.is-waiting { position: relative; }
/* Начальное состояние без inline-style: ширину до прихода первого числа
   держит здесь, а не атрибутом style (его режет CSP). */
.progress.is-waiting > * { inline-size: 100% !important; background: none; }
.progress:not(.is-waiting) > *:not([style]) { inline-size: 0; }
.progress.is-waiting::after {
  content: "";
  position: absolute;
  inset-block: 0;
  inline-size: 40%;
  border-radius: var(--radius-pill);
  background: linear-gradient(90deg,
    transparent, var(--accent) 45%, var(--accent) 55%, transparent);
  animation: progress-sweep 1.4s var(--ease, ease-in-out) infinite;
}
@keyframes progress-sweep {
  from { inset-inline-start: -40%; }
  to   { inset-inline-start: 100%; }}
/* Кому движение мешает — ровная приглушённая заливка вместо бега. */
@media (prefers-reduced-motion: reduce) {
  .progress.is-waiting::after {
    animation: none;
    inset-inline: 0;
    inline-size: 100%;
    background: var(--accent);
    opacity: .35;
  }}

.pager {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  align-items: center;
  justify-content: center;
  margin-block: var(--s-5);
}
.pager a {
  display: inline-flex;
  align-items: center;
  min-block-size: var(--control-h);
  padding-inline: var(--s-4);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-control);
  text-decoration: none;
  font-weight: 600;
}
.pager a:hover { background: var(--subtle); }
.pager span { color: var(--text-2); font-size: .9375rem; font-variant-numeric: tabular-nums; }

/* Каталог: строки одной высоты и метка на своём месте ---------------------- */
/* Раскладка фиксированная, иначе ширина колонок скачет от содержимого страницы,
   и наименование то влезает в строку, то нет. Ширины заданы всем колонкам,
   кроме наименования: оно забирает остаток. */
/* Ширины взяты из самого каталога, а не на глаз. Длина текста в колонках
   (933 позиции): код до 7 знаков, форма до 38, дозировка до 17, фасовка до 6,
   наименование до 87. Поэтому всем, кроме наименования, задан свой предел,
   а наименование забирает остаток — ему нужно больше всех. */
.table-catalog { table-layout: fixed; }
.table-catalog th:nth-child(1) { inline-size: 3.5rem; }   /* выбор */
.table-catalog th:nth-child(2) { inline-size: 6.5rem; }   /* код, до 7 знаков */
                                                          /* 3 — наименование */
.table-catalog th:nth-child(4) { inline-size: 10rem; }    /* форма, компактно */
.table-catalog th:nth-child(5) { inline-size: 7rem; }     /* дозировка, до 17 */
.table-catalog th:nth-child(6) { inline-size: 6rem; }     /* фасовка, до 6 */
.table-catalog td:nth-child(2) { font-variant-numeric: tabular-nums; }

/* Окно здесь не источник истины: после потолка app-shell оно растёт, а
   таблица уже нет. Контейнер 61rem выбран по данным: в широкой схеме при
   960px переносятся 15 из 40 названий, при 974px — ни одного. */
@media (min-width: 860px) {
  .catalog-table-pane {
    container-type: inline-size;
    container-name: catalog-table;
  }
}
@container catalog-table (min-width: 61rem) {
  .table-catalog th:nth-child(4) { inline-size: 13rem; }
  .table-catalog th:nth-child(5) { inline-size: 7.5rem; }
  .table-catalog th:nth-child(6) { inline-size: 5.5rem; }
}

/* Короткие списки мониторингов используют одни размеры на обзоре, странице
   мониторингов и в истории: даты и счётчики не раздуваются от свободного
   места, остаток достаётся состоянию. */
@media (min-width: 860px) {
  .table-job-list { table-layout: fixed; }
  .table-job-list .col-date { inline-size: 12rem; }
  .table-job-list .col-count { inline-size: 7rem; }
  .table-job-list .col-action { inline-size: 7rem; }
  .table-job-list td:first-child { white-space: nowrap; overflow-wrap: normal; }
}
/* Высота под две строки названия. Названия различаются хвостом — «15г» против
   «50г», — поэтому обрезать их нельзя, а одна строка вмещает не все. Две
   вмещают почти все, и строки получаются одной высоты.

   Только от 768 px, и это не придирка. Ниже эта же таблица раскладывается
   карточками: tr становится блоком с overflow: hidden. Фиксированная высота
   переживала превращение — у правила и специфичность выше, и стоит оно ниже
   блока с карточками, — и каждая карточка оставалась 72 px при нужных 313.
   Наружу торчала галочка и обрезанный код, названий не было видно вовсе. */
@media (min-width: 860px) {
  .table-catalog tbody tr { block-size: 4.5rem; }}
.table-catalog td { vertical-align: middle; }

/* Метка стоит у правого края своей ячейки и всегда на одном месте. Внутри
   текста она вела себя как слово: уезжала на вторую строку и тянула за собой
   высоту строки, из-за чего соседние строки получались разной высоты.
   Раскладка на вложенном блоке: на самой ячейке flex ломает таблицу. */
.cell-name {
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
}
.cell-name-text { min-inline-size: 0; }
.cell-name .badge { flex: 0 0 auto; }

/* На десктопе label остаётся строчным — тогда копирование таблицы даёт одну
   TSV-строку на препарат. Прозрачный псевдоэлемент расширяет тот же label до
   всей ячейки, поэтому подсвеченная строка нажимается не только по буквам. */
@media (min-width: 860px) {
  .table-catalog td { position: relative; }
  .table-catalog td > label {
    display: inline;
    min-block-size: 0;
  }
  .table-catalog td > label::after {
    content: "";
    position: absolute;
    inset: 0;
  }
  .table-catalog td:has(.cell-name .badge) { padding-inline-end: 7rem; }
  .cell-name .badge {
    position: absolute;
    z-index: 1;
    inset-inline-end: var(--s-4);
    inset-block-start: 50%;
    transform: translateY(-50%);
  }
  /* Когда предел уже набран, синяя подсветка не обещает ещё один выбор. */
  .table-catalog tr:has(input[name="code"]:disabled) td > label {
    cursor: default;
  }
  .table-catalog tbody tr:has(input[name="code"]:disabled):hover {
    background: var(--surface);
  }
  .table-catalog tbody tr:nth-child(even):has(input[name="code"]:disabled):hover {
    background: var(--subtle);
  }
}

.load-more {
  display: grid;
  justify-items: center;
  gap: var(--s-2);
  margin-block: var(--s-5);
}
/* hidden обязан прятать, что бы ни стояло в классе.

   Браузерное правило [hidden]{display:none} слабее любого класса, и всё, чему
   задан свой display, оставалось видимым: плашка «бот прислал код» —
   display: grid — висела на экране ещё до того, как бот что-либо прислал.
   Раньше это лечили поштучно (.load-more[hidden], .modal[hidden]), и каждая
   новая скрываемая вещь наступала на грабли заново. */
[hidden] { display: none !important; }

.load-more[hidden] { display: none; }

.suggest { margin-block-start: var(--s-6); }
.suggest .row { align-items: end; }


.btn-danger, .btn.danger {
  background: var(--surface);
  border-color: var(--error);
  color: var(--error);
}
.btn-danger:hover, .btn.danger:hover { background: var(--error-soft); color: var(--error); }
.danger { color: var(--error); }
.danger :where(input, select, textarea) { color: var(--text); }
form.danger {
  /* Меряется своим содержимым: в поле вводят одно слово, и растянутая на всю
     ширину красная коробка пугает сильнее, чем сообщает. */
  inline-size: fit-content;
  max-inline-size: 100%;
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--error);
  border-inline-start-width: 4px;
  border-radius: var(--radius-card);
  background: var(--error-soft);
}
form.danger > * + * { margin-block-start: var(--s-3); }
form.danger input[type="text"] { inline-size: 14rem; }

.block { display: block; inline-size: 100%; }
.size { font-variant-numeric: tabular-nums; color: var(--text-2); }

/* Юридические документы: markdown, отрисованный сервисом. */
.doc { max-inline-size: var(--measure); }
.doc > * + h2 { margin-block-start: var(--s-6); }
.doc > * + h3 { margin-block-start: var(--s-5); }
.doc ul, .doc ol { padding-inline-start: 1.35em; }
.doc li { margin-block-end: var(--s-2); }
.doc blockquote {
  margin: var(--s-4) 0;
  padding: var(--s-3) var(--s-4);
  border-inline-start: 4px solid var(--accent);
  border-radius: var(--radius-control);
  background: var(--accent-soft);
}
.doc code {
  overflow-wrap: anywhere;
  padding: 1px 5px;
  border-radius: 6px;
  background: var(--subtle);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .9em;
}
/* Оглавление документа и «Наверх». */
.toc {
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--subtle);
  padding: var(--s-3) var(--s-4);
  margin-block: var(--s-4);
}
.toc > summary { cursor: pointer; font-weight: 600; min-block-size: 44px; display: flex; align-items: center; }
.toc ol {
  margin: var(--s-3) 0 0;
  padding-inline-start: 0;
  list-style: none;
  display: grid;
  gap: var(--s-1);
}
.toc a { color: var(--accent); text-decoration: none; }
.toc a:hover { text-decoration: underline; }
.toc .toc-l3 { margin-inline-start: var(--s-4); font-size: .9375rem; }
.toc[data-toc-placement="rail"] { display: none; }
@media (max-width: 1023.98px) {
  /* В документе остаётся оглавление перед текстом; скрытая копия опоры не
     должна добавлять пустую строку сетки после длинного документа. */
  .page-rail:has(> .toc[data-toc-placement="rail"]) { display: none; }
}
@media (min-width: 1024px) {
  .toc[data-toc-placement="inline"] { display: none; }
  .toc[data-toc-placement="rail"] { display: block; margin: 0; }
}
.doc-top-link { margin-block-start: var(--s-6); }
.doc-top-link a {
  display: inline-flex; align-items: center; min-block-size: 44px;
  color: var(--text-2); text-decoration: none;
}

.doc table {
  inline-size: 100%;
  border-collapse: collapse;
  margin-block: var(--s-4);
  font-size: .9375rem;
}
.doc th, .doc td {
  overflow-wrap: anywhere;
  padding: var(--s-2) var(--s-3);
  border: 1px solid var(--border);
  text-align: start;
  vertical-align: top;
}
.doc th { background: var(--subtle); }
/* Документы на телефоне: таблица разворачивается в карточки, подпись строки
   берётся из data-label (проставляется на сервере, app/routers/public.py). */
/* Документная таблица не запирается в скролл-бокс ни на одной ширине: внутри
   длинного текста обрезанная таблица читается как конец раздела. */
.table-scroll:has(.doc-table) { max-block-size: none; overflow: visible; }
@media (max-width: 859.98px) {
  .doc-table td[data-label]::before {
    content: attr(data-label) ": ";
    font-weight: 600;
    color: var(--text-2);
  }
}
.doc sup a { text-decoration: none; }
.doc-links { display: flex; flex-wrap: wrap; gap: var(--s-3); margin-block-start: var(--s-6); }

/* ------------------------------------------------------------------ *
 * 18. Кабинет на узком экране
 *
 * Раздел опустел, и это правильный итог. Здесь жили три правила, и все
 * три были лишними:
 *
 *   .app-sidebar { display: none }  — бокового меню нет в разметке;
 *   .app-shell { padding-block-end: 56+24+safe }  — место под панель
 *     подкладывает body через --nav-h, а второй раз это давало 136 px
 *     пустоты и двойную safe-area (C-09, E-13);
 *   .app-shell .mobile-nav a { … }  — селектор не срабатывал никогда:
 *     панель включается из {% block chrome %} вне main.app-shell, и
 *     применялось публичное правило из раздела 14 (C-10).
 *
 * Вместе с ними ушёл порог 959 px — один из четырёх, на которых
 * переключался мобильный хром.
 * ------------------------------------------------------------------ */

/* ================================================================== *
 * 19. Единый отступ и фирменный слой FlexPricer
 *
 * Отступ. У .wrap поле задавалось тремя правилами на разных ширинах,
 * а .app-shell не .wrap и полей не имел вовсе: кабинет упирался в край
 * экрана, публичные страницы нет. Теперь и то и другое берёт --gutter,
 * поэтому шапка, контент и подвал стоят по одной линии на любой ширине.
 *
 * Фирменный слой. Правила навешены на классы, которые уже есть в
 * разметке, поэтому градиент появляется сам и ничего не надо
 * переразмечать. Яркая пара мятный-синий идёт только туда, где нет
 * текста; там, где текст белый, работает тёмная пара с контрастом
 * 5.91 и 6.70 на концах.
 * ================================================================== */

/* Поле задаётся только .wrap и .app-shell. Шапка и подвал отдельного
   правила не получают: в разметке это тот же самый элемент
   (<div class="wrap header-inner">), и любое своё padding-inline тут
   затирало бы отступ .wrap и прибивало содержимое к краю экрана. */
.wrap,
.app-shell { padding-inline: var(--gutter); }

/* Подвал кабинета живёт внутри .app-footer и обёрнут в .wrap: без этого он
   остаётся на ширине публичной страницы, пока рабочая область уже 100rem. */
.app-footer > .wrap { max-inline-size: var(--wrap-app); }

.app-shell {
  inline-size: 100%;
  /* Потолок здесь НЕ ставим: он объявлен один раз, при самом .app-shell
     (100rem). Стоявший тут var(--wrap) затирал его по порядку внутри слоя и
     запирал кабинет в ширину публичной страницы. */
  margin-inline: auto;
}

main,
.site-footer { position: relative; z-index: 1; }

/* На телефоне верх уже занят нижней навигацией. Сохраняем принятую мобильную
   механику: публичная шапка уезжает при прокрутке, липнет только с 860 px. */
@media (max-width: 859.98px) {
  .site-header { position: relative; z-index: 1; }
}

/* --- главная кнопка: градиент, белый текст, лёгкий блик --- */
.btn-primary {
  background: var(--ai-gradient-ink);
  border-color: transparent;
  color: var(--accent-ink);
  position: relative;
  overflow: hidden;
}
.btn-primary::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, rgb(255 255 255 / 0) 30%,
                              rgb(255 255 255 / .28) 50%, rgb(255 255 255 / 0) 70%);
  mix-blend-mode: overlay;
  transform: translateX(-120%);
  transition: transform var(--dur-slow) var(--ease);
}
.btn-primary:hover::after { transform: translateX(120%); }
.btn-primary:hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
}
.btn-primary:disabled,
.btn-primary[aria-disabled="true"] {
  background: var(--subtle);
  border-color: var(--border-strong);
  color: var(--text-2);
  opacity: 1;
  filter: none;
}
.btn-primary:is(:disabled, [aria-disabled="true"])::after { display: none; }

/* --- крупное число в карточке: ink, табличные цифры --- */
.stat {
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* --- логотип в шапке: отклик на наведение --- */
.brand:hover { color: var(--accent-hover); }

/* --- полоса прогресса заявки: сплошной акцент --- */
.progress > *,
.progress::-webkit-progress-value,
progress.progress::-moz-progress-bar { background: var(--accent); }

/* --- значок «считает машина»: тихая плашка --- */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: 3px 10px;
  background: var(--accent-soft);
  color: var(--accent);
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  font-size: .8125rem;
  font-weight: 600;
  white-space: nowrap;
}

/* Свечение и блик отключаются вместе с остальным движением и в печати. */
@media (prefers-reduced-motion: reduce) {
  .btn-primary::after { display: none; }}

/* ------------------------------------------------------------------ *
 * 20. Ритм разделов
 *
 * Заголовок, идущий следом за блоком карточек или таблицей, прижимался
 * вплотную. Задаём один вертикальный шаг на весь сайт, чтобы расстояния
 * между разделами были одинаковыми и на широком экране, и на узком.
 * ------------------------------------------------------------------ */
/* Карточка исключена: у неё свои поля, и ещё один шаг сверху делает из неё
   пустую коробку с заголовком посередине. Правило специфичнее, чем
   .card > h2, поэтому без исключения оно его перебивает. */
.app-content > h2,
main > h2,
.app-content > section:not(.card) > h2 { margin-block: var(--s-7) var(--s-4); }

/* Публичные шаблоны комбинируют .page-main, .narrow и .stack. Один селектор
   возвращает одинаковый шаг перед настоящими разделами, но не добавляет его
   заголовку, который открывает блок. Карточки сюда не входят. */
@media (min-width: 1024px) {
  body.public :where(.page-main, .narrow, .stack) > h2:not(:first-child) {
    margin-block: var(--s-7) var(--s-3);
  }
}

.app-content > h1,
main > h1 { margin-block: 0 var(--s-4); }

.cards + h2,
.table + h2,
.toolbar + h2,
form + h2 { margin-block-start: var(--s-7); }

/* Большие таблицы кабинета обёрнуты в отдельный scroll-box. Исправление
   прежнего селектора включаем только в настольном режиме: мобильный ритм уже
   принят отдельной прожаркой и не должен меняться из-за этого этапа. */
@media (min-width: 1024px) {
  .table-scroll + h2 { margin-block-start: var(--s-7); }
}

.app-content > :first-child,
main > :first-child { margin-block-start: 0; }

.cards,
.app-content > .table,
.app-content > form,
.app-content > .toolbar { margin-block-end: var(--s-6); }

@media (min-width: 1024px) {
  .app-content > .table-scroll { margin-block-end: var(--s-6); }
}

.disclaimer { margin: 0; font-size: .875rem; color: var(--text-2); }

/* ------------------------------------------------------------------ *
 * 21. Таблицы-определения на узком экране
 *
 * На публичных страницах есть таблицы без шапки: слева термин, справа
 * пояснение. На 375 px первая колонка переносится по словам и обе
 * сжимаются в кашу. Раскладываем такую пару в столбик: термин строкой,
 * пояснение под ним. Отбор идёт по отсутствию thead, поэтому таблицы
 * кабинета с заголовками правило не задевает.
 * ------------------------------------------------------------------ */
@media screen and (max-width: 859.98px) {
  /* C-20: до 640 таблицы без thead оставались таблицами в зазоре 641-859. */
  .table:not(:has(thead)) tr {
    display: block;
    border-block-end: 1px solid var(--border);
    padding-block: var(--s-3);
  }
  .table:not(:has(thead)) tr:last-child { border-block-end: 0; }
  .table:not(:has(thead)) :is(td, th[scope="row"]) {
    display: block;
    border: 0;
    padding-inline: 0;
  }
  .table:not(:has(thead)) :is(td:first-child, th[scope="row"]) {
    font-weight: 600;
    padding-block: 0 var(--s-1);
  }
  .table:not(:has(thead)) td:last-child {
    padding-block: 0;
    color: var(--text-2);
  }}


/* 22. Parso: знак, плашка кабинета, чат ==================================== */
/*
   Раздел добавлен при переходе на Parso. Всё, что было раньше, переиспользуется
   как есть: кнопки, поля, карточки, таблицы с превращением в карточки на узком
   экране. Здесь только то, чего в системе не было.
*/

/* --- Знак ---------------------------------------------------------------- */
/* Знак рисуется рамкой с градиентом, а не картинкой: в шапке он нужен на
   каждой странице, и лишний запрос за 500 байт того не стоит. */
.brand { display: inline-flex; align-items: center; gap: var(--s-3); }
.brand-mark {
  inline-size: 28px;
  block-size: 28px;
  flex: 0 0 auto;
  border-radius: 7px;
  background: var(--accent);
  position: relative;
}
.brand-mark::after {
  content: "P";
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: #fff;
  font-weight: 700;
  font-size: 17px;
  line-height: 1;
}
.brand-name { font-weight: 700; letter-spacing: -.01em; }
.brand-sm .brand-mark { inline-size: 24px; block-size: 24px; border-radius: 6px; }
.brand-sm .brand-mark::after { font-size: 14px; }

/* --- Первый экран -------------------------------------------------------- */
.hero-text { max-inline-size: 34rem; }
.hero-text h1 { font-size: clamp(2rem, 1.4rem + 2.6vw, 3.25rem); line-height: 1.08; }
.hero-text .lead { max-inline-size: 30rem; }

/* На широком экране второй столбец уже есть в содержании: заголовок остаётся
   слева, а лид, действия и оговорка складываются справа. Нового текста и
   повторной таблицы для заполнения пустоты не требуется. */
@media (min-width: 1280px) {
  .hero { max-inline-size: none; }
  .hero-text {
    display: grid;
    grid-template-columns: minmax(46rem, 1fr) minmax(22rem, 28rem);
    column-gap: var(--s-7);
    align-items: start;
    max-inline-size: none;
  }
  .hero-text h1 {
    grid-column: 1;
    grid-row: 1 / span 3;
    align-self: start;
    font-size: clamp(3.25rem, 2.6rem + .8vw, 3.875rem);
  }
  .hero-text .lead,
  .hero-text .hero-actions,
  .hero-text > .fine { grid-column: 2; }
  .hero-text .lead {
    max-inline-size: none;
    margin-block: 0 var(--s-4);
    font-size: clamp(1.25rem, 1.05rem + .2vw, 1.375rem);
  }
  .hero-text .hero-actions { margin-block: 0 var(--s-4); }
}

/* --- Блоки главной ------------------------------------------------------- */

.cards-3 { grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }


.ai-teaser {
  margin-block: var(--s-7);
  padding: var(--s-6);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--surface);
  background-image: var(--ai-glow);
}
.ai-teaser-inner { max-inline-size: 40rem; }
.ai-teaser h2 { margin-block-start: var(--s-2); }

/* --- Всплывающее окно входа ---------------------------------------------- */
/*
   Требования, которые нельзя нарушать при правке: видимый крестик, закрытие
   по клику вне окна и по Esc, одно поле, без таймера. Окно не показывается
   второй раз за сессию — отметку ставит app.js.
*/
.modal { position: fixed; inset: 0; z-index: 60; }
.modal[hidden] { display: none; }
.modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgb(15 30 51 / .45);
  animation: fade var(--dur) var(--ease);
}
.modal-card {
  position: relative;
  z-index: 1;
  inline-size: min(28rem, calc(100% - var(--s-5) * 2));
  margin: min(12vh, 6rem) auto 0;
  padding: var(--s-6);
  border-radius: var(--radius-card);
  background: var(--surface);
  box-shadow: var(--shadow-2);
  animation: rise var(--dur-slow) var(--ease);
}
.modal-card h2 { margin-block: var(--s-2) var(--s-3); font-size: 1.5rem; }
.modal-close {
  position: absolute;
  inset-block-start: var(--s-3);
  inset-inline-end: var(--s-3);
  inline-size: 44px;
  block-size: 44px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--radius-control);
  background: none;
  color: var(--text-2);
  cursor: pointer;
}
.modal-close:hover { background: var(--subtle); color: var(--text); }

@keyframes fade { from { opacity: 0 } to { opacity: 1 }}
@keyframes rise {
  from { opacity: 0; transform: translateY(12px) }
  to   { opacity: 1; transform: none }}
@media (prefers-reduced-motion: reduce) {
  .modal-backdrop, .modal-card { animation: none; }}

/* На узком экране окно приезжает снизу: тянуться большим пальцем к центру
   экрана неудобно, а крестик должен оставаться в досягаемости. */
@media (max-width: 479.98px) {
  .modal-card {
    position: absolute;
    inset-block-end: 0;
    inset-inline: 0;
    inline-size: 100%;
    margin: 0;
    border-end-start-radius: 0;
    border-end-end-radius: 0;
    max-block-size: 92dvh;
    overflow-y: auto;
  }
  @keyframes rise {
    from { opacity: 0; transform: translateY(100%) }
    to   { opacity: 1; transform: none }}}

/* --- Верхняя плашка кабинета --------------------------------------------- */
/*
  Одна линия: знак, разделы, состояние доступа и выход. Разделов девять, и на
  средней ширине они не влезают рядом с почтой — поэтому почта прячется
  первой, а сами разделы прокручиваются вбок внутри своей полосы. Ниже 1024 px
  они уходят во вторую строку, а четыре главных дублирует нижняя панель.
*/
.app-bar {
  position: sticky;
  inset-block-start: 0;
  z-index: 30;
  container: app-bar / inline-size;
  background: var(--surface);
  border-block-end: 1px solid var(--border);
}
/* Плашка тянется во всю ширину окна, а не по текстовой колонке: это панель
   рабочего приложения. На 1160 px разделы переставали помещаться и уезжали в
   прокрутку, а прокрутка в прилипшей плашке скрывает разделы насовсем. */
.app-bar-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3) var(--s-5);
  max-inline-size: var(--wrap-app);
  margin-inline: auto;
  padding: var(--s-2) var(--gutter);
}

.app-bar-right {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-inline-start: auto;
  min-inline-size: 0;
}

/* На телефоне знак, состояние доступа и выход не помещаются в строку втроём и
   переносятся, отчего шапка занимает две строки и съедает треть экрана. Слово
   «Parso» убираем: значок остаётся и по-прежнему ведёт в «Обзор», а название
   и так стоит во вкладке браузера. Экономит около 90 px по горизонтали и
   ровно одну строку по вертикали — на каждом экране кабинета. */
@media (max-width: 479.98px) {
  .app-bar .brand-name { display: none; }
  .app-bar-inner { gap: var(--s-2) var(--s-3); }}
.app-bar-email {
  display: none;
  color: var(--text-2);
  font-size: .875rem;
  max-inline-size: 14rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.app-bar-logout { margin: 0; }

.plan-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-block-size: 32px;
  padding-inline: var(--s-3);
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: .8125rem;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  /* В шапке кабинета на 320 «Пробный · 5 дней» иначе раздвигает панель. */
  max-inline-size: 11rem;
  overflow: hidden;
  text-overflow: ellipsis;
}
.plan-chip:hover { border-color: var(--accent); }
.plan-chip-days { font-weight: 400; }
.plan-chip-warn { background: var(--warning-soft); color: var(--warning); }
.plan-chip-off  { background: var(--error-soft); color: var(--error); }

.app-tabs {
  display: flex;
  gap: var(--s-1);
  overflow-x: auto;
  scrollbar-width: none;
  /* вторая строка на узком экране: занимает всю ширину и прокручивается */
  order: 3;
  flex: 1 1 100%;
  margin-inline: calc(var(--s-3) * -1);
  padding-inline: var(--s-3);
}

/* Ниже 860 навигация одна — нижняя панель, а этот ряд спрятан. Восемь
   разделов в строку не помещаются: она прокручивалась вбок, и раздел за краем
   человек не находил. Заодно уходит вторая подсветка текущего раздела — она
   была сразу в двух местах, наверху и внизу.

   Правило стоит здесь, а не рядом с нижней панелью: там оно оказывалось выше
   по файлу, чем display: flex этого же селектора, и не срабатывало. */
@media (max-width: 859.98px) {
  .app-tabs { display: none; }}
.app-tabs::-webkit-scrollbar { display: none; }
.app-tabs a {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  min-block-size: 40px;
  padding-inline: var(--s-3);
  border-radius: var(--radius-control);
  color: var(--text-2);
  font-size: .9375rem;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}
@media (min-width: 860px) and (max-width: 1279.98px) {
  .app-tabs { flex-wrap: wrap; }
  .app-tabs a { padding-inline: var(--s-2); }
}
.app-tabs a:hover { color: var(--text); background: var(--subtle); }
.app-tabs a[aria-current="page"] {
  color: var(--accent);
  background: var(--accent-soft);
}

@media (min-width: 1280px) {
  .app-bar-inner { flex-wrap: nowrap; }
  .app-tabs {
    order: 0;
    flex: 0 1 auto;
    flex-wrap: nowrap;
    min-inline-size: 0;
    margin-inline: 0;
    padding-inline: 0;
    /*
      Прокрутки в прилипшей плашке быть не должно: раздел, уехавший за край,
      человек не найдёт, а полосы прокрутки там не видно. Поэтому разделы
      ужимаются отступами, пока не поместятся все восемь. «Настройки» уехали
      направо к выходу, они и освободили место.
    */
    overflow: visible;
  }
  .app-tabs a { padding-inline: var(--s-3); }
  .app-bar-right { flex: 0 0 auto; margin-inline-start: auto; }}

/* Значок с действием: настройки, справка. Цель нажатия 40 px. */
.icon-link {
  display: inline-grid;
  place-items: center;
  inline-size: 40px;
  block-size: 40px;
  border-radius: var(--radius-control);
  color: var(--text-2);
}
.icon-link:hover { background: var(--subtle); color: var(--text); }
.icon-link[aria-current="page"],
.icon-link.is-section { background: var(--accent-soft); color: var(--accent); }
/* Почта уходит первой: она справочная, а разделы и выход нужны для работы.
   Запрос смотрит на саму плашку. На 1280 вкладкам не хватает 53 px; от 1360
   все девять помещаются с запасом, включая короткое имя аккаунта. */
@container app-bar (min-width: 85rem) {
  .app-bar-email { display: block; }}

.app-footer {
  margin-block-start: var(--s-8);
  padding-block: var(--s-5);
  border-block-start: 1px solid var(--border);
}
.app-footer .fine { margin: 0; text-align: center; }


/* --- Чат ассистента ------------------------------------------------------ */
.chat {
  margin-block: var(--s-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--surface);
  overflow: hidden;
}
.chat-log {
  display: grid;
  gap: var(--s-4);
  padding: var(--s-5);
  max-block-size: 32rem;
  overflow-y: auto;
}

.msg { display: flex; align-items: start; }
.msg-user { justify-content: flex-end; }
.msg-bot  { justify-content: flex-start; }
.bubble {
  overflow-wrap: anywhere;
  max-inline-size: min(42rem, 88%);
  padding: var(--s-3) var(--s-4);
  border-radius: var(--radius-card);
  font-size: .9375rem;
}
.msg-user .bubble { background: var(--subtle); }
.msg-bot .bubble {
  /* Фирменная рамка нарисована двумя фонами, а не border-image: тот с
     радиусом не работает и заставлял держать прямой угол. Приём тот же, что
     у значков и карточек ParsoAI: заливка по padding-box, градиент по
     border-box, сама рамка прозрачная. Радиус при этом соблюдается. */
  border: 1px solid transparent;
  border-inline-start-width: 3px;
  border-radius: var(--radius-card);
  background:
    linear-gradient(var(--surface), var(--surface)) padding-box,
    var(--ai-gradient) border-box;
}
.bubble > :first-child { margin-block-start: 0; }
.bubble > :last-child { margin-block-end: 0; }
.bubble .table { font-size: .875rem; }

.num.up { color: var(--warning); font-weight: 600; }
.num.down { color: var(--success); font-weight: 600; }

.chat-form {
  display: flex;
  gap: var(--s-3);
  padding: var(--s-4);
  border-block-start: 1px solid var(--border);
  background: var(--subtle);
}
.chat-form input[type="text"] {
  flex: 1 1 auto;
  min-inline-size: 0;
  min-block-size: var(--control-h);
  padding-inline: var(--s-4);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  transition: border-color var(--dur-fast) var(--ease);
}
@media (hover: hover) {
  .chat-form input[type="text"]:hover { border-color: var(--text-2); }
}
.chat-form input[type="text"]:focus { border-color: var(--accent); }
.chat-form button {
  flex: 0 0 auto;
  inline-size: var(--control-h);
  padding: 0;
  border-radius: var(--radius-pill);
  display: grid;
  place-items: center;
}
.chat-head, .ai-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-4);
  flex-wrap: wrap;
}

/* Готовые вопросы: капсулы над полем ввода. Один акцент, hairline-рамка. */
.chat-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  padding: var(--s-3) var(--s-4);
  border-block-start: 1px solid var(--border);
}
.chip-q {
  padding: var(--s-2) var(--s-4);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--accent);
  font: inherit;
  font-size: .875rem;
  font-weight: 600;
  cursor: pointer;
}
@media (hover: hover) {
  .chip-q:hover { border-color: var(--accent); background: var(--subtle); }
}
.chip-q[aria-disabled="true"] {
  border-color: var(--border-strong);
  background: var(--subtle);
  color: var(--text-2);
  opacity: .65;
  cursor: not-allowed;
}
.chip-q[aria-disabled="true"]:hover {
  border-color: var(--border-strong);
  background: var(--subtle);
}

/* Надписи ParsoAI: градиентный текст, вес не меняется. Класс общий для
   навигации, заголовка страницы и упоминаний в карточках.
   Селекторы с aria-current — чтобы активная вкладка не перекрашивала
   градиент обратно в сплошной цвет. */
.ai-brand,
.app-tabs a.nav-ai, .app-tabs a.nav-ai[aria-current="page"],
.mobile-nav a.nav-ai span,
.sheet-links a.nav-ai {
  background-image: var(--ai-gradient);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
/* Блочный заголовок тянется на всю строку, и слову достаётся только синий
   край градиента. Ширина по содержимому возвращает оба цвета буквам. */
h1.ai-brand, h2.ai-brand { inline-size: fit-content; }
/* У активной вкладки два слоя: градиент режется по буквам, подложка
   капсулы — по рамке. Порядок слоёв и clip идут парой. */
.app-tabs a.nav-ai[aria-current="page"] {
  background-image: var(--ai-gradient),
    linear-gradient(var(--accent-soft), var(--accent-soft));
  -webkit-background-clip: text, border-box;
  background-clip: text, border-box;
}
/* В нижней панели градиент получают только подпись и обводка значка: сама
   ссылка остаётся непрозрачной, и подложка активной вкладки не срезается. */
.mobile-nav a.nav-ai .icon { stroke: url(#ai-ink) var(--ai-a); }
.svg-defs { position: absolute; inline-size: 0; block-size: 0; overflow: hidden; }
.ai-ink-a { stop-color: var(--ai-a); }
.ai-ink-b { stop-color: var(--ai-b); }

/* Город заявки: две капсулы в своей строке панели запуска, чтобы не толкать
   счётчик и кнопку. Отмеченная — акцентом, фокус виден с клавиатуры. */
.city-pick { flex: 1 1 100%; }
.city-pick-legend { margin-block-end: var(--s-2); font-size: .875rem; color: var(--text-2); }
.city-pick-options { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.city-pick-option {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-block-size: 44px;
  padding-inline: var(--s-4);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text);
  font-weight: 600;
  cursor: pointer;
}
.city-pick-option input {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.city-pick-option:has(input:checked) {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent);
}
.city-pick-option:has(input:focus-visible) { outline: 2px solid var(--focus); outline-offset: 2px; }
/* На телефоне панель запуска прилипает над нижней навигацией и отнимает
   таблицу: подпись уходит только к экранным читалкам, капсулы — в сегменты. */
@media (max-width: 479.98px) {
  .city-pick-legend {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .city-pick-option { flex: 1 1 0; justify-content: center; padding-inline: var(--s-2); }
}

/* Экран чата: страница не прокручивается, прокручивается только лента.
   Высота собирается флексом от body — шапка и подвал остаются на местах,
   а чату достаётся всё остальное. */
body.ai-screen-body {
  /* min, а не фиксированная высота, и без overflow: hidden: на iOS клавиатура
     иначе не давала документу прокрутиться, и поле ввода уезжало под неё (Р-5). */
  min-block-size: 100dvh;
  display: flex;
  flex-direction: column;
}
body.ai-screen-body main.app-shell {
  flex: 1 1 auto;
  min-block-size: 0;
  display: flex;
  flex-direction: column;
}
body.ai-screen-body footer { display: none; }  /* экрану чата не нужен */
/* Внутри main живёт обёртка .app-content — флекс тянется через неё. */
.ai-screen .app-content {
  flex: 1 1 auto;
  min-block-size: 0;
  display: flex;
  flex-direction: column;
}
.ai-screen .ai-head { flex: 0 0 auto; }
.ai-screen .chat {
  flex: 1 1 auto;
  min-block-size: 0;
  display: flex;
  flex-direction: column;
  margin-block-end: var(--s-4);
}
.ai-screen .chat-log {
  flex: 1 1 auto;
  min-block-size: 0;
  max-block-size: none;
  overflow-y: auto;
  /* Прокрутка ленты не проваливается в прокрутку документа. */
  overscroll-behavior: contain;
  /* Приветственный пузырь прижат к низу, а не растянут по всей ленте (E-12). */
  align-content: end;
}
.ai-screen .chat-chips { flex: 0 0 auto; }
/* Поле ввода липнет к низу; когда всплывает клавиатура, документ прокручивается
   под ним, а не прячет его (Р-5). */
.ai-screen .chat-form {
  flex: 0 0 auto;
  position: sticky;
  inset-block-end: 0;
  background: var(--surface);
}
/* Телефон: кнопки вопросов сворачиваются в одну прокручиваемую строку —
   экрану чата достаётся высота. Описание раскрывает нативный <details>
   ниже: отдельная стрелка на h1 дублировала его и не имела обработчика. */
@media (max-width: 859.98px) {
  .ai-screen .chat-chips {
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x proximity;
    scroll-padding-inline: var(--s-4);
    scrollbar-width: none;
    /* Правый край гаснет — видно, что лента капсул листается (E-11). */
    -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 2.5rem), transparent);
    mask-image: linear-gradient(90deg, #000 calc(100% - 2.5rem), transparent);
  }
  .ai-screen .chat-chips::-webkit-scrollbar { display: none; }
  .ai-screen .chip-q {
    white-space: nowrap;
    flex: 0 0 auto;
    scroll-snap-align: start;
    max-inline-size: 72vw;
    overflow: hidden;
    text-overflow: ellipsis;
    min-block-size: 44px;
  }}

/* Описание ассистента: на телефоне сворачивается в details, на ≥600 показано
   постоянно, маркер прячется (E-15, E-19: на планшете места хватает).
   Закрытый details скрывает детей через служебный ::details-content, поэтому
   одного display:block у абзаца недостаточно. В браузере без этого
   псевдоэлемента summary остаётся видимым как безопасный запасной путь. */
.ai-desc > summary {
  cursor: pointer;
  color: var(--text-2);
  font-size: .875rem;
  min-block-size: 44px;
  display: flex;
  align-items: center;
}
@media (min-width: 600px) {
  @supports selector(::details-content) {
    .ai-desc > summary { display: none; }
    .ai-desc::details-content {
      content-visibility: visible;
      block-size: auto;
    }
    .ai-desc > .muted { display: block; }
  }
}

/* На телефоне чат остаётся экраном во всю высоту: новые сообщения живут у
   клавиатуры. От 860 px это обычная рабочая страница. Короткая история больше
   не раздувает пустую ленту на весь монитор, ссылки подвала снова доступны,
   а колонка 60rem держит реплики рядом и оставляет 912 px под таблицу. */
@media (min-width: 860px) {
  body.ai-screen-body {
    min-block-size: 0;
    display: block;
  }
  body.ai-screen-body main.app-shell { display: grid; }
  body.ai-screen-body footer { display: block; }
  /* .app-content.work снимает общий потолок рабочих экранов ниже по файлу;
     дополнительный класс в селекторе оставляет локальный потолок сильнее
     независимо от порядка этих двух правил. */
  .ai-screen .app-content.work {
    display: block;
    max-inline-size: 60rem;
  }
  .ai-screen .chat { display: block; }
  .ai-screen .chat-log { max-block-size: min(60vh, 40rem); }
  .ai-screen .chat-form { position: static; }
  .ai-screen .bubble-data {
    inline-size: 100%;
    max-inline-size: none;
  }
}

/* Совсем низкое окно в ландшафте: чат не влезает — обычная прокрутка.
   Условие orientation обязательно: иначе режим включался при появлении
   клавиатуры в портрете и раскладка прыгала (Р-5). */
@media (max-height: 480px) and (orientation: landscape) {
  body.ai-screen-body { block-size: auto; overflow: auto; }
  body.ai-screen-body main.app-shell { overflow: visible; display: block; }
  .ai-screen .app-content { display: block; overflow: visible; }
  .ai-screen .chat { display: block; }
  .ai-screen .chat-log { max-block-size: 24rem; }}

/* Градиентная рамка чата: тот же приём двух фонов, что у пузыря. Анимация —
   медленный дрейф градиента, при reduced-motion рамка статична. */
.ai-screen .chat {
  border-color: transparent;
  background:
    linear-gradient(var(--surface), var(--surface)) padding-box,
    var(--ai-gradient) border-box;
  background-size: auto, 220% 100%;
}
/* Бесконечная анимация рамки — только там, где есть мышь и большой экран:
   на телефоне это постоянная перерисовка блока чата (E-18). */
@media (hover: hover) and (min-width: 860px) {
  .ai-screen .chat { animation: ai-frame 12s ease-in-out infinite alternate; }
}
@keyframes ai-frame {
  0% { background-position: 0 0, 0% 50%; }
  100% { background-position: 0 0, 100% 50%; }}
@media (prefers-reduced-motion: reduce) {
  .ai-screen .chat { animation: none; }}

/* Действия и уточнения внутри пузыря ответа: Excel, Картинкой, варианты. */
.chat-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin-block-start: var(--s-3);
}

/* График в ответе: линия минимальной цены, один акцент. */
.ai-chart {
  inline-size: 100%;
  max-inline-size: 24rem;
  block-size: auto;
  margin-block-start: var(--s-3);
  color: var(--accent);
}
@media (min-width: 860px) {
  .ai-chart { max-inline-size: 40rem; }
}

/* «Думает»: три точки в пузыре ассистента. */
.bubble-typing { display: inline-flex; gap: .3rem; padding-block: .3rem; }
.bubble-typing span {
  inline-size: .45rem;
  block-size: .45rem;
  border-radius: 50%;
  background: var(--text-2);
  animation: ai-dot 1.2s ease-in-out infinite;
}
.bubble-typing span:nth-child(2) { animation-delay: .2s; }
.bubble-typing span:nth-child(3) { animation-delay: .4s; }
@keyframes ai-dot {
  0%, 60%, 100% { opacity: .25; transform: translateY(0); }
  30% { opacity: 1; transform: translateY(-2px); }}

/* Пока ответа нет, рамка поля ввода дышит фирменным градиентом: тот же
   приём двух фонов, что у пузыря ассистента, тени не появляются. */
.chat-form.busy input[type="text"] {
  border-color: transparent;
  background:
    linear-gradient(var(--surface), var(--surface)) padding-box,
    var(--ai-gradient) border-box;
  animation: ai-breathe 1.6s ease-in-out infinite;
}
@keyframes ai-breathe {
  0%, 100% { filter: saturate(.6) brightness(1); }
  50% { filter: saturate(1.5) brightness(1.03); }}
@media (prefers-reduced-motion: reduce) {
  .bubble-typing span, .chat-form.busy input[type="text"] { animation: none; }}

/* --- Плашки состояния ---------------------------------------------------- */
.panel {
  margin-block: var(--s-4);
  padding: var(--s-3) var(--s-4);
  /* Кабинету отдана полная ширина окна, и без предела панель из трёх коротких
     строк растягивается на все 100rem. Ограничение по мере чтения, как на
     публичных страницах. */
  max-inline-size: min(100%, var(--measure));
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--surface);
}
.panel > :first-child { margin-block-start: 0; }
.panel > :last-child { margin-block-end: 0; }
/* Внутри панели строки идут плотнее обычного текста: это сводка состояния,
   а не проза, и глобальные 16px разносят её на пол-экрана. */
.panel > p { margin-block-end: var(--s-2); }
.panel-title { font-weight: 700; margin-block-end: var(--s-2); }
.panel-ok   { background: var(--success-soft); border-color: transparent; }
.panel-warn { background: var(--warning-soft); border-color: transparent; }

/* Значения состояния. Смысл не передаётся одним цветом: у каждой метки свой
   текст, а оттенок только помогает. */
.pill-ok   { background: var(--success-soft); color: var(--success); }
.pill-warn { background: var(--warning-soft); color: var(--warning); }
.pill-bad  { background: var(--error-soft);   color: var(--error); }
.pill-mute { background: var(--subtle);       color: var(--text-2); }
.pill-soon {
  background: var(--subtle);
  color: var(--text-2);
  border: 1px dashed var(--border-strong);
}

/* --- Тарифы -------------------------------------------------------------- */


.plan-card-current { border-color: var(--accent); border-width: 2px; }


.plan-state-soon { color: var(--text-2); font-weight: 400; font-size: .875rem; }

.ticks { list-style: none; margin: var(--s-3) 0; padding: 0; display: grid; gap: var(--s-2); }
.ticks li {
  display: grid;
  grid-template-columns: 1.25rem 1fr;
  gap: var(--s-2);
  font-size: .9375rem;
}
.ticks li::before {
  content: "";
  inline-size: 1.25rem;
  block-size: 1.25rem;
  background-color: var(--success);
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m5 13 4 4 10-10' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m5 13 4 4 10-10' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
}


/* --- Мелочи -------------------------------------------------------------- */
.crumbs { font-size: .875rem; color: var(--text-2); }
.crumbs a { color: var(--text-2); }

.row-form {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  align-items: end;
  margin-block: var(--s-4);
}
.row-form .field { margin-block-end: 0; flex: 1 1 16rem; min-inline-size: 0; }
@media (min-width: 1024px) {
  /* Короткие промокод и имя токена не должны растягиваться на всю карточку. */
  .row-form .field { flex: 0 1 26rem; max-inline-size: 26rem; }
  /* Поиск — главное поле панели; предложение позиции остаётся вторичным. */
  .toolbar .search { flex: 1 1 28rem; }
  .toolbar .search .field { flex: 1 1 auto; }
  .suggest .row .field { flex: 0 1 32rem; max-inline-size: 32rem; }
}

.card-lead { border-color: var(--accent); }
.sep { margin-block: var(--s-6); border: 0; border-block-start: 1px solid var(--border); }

.plain-list { margin: var(--s-3) 0; padding-inline-start: 1.25rem; display: grid; gap: var(--s-2); }

.code {
  margin-block: var(--s-3);
  padding: var(--s-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
  background: var(--subtle);
  overflow-x: auto;
  /* На телефоне пример команды переносится: прокручивать вбок блок внутри
     страницы, которую тоже надо прокручивать вбок, невозможно. С 768 —
     как было, одной строкой. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font-size: .8125rem;
  line-height: 1.6;
}
@media (min-width: 860px) {
  .code { white-space: pre; overflow-wrap: normal; }}
.code-list {
  padding: var(--s-3) var(--s-4);
  border-radius: var(--radius-control);
  background: var(--subtle);
  font-size: .875rem;
  word-break: break-all;
}
/* В настольном режиме 13-значный код — цельная единица. Разрывать его можно
   только на телефоне, где это аварийная защита от горизонтального скролла. */
@media (min-width: 860px) {
  .code-list { word-break: normal; overflow-wrap: anywhere; }
}

.token-fresh .token-value {
  margin-block-start: var(--s-3);
  padding: var(--s-3) var(--s-4);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-control);
  background: var(--surface);
  font-size: .875rem;
  word-break: break-all;
  user-select: all;                  /* один клик выделяет токен целиком */
}

.row-best { background: var(--success-soft); }
.row-off { opacity: .55; }
.row-actions { white-space: nowrap; }
.row-actions form { display: inline; margin: 0; }

/* Подсветка строки списка обещает действие. На десктопе увеличиваем именно
   ссылку до удобных 42 px, но не накрываем ею строку: дату и счётчик по-прежнему
   можно выделить и скопировать мышью. */
@media (min-width: 860px) {
  .table-job-list .row-open a {
    display: inline-flex;
    align-items: center;
    min-block-size: 42px;
    padding-inline: var(--s-3);
    margin-block: calc(var(--s-3) * -1);
    margin-inline: calc(var(--s-3) * -1);
    border-radius: var(--radius-control);
  }
  .table-job-list .row-open a:hover { background: var(--subtle); }
}

.btn-sm { min-block-size: 32px; padding-inline: var(--s-3); font-size: .875rem; }
.btn-link {
  border: 0;
  background: none;
  padding: 0;
  color: var(--accent);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.btn-link-danger { color: var(--error); }

.auth-card {
  inline-size: min(28rem, 100%);
  margin: var(--s-7) auto;
  padding: var(--s-6);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--surface);
  box-shadow: var(--shadow-1);
}
.auth-card h1 { font-size: 1.625rem; margin-block: var(--s-2) var(--s-3); }
/* Карточка входа сама расставляет ритм: без этого подпись под кнопкой
   прилипает к ней вплотную, потому что сброс снял поля у абзацев. Правило
   общее для страницы входа и для окна: разметка внутри у них одна, и чинить
   их порознь означает чинить дважды. */
.auth-card > form,   .modal-card > form   { margin-block-start: var(--s-5); }
.auth-card > .fine,  .modal-card > .fine  { margin-block-start: var(--s-4); }
.auth-card > .fine + .fine,
.modal-card > .fine + .fine                { margin-block-start: var(--s-3); }
.auth-card > .notice, .modal-card > .notice { margin-block-start: var(--s-4); }
.narrow-page main { max-inline-size: 34rem; }
@media (min-width: 1024px) {
  /* Main уже занимает всё свободное место между шапкой и подвалом. Равные
     поля и нулевые внешние поля карточки дают честное центрирование, но при
     низком окне grid просто растёт вместе с содержимым и ничего не режет. */
  body.narrow-page main {
    display: grid;
    align-content: center;
    padding-block: var(--s-7);
  }
  body.narrow-page .auth-card { margin-block: 0; }
  .narrow.consent-col { margin-inline: 0; }
}
/* Вход — воронка: ни верхней навигации, ни кнопки «Войти» в шапке (она вела
   бы на текущую страницу), подвал сокращён до документов (Р-4, B-11). */
.narrow-page .site-nav,
.narrow-page .header-actions { display: none; }
.narrow-page .footer-top,
.narrow-page .disclaimer,
.narrow-page .footer-slogan,
.narrow-page .footer-nav:not(.footer-docs) { display: none; }

.footer-top {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  flex-wrap: wrap;
  margin-block-end: var(--s-4);
}
.footer-slogan { margin: 0; color: var(--text-2); }
.footer-docs { margin-block-start: var(--s-2); }
.footer-legal {
  grid-column: 1 / -1;
  margin-block-start: var(--s-4);
  font-size: .8125rem;
  color: var(--text-2);
}

.table-compact th, .table-compact td { padding: var(--s-2) var(--s-3); }

/* --- Частые вопросы ------------------------------------------------------ */
/*
  Раскладушка на <details>. Браузер по умолчанию рисует у неё системный
  треугольник и не даёт ни рамки, ни отступов: в оформлении сервиса такой блок
  выглядит вставленным из другой страницы. Здесь он превращается в карточку с
  обводкой и своим знаком раскрытия.

  Механику оставляем родную: <details> открывается без единой строки
  сценария, работает с клавиатуры и читается диктором как надо. Своя
  реализация на кнопках всё это пришлось бы повторять руками.
*/
.faq { display: grid; gap: var(--s-3); }

.faq details {
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--surface);
  transition: border-color var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease);
}
.faq details:hover { border-color: var(--border-strong); }
.faq details[open] {
  border-color: var(--accent);
  box-shadow: var(--shadow-1);
}

.faq summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  min-block-size: var(--row-h-lg);
  padding: var(--s-3) var(--s-5);
  font-weight: 600;
  cursor: pointer;
  /* оба правила нужны: разные браузеры прячут системный знак по-разному */
  list-style: none;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary :where(h2, h3) {
  display: inline;
  margin: 0;
  font-size: inherit;
  font-weight: 600;
}
.faq summary::marker { content: ""; }
.faq summary:hover { color: var(--accent); }
.faq summary:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: -2px;
  border-radius: var(--radius-card);
}

/* Знак раскрытия. Своя стрелка вместо системного треугольника, поворачивается
   при открытии. Цель нажатия — вся строка, а не сам знак. */
.faq summary::after {
  content: "";
  flex: 0 0 auto;
  inline-size: 1.5rem;
  block-size: 1.5rem;
  background-color: var(--accent);
  transition: transform var(--dur) var(--ease);
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m6 9 6 6 6-6' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m6 9 6 6 6-6' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
}
.faq details[open] summary::after { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) {
  .faq summary::after, .faq details { transition: none; }}

.faq details > :not(summary) {
  margin: 0;
  padding: 0 var(--s-5) var(--s-5);
  color: var(--text-2);
  max-inline-size: var(--measure);
}
.faq details > summary + * { padding-block-start: var(--s-1); }

/* Заголовок страницы помощи длинный, и на средней ширине он вплотную
   подходил к правому краю. Своя вилка размера вместо общей. */
.faq-page h1 { font-size: clamp(1.75rem, 1.2rem + 2vw, 2.5rem); }

/* 23. Настольная раскладка ================================================= */
/*
  Замечание владельца 25.08.2026: «десктоп выглядит как расширенный мобильный
  вариант». Так и было — одна колонка, растянутая на широкий экран, с пустой
  правой половиной. Мобильная раскладка остаётся главной, её здесь никто не
  трогает: всё в этом разделе включается от 1024 px и выше.

  Идея простая. На узком экране страница читается сверху вниз одной колонкой.
  На широком у неё появляется вторая, узкая колонка справа: в ней стоит то,
  что на телефоне лежит в самом низу и до чего надо доскроллить, — кнопка
  действия, короткая навигация по странице, полезная сноска. Пустое место
  превращается в опору, а длина страницы падает.
*/

/* Текстовая колонка перестаёт жаться к левому краю. */
.narrow { max-inline-size: var(--col-text); margin-inline: auto; }

/* На настольной ширине центрировать её нельзя: рядом стоят блоки во всю
   ширину контейнера (таблица, снимок, полоса карточек), они не центрируются,
   и страница получает два левых края. Прижимаем колонку к тому же краю, что и
   всё остальное, — тогда одиннадцать публичных маршрутов встают на одну
   вертикаль. Ниже 860 правило не действует: там колонка и так во всю ширину,
   а её отступы приняты мобильной прожаркой. */
@media (min-width: 860px) {
  body.public .narrow { margin-inline: 0; }
}

/* Тот же класс в кабинете. Он проигрывал потолку .app-content:not(.work) по
   специфичности, и страница настроек шла строкой в 126 знаков. Ставим ниже по
   файлу: специфичность равная, побеждает порядок. */
.app-content.narrow { max-inline-size: var(--col-text); margin-inline: 0; }

/* --- Две колонки: содержимое и боковая опора ------------------------------ */
.page-grid { display: grid; gap: var(--s-6); }
.page-main { min-inline-size: 0; }
.page-main > .narrow { margin-inline: 0; }

.page-rail { display: grid; gap: var(--s-4); align-content: start; }

@media (min-width: 1024px) {
  .page-grid {
    grid-template-columns: minmax(0, 1fr) var(--rail-w);
    gap: var(--s-7);
    align-items: start;
  }
  /* В /today и /price текстовая колонка лежит внутри .stack. Потомковый
     селектор даёт ей тот же потолок, что прямому ребёнку на /how. */
  .page-main .narrow { max-inline-size: min(var(--col-text-lg), 100%); }
  .page-rail {
    position: sticky;
    /* Опора и оглавление остаются ниже прилипшей публичной шапки. */
    inset-block-start: calc(var(--header-h) + var(--s-5));
  }}
/* На узком экране опора уезжает вниз: там она читается как обычный блок в
   конце страницы, а не как что-то, отклеившееся сбоку. */
@media (max-width: 1023.98px) {
  .page-rail { order: 2; }}

.rail-card {
  display: grid;
  gap: var(--s-3);
}
.rail-card h2 {
  margin: 0;
  font-size: 1rem;
  font-weight: 700;
}
.rail-card p {
  margin: 0;
  font-size: .9375rem;
  color: var(--text-2);
}
body.public main .rail-card p { max-inline-size: var(--measure); }
.rail-card .btn { inline-size: 100%; }

.surface-accent {
  border-color: transparent;
  background: var(--accent-soft);
}
.rail-card.surface-accent p { color: var(--text); }

/* Навигация по странице. Собирается сценарием из заголовков h2, поэтому
   ничего не надо держать в шаблоне руками и список не устаревает. */


/* --- Нумерованные шаги ---------------------------------------------------- */
/*
  Список <ol class="steps"> раньше терял номера: list-style снят, а своего
  счётчика не было. Порядок действий без номеров читается как набор абзацев,
  и человек теряет, на каком он шаге.
*/
/*
  Номер ставим отдельным слоем, а не колонкой сетки. Внутри <li> обычно лежит
  жирный заголовок и текст после него: для сетки это две разные ячейки, и
  текст уезжал во вторую строку шириной с кружок, рассыпаясь по букве в
  строке. Со слоем содержимое остаётся обычным потоком и переносится как
  положено, каким бы оно ни было.
*/
.steps > li {
  counter-increment: step;
  position: relative;
  padding-inline-start: 3.5rem;
  min-block-size: 2.25rem;
}
.steps > li::before {
  content: counter(step);
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: -1px;
  inline-size: 2.25rem;
  block-size: 2.25rem;
  display: grid;
  place-items: center;
  border-radius: var(--radius-pill);
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.steps > li > p:first-child { margin-block-start: 0; }

/* --- Выбор из нескольких вариантов --------------------------------------- */
/*
  Раньше здесь не было ничего: переключатели и подписи разъезжались, а длинное
  слово рвалось посередине. Каждый вариант теперь строка-карточка во всю
  ширину, подпись стоит рядом с кружком, а не под ним.
*/


.radio {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--s-2);
  align-items: center;
  min-block-size: var(--control-h-lg);
  padding-inline: var(--s-3) var(--s-4);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-control);
  background: var(--surface);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease);
}
.radio:hover { border-color: var(--accent); }
.radio > span { font-size: .9375rem; }
.radio:has(input:checked) {
  border-color: var(--accent);
  border-width: 2px;
  background: var(--accent-soft);
  padding-inline: calc(var(--s-3) - 1px) calc(var(--s-4) - 1px);
}
.radio:has(input:focus-visible) { outline: 2px solid var(--focus); outline-offset: 2px; }

/* --- Рабочие экраны кабинета --------------------------------------------- */
/*
  Здесь идёт настоящая работа, и на широком экране колонка по центру только
  мешает: таблица цен упирается в свои же поля, а кнопка запуска уезжает под
  сгиб. Ширину отдаём содержимому, действия прижимаем к низу окна.
*/
@media (min-width: 1024px) {
  /* Рабочая область во всю ширину: таблицы, каталог, результаты. */
  .app-content.work { max-inline-size: none; }

  /* Каталог: список слева, сводка выбора справа и всегда на виду. */
  .work-split {
    display: grid;
    grid-template-columns: minmax(0, 1fr) var(--rail-w);
    gap: var(--s-6);
    align-items: start;
  }
  .work-aside {
    position: sticky;
    inset-block-start: var(--rail-top);
    display: grid;
    gap: var(--s-4);
  }}

/* Каталог использует ту же двухколоночную основу, но на нижней настольной
   ступени отдаёт опоре меньше места: шесть колонок таблицы должны остаться
   рабочими уже на 1024 px. */
@media (min-width: 1024px) {
  .catalog-work .work-split {
    grid-template-columns: minmax(0, 1fr) clamp(16.5rem, 22vw, var(--rail-w));
    gap: var(--s-4);
  }
  .catalog-selection {
    position: static;
    inset: auto;
    min-inline-size: 0;
    display: grid;
    align-items: stretch;
    justify-content: stretch;
    gap: var(--s-4);
    margin: 0;
    padding: var(--s-5);
    box-shadow: var(--shadow-1);
  }
  .catalog-selection > p { margin: 0; }
  .catalog-selection .btn { inline-size: 100%; }
}
.catalog-selection-list {
  min-inline-size: 0;
  display: grid;
  gap: var(--s-3);
}
.catalog-selection-list h2 { margin: 0; font-size: 1rem; }
.catalog-selection-list > p { margin: 0; }
.catalog-selection-list ol {
  min-inline-size: 0;
  display: grid;
  gap: var(--s-2);
  margin: 0;
  padding: 0;
  list-style: none;
}
.catalog-selection-list li {
  min-inline-size: 0;
  padding-block-start: var(--s-2);
  border-block-start: 1px solid var(--border);
  font-size: .875rem;
  line-height: 1.35;
}
.catalog-selection-list li:first-child { padding-block-start: 0; border-block-start: 0; }
.catalog-selected-name {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.catalog-selection-list small {
  display: block;
  margin-block-start: 2px;
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
}
@media (max-width: 1023.98px) {
  /* Коробка aside исчезает только из раскладки: одна и та же панель остаётся
     последним блоком формы и липнет к низу, как до появления правой опоры. */
  .catalog-aside { display: contents; }
  .catalog-selection-list { display: none; }
}

/* Панель действий внизу рабочего экрана. Общая геометрия объявлена рядом со
   .sticky-actions. На телефоне панель прилипает к низу окна, на компьютере
   остаётся в потоке: там она видна и так, а липкая полоса съедала бы строку. */
@media (max-width: 1023.98px) {
  .work-actions {
    position: sticky;
    /* Раньше на 960–1023 висело на 72 px выше низа, хотя панели там нет. */
    inset-block-end: calc(var(--s-4) + var(--nav-h));
    z-index: 20;
    box-shadow: var(--shadow-2);
  }}

/* Поле для вставки штрихкодов. Моноширинный набор и табличные цифры: столбец
   кодов должен читаться столбцом, а не рваным текстом. */
.codes-input {
  min-block-size: 14rem;
  font-variant-numeric: tabular-nums;
  line-height: 1.7;
  resize: vertical;
}

/* Сводка разбора: число слева, подпись справа. */
.tally { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-2); }
.tally li {
  display: grid;
  grid-template-columns: 3rem minmax(0, 1fr);
  gap: var(--s-3);
  align-items: baseline;
  font-size: .9375rem;
  color: var(--text-2);
}
.tally-num {
  font-size: 1.375rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  text-align: end;
}
.tally-ok { color: var(--success); }

/* --- Шапка рабочей страницы ---------------------------------------------- */
.page-head {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3) var(--s-5);
  align-items: end;
  justify-content: space-between;
  margin-block-end: var(--s-4);
}
.page-head h1 { margin: 0; }

/* Состояние заявки одной строкой: метка, счётчики. */
.statline {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3) var(--s-5);
  align-items: center;
  font-size: .9375rem;
  color: var(--text-2);
}
.statline strong { color: var(--text); font-variant-numeric: tabular-nums; }

/* Полоса фильтров. Подпись слева от поля, всё в одну строку; на узком экране
   переносится само. Кнопка выгрузки живёт в шапке страницы, а не здесь: там
   она видна сразу, без прокрутки до таблицы. */
.filterbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-start;
  gap: var(--s-3) var(--s-4);
  margin-block: var(--s-4);
  padding: var(--s-3) var(--s-4);
  /* Липнет к верху под .app-bar, а не к низу: это фильтр, не панель действий. */
  position: sticky;
  inset-block-start: calc(var(--app-bar-h) - 4px);
  z-index: 15;
}
.filterbar-export { margin-inline-start: auto; }
@media (max-width: 599.98px) {
  /* На узком экране поля во всю ширину: селекты 189 px в три строки не читались. */
  .filterbar .field-inline { flex: 1 1 100%; flex-direction: column; align-items: stretch; gap: 2px; }
  .filterbar select { inline-size: 100%; }
  .filterbar .btn { flex: 1 1 100%; }
  .filterbar-export { margin-inline-start: 0; }
}
.filterbar .field-inline {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin: 0;
  flex: 0 1 auto;
}
.filterbar .field-label { margin: 0; font-size: .875rem; }
@media (min-width: 860px) {
  .filterbar .field-label { white-space: nowrap; }}
.filterbar select { min-inline-size: 10rem; }

/* Таблица результата длинная: даём ей больше высоты, чем таблице по умолчанию,
   чтобы липкая шапка окупалась. */
.table-tall { max-block-size: var(--table-h-lg); }


/* Выпадающий список: своя стрелка вместо системной. */
.field select,
.filterbar select {
  appearance: none;
  -webkit-appearance: none;
  padding-inline-end: 2.25rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m6 9 6 6 6-6' fill='none' stroke='%230B6E7F' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--s-3) center;
  background-size: 1.15rem;
  cursor: pointer;
}

/* D-16: поле подтверждения удаления и плашка-ошибка в кабинете — во всю
   ширину. Плашка была fit-content, и поле ≈190 px из 300 выглядело обрезанным. */
.danger .field, .danger .input, .danger input { inline-size: 100%; }
.app-content .notice-error { inline-size: auto; }

/* --- Свой выбор файла ----------------------------------------------------- */
/*
  Родное поле спрятано, но оставлено в разметке: клик по <label> открывает
  системный выбор, клавиатура и экранный диктор работают как обычно. Кнопка и
  подпись рисуются нами, поэтому системного серого здесь нет.
*/
.filepick {
  display: inline-flex;
  /* На 320 кнопка и подпись не влезают в одну строку: без переноса
     min-content поля выталкивает всю панель за край экрана. */
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3);
  inline-size: 100%;
  min-inline-size: 0;
  min-block-size: var(--control-h);
  padding: var(--s-1) var(--s-3) var(--s-1) var(--s-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
  background: var(--subtle);
  cursor: pointer;
}
.filepick:hover { border-color: var(--border-strong); }
.filepick:has(input:focus-visible) {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}
.filepick-btn {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  min-block-size: 36px;
  padding-inline: var(--s-4);
  border-radius: var(--radius-pill);
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--accent);
  font-size: .875rem;
  font-weight: 400;
  white-space: nowrap;
}
.filepick:hover .filepick-btn { background: var(--accent); color: var(--accent-ink); }
.filepick-name {
  min-inline-size: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: .875rem;
  color: var(--text-2);
}
.filepick:has(input:valid) .filepick-name { color: var(--text); font-weight: 600; }
.filepick-field { display: grid; gap: var(--s-2); }

/* Состояния заявки: метка и пояснение. Таблицей это верстать нельзя — первая
   колонка растягивается по самой длинной метке, и между ней и текстом
   возникает провал в половину строки. */
.states {
  display: grid;
  gap: var(--s-3) var(--s-4);
  margin: var(--s-4) 0;
}
.states dt { margin: 0; }
.states dd { margin: 0; color: var(--text-2); }
@media (min-width: 480px) {
  .states {
    grid-template-columns: max-content minmax(0, 1fr);
    align-items: baseline;
    row-gap: var(--s-4);
  }}

/* --- Сравнение тарифов ---------------------------------------------------- */
.plans-table th[scope="row"] { font-weight: 600; white-space: normal; }
.plans-table .plan-col { vertical-align: bottom; }
.plan-name { display: block; font-size: 1.0625rem; }
.plan-cost {
  display: block;
  margin-block-start: var(--s-1);
  font-size: 1.25rem;
  font-weight: 700;
  letter-spacing: -.02em;
  white-space: nowrap;
}
.plan-cost small {
  display: block;
  font-size: .75rem;
  font-weight: 400;
  color: var(--text-2);
}
.plan-col-current { background: var(--accent-soft); }
.plans-table .yes { color: var(--success); font-weight: 600; }
.plans-table .no { color: var(--text-2); }
.plans-state td { padding-block: var(--s-4); }
/* До 1024 сравнение остаётся серией карточек: на этой полосе четыре колонки
   ещё не дают устойчивого чтения. От 1024 цену несёт шапка таблицы. */
@media (min-width: 1024px) { .plans-price { display: none; }}
.plans-price td { font-weight: 700; }
.plans-price small { color: var(--text-2); font-weight: 400; }
@media screen and (min-width: 860px) and (max-width: 1023.98px) {
  .table-scroll:has(.plans-table) {
    max-block-size: none;
    overflow: visible;
    border: 0;
    background: none;
  }
  .plans-table { display: block; border: 0; }
  .plans-table thead {
    position: absolute;
    inset-block-start: 0;
    inset-inline-start: 0;
    inline-size: 1px;
    block-size: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .plans-table,
  .plans-table tbody,
  .plans-table tr { display: block; inline-size: 100%; }
  .plans-table tr {
    margin-block-end: var(--s-3);
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    background: var(--surface);
    overflow: hidden;
  }
  .plans-table tr:hover { background: var(--surface); }
  .plans-table tbody th[scope="row"] {
    display: block;
    inline-size: 100%;
    padding: var(--s-3) var(--s-4) var(--s-2);
    border: 0;
    font-size: .875rem;
    line-height: 1.4;
    color: var(--text);
  }
  .plans-table td {
    display: grid;
    inline-size: 100%;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s-1) var(--s-3);
    padding: var(--s-2) var(--s-4);
    border-block-end: 1px solid var(--border);
    text-align: start;
  }
  .plans-table td:last-child { border-block-end: 0; }
  .plans-table td[data-label]::before {
    content: attr(data-label);
    margin-block-end: 2px;
    font-size: .8125rem;
    font-weight: 600;
    color: var(--text-2);
  }
  .plans-table td > .pill { justify-self: start; }
  .plans-table caption { padding-inline: 0; }
  .plans-table .plans-state td { padding-block: var(--s-4); }
}
@media screen and (max-width: 859.98px) {
  /* A-08: подпись строки во всю ширину карточки, а не по ширине текста —
     иначе линия под ней короткая и композиция рваная. */
  .plans-table td[data-label] { grid-template-columns: minmax(0, 1fr); }
  .plans-table td[data-label]::before { margin-block-end: 2px; }
}

/* Список того, что готовится. Галочек намеренно нет: галочка означает «это
   уже есть», а зачёркнутый пункт в списке возможностей выглядит извинением. */
.soon-list {
  list-style: none;
  margin: var(--s-3) 0;
  padding: 0;
  display: grid;
  gap: var(--s-3);
}
.soon-list li {
  position: relative;
  padding-inline-start: var(--s-5);
  color: var(--text-2);
}
.soon-list li::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: .55em;
  inline-size: 8px;
  block-size: 8px;
  border-radius: var(--radius-pill);
  border: 2px solid var(--accent);
}
.soon-list strong { color: var(--text); }


/* --- Полосы главной (редизайн 2026-08) ----------------------------------- */
/* Секция на всю ширину экрана изнутри .wrap: вылет отрицательными полями,
   контент возвращается тем же padding. Глубина — сменой поверхности. */
.band {
  margin-inline: calc(50% - 50vw);
  padding-inline: calc(50vw - 50%);
  padding-block: var(--s-8);
  margin-block-start: var(--s-7);
}
.band + .band { margin-block-start: 0; }
.band > * { max-inline-size: var(--wrap); }
.band h2 { margin-block-end: var(--s-5); }
.band-subtle { background: var(--subtle); }
.band-dark {
  background: var(--band-bright);
  color: #FFFFFF;
}
.band-dark h2 { color: #FFFFFF; }
.band-note { color: rgb(255 255 255 / .86); max-inline-size: 40rem; }
.band:not(.band-dark) .band-note { color: var(--text-2); }
.band-center { text-align: center; }
.band-center > * { margin-inline: auto; }
.row-center { justify-content: center; }

/* Кнопки на тёмной полосе */
.btn-on-dark { background: #FFFFFF; border-color: #FFFFFF; color: var(--text); }
.btn-on-dark:hover { background: #E8E8ED; border-color: #E8E8ED; color: var(--text); }
.btn-dark-ghost {
  background: transparent;
  border-color: rgb(255 255 255 / .35);
  color: #FFFFFF;
}
.btn-dark-ghost:hover { background: rgb(255 255 255 / .12); color: #FFFFFF; }
.band-dark a:not(.btn) { color: #FFFFFF; text-decoration: underline; }

/* Три шага главной (не путать с ol.steps на /how) */
.home-steps {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: var(--s-5);
  margin-block-end: var(--s-6);
}
.home-step h3 { margin-block: var(--s-2) var(--s-2); }
.home-step p { color: var(--text-2); margin: 0; }
.home-step-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 32px;
  block-size: 32px;
  border-radius: 50%;
  background: var(--text);
  color: #FFFFFF;
  font-weight: 600;
  font-size: .9375rem;
}

/* Карточки границ/малоданности */
.limit-cards { margin-block-end: var(--s-4); }
.card-subtle { background: var(--subtle); border-color: transparent; }
.lead-narrow { max-inline-size: 40rem; color: var(--text-2); font-size: 1.0625rem; }

/* --- Лента главной: свечение, функции, цветные подложки (13.09.2026) ---- */
/* Hero — светящаяся плашка. Цвет несёт фон, текст остаётся на светлом. */
.hero-glow {
  max-inline-size: none;
  margin-block-start: var(--s-5);
  padding: var(--s-7) clamp(20px, 5vw, 64px);
  border-radius: 32px;
  background: var(--hero-glow), var(--surface);
  box-shadow: var(--shadow-1);
}
.feature-grid {
  display: grid;
  gap: var(--s-4);
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
}
.feature {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  padding: var(--s-5);
  border-radius: var(--radius-card);
}
.feature h3 { margin: var(--s-2) 0 0; font-size: 1.125rem; }
.feature p { margin: 0; color: var(--text-2); }
.feature-icon {
  display: inline-grid;
  place-items: center;
  inline-size: 44px;
  block-size: 44px;
  border-radius: 14px;
  background: var(--surface);
}
.feature-icon .icon { inline-size: 24px; block-size: 24px; }
/* Подложки перебивают фон карточки своей специфичностью, а не порядком:
   слой стекла ниже по файлу красит .card заново. */
.tint-blue,   .card.tint-blue   { background: var(--tint-blue);   border-color: transparent; }
.tint-violet, .card.tint-violet { background: var(--tint-violet); border-color: transparent; }
.tint-teal,   .card.tint-teal   { background: var(--tint-teal);   border-color: transparent; }
.tint-amber,  .card.tint-amber  { background: var(--tint-amber);  border-color: transparent; }
.tint-blue   .feature-icon { color: var(--tint-blue-ink); }
.tint-violet .feature-icon { color: var(--tint-violet-ink); }
.tint-teal   .feature-icon { color: var(--tint-teal-ink); }
.tint-amber  .feature-icon { color: var(--tint-amber-ink); }
.band-ai { background: linear-gradient(118deg, var(--tint-blue), var(--tint-violet)); }

/* Проход света по контуру главного CTA ===================================== */
/* Кольцо рисуется в том же ::after, что и блик .btn-primary (строка ~1923),
   и по специфичности замещает его. transform и blend-mode блика обнуляем
   явно, иначе кольцо уедет вместе с ним. Фокус-кольцо у .btn уже задано
   (строка ~174), своё не дублируем. */
.btn.contour { position: relative; isolation: isolate; overflow: visible; }
.btn.contour::after {
  content: "";
  position: absolute;
  inset: -2px;
  padding: 2px;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  mix-blend-mode: normal;
  transform: none;
  transition: none;
  background: linear-gradient(110deg, var(--accent) 10%, #D4EAFF 45%, #FFFFFF 50%, var(--accent) 65%);
  background-size: 250% 100%;
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  mask-composite: exclude;
}
@media (hover: hover) {
  .btn.contour:hover::after { animation: parso-contour-pass 650ms ease-out 1; }}
.btn.contour:focus-visible::after { animation: parso-contour-pass 650ms ease-out 1; }
.btn.contour:is(:active, :disabled, [aria-disabled="true"], [aria-busy="true"])::after {
  animation: none;
  opacity: 0;
}
@keyframes parso-contour-pass {
  0% { opacity: 0; background-position: 100% 0; }
  25% { opacity: 1; }
  100% { opacity: 0; background-position: 0 0; }}
@media (prefers-reduced-motion: reduce) {
  .btn.contour::after { animation: none !important; opacity: 0 !important; }}
@media (forced-colors: active) {
  .btn.contour::after { content: none; }}


/* --------------------------------------------------------------------------
   Витрина «Цены аптек за сегодня»: крупное число в карточке показателя.
   Инлайновый style здесь не годится: политика ресурсов разрешает стили
   только из своего файла. */
.stat-num { font-size: 1.75rem; font-weight: 700; margin: 0; }

.stat-card { display: flex; flex-direction: column; gap: var(--s-2); padding: var(--s-4); }
.stat-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-2);
}
.stat-head h2 { margin: 0; font-size: .9375rem; color: var(--text-2); font-weight: 600; }
.stat-card .stat { margin: 0; font-size: 1.625rem; font-weight: 700; }
.stat-card .fine { margin: 0; }
.stat-card > .btn { margin-block-start: auto; align-self: start; }

/* --- Parso Glass: материалы и визуальная иерархия -------------------------
   Этот раздел меняет поверхности существующих компонентов, не их сетки.
   mobile остаётся последним слоем. Высоты липких панелей и компенсации
   safe-area сохраняются; строки таблиц и поля не получают blur. */

body {
  background-color: var(--canvas);
  background-image: linear-gradient(128deg, #E4EEFF 0, #F6F9FF 34rem, var(--canvas) 64rem);
  background-repeat: no-repeat;
}
h1, h2 { letter-spacing: -.035em; }
h3 { letter-spacing: -.02em; }

.site-header,
.app-bar {
  isolation: isolate;
  background: transparent;
  border-block-end-color: transparent;
  box-shadow: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
/* Плавающий контур нарисован внутри принятой высоты шапки. Он не добавляет
   отступов к документу и не сдвигает привязанные к шапке липкие элементы. */
.site-header::before,
.app-bar::before {
  content: "";
  position: absolute;
  inset: 4px 8px;
  z-index: -1;
  pointer-events: none;
  border: 1px solid var(--border);
  border-radius: 24px;
  background: var(--glass-fill);
  box-shadow: var(--glass-shadow);
}
.site-nav a,
.app-tabs a,
.icon-link { border-radius: var(--radius-pill); }
.site-nav a[aria-current="page"],
.app-tabs a[aria-current="page"],
.icon-link[aria-current="page"] {
  background: var(--accent-soft);
  box-shadow: var(--glass-light);
}
.brand-mark {
  background: linear-gradient(150deg, #347DE4, #075DCC 58%, #05499D);
  box-shadow: inset 0 1px 1px rgb(255 255 255 / .6), 0 3px 8px rgb(7 93 204 / .18);
}
.brand-name { letter-spacing: -.045em; }
.site-footer { background: #EDF1F7; }
.app-footer { background: transparent; }

.hero-kicker {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  margin-block: 0 var(--s-5);
  padding: 6px 12px;
  border: 1px solid #CADAF1;
  border-radius: var(--radius-pill);
  background: #F7FAFF;
  box-shadow: var(--glass-light);
  color: #31577F;
  font-size: .8125rem;
  font-weight: 600;
  letter-spacing: .01em;
}
.hero-kicker::before {
  content: "";
  inline-size: 6px;
  block-size: 6px;
  border-radius: 50%;
  background: var(--accent);
}
.hero-text h1 { letter-spacing: -.05em; line-height: 1.08; }
.hero-subtitle { display: block; color: #526B8A; }
.hero-text .lead { color: #43536B; }
.page-intro { color: var(--text-2); max-inline-size: var(--measure); }
.app-content > .page-intro { margin-block-end: var(--s-5); }

.table caption { background: var(--surface); }
.table thead th { background: #EAF0F7; color: #41536C; }
.table-scroll:focus-visible { outline-offset: 3px; }

.card,
.step { border-color: var(--border); }
.card-subtle { background: #F6F8FC; }
.surface-accent {
  border-color: #CBDBF1;
  background: linear-gradient(140deg, #F8FBFF, #E7F0FF);
}
.empty { background: #F8FAFD; }
.cards .card .stat { letter-spacing: -.04em; }

.btn { font-weight: 600; }
.btn-primary {
  background: linear-gradient(180deg, #1268D9, var(--accent));
  border-color: rgb(3 61 138 / .22);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .26), 0 4px 12px rgb(7 93 204 / .15);
}
.btn-primary:hover { background: var(--accent-hover); }
.btn-ghost {
  background: linear-gradient(170deg, #FFFFFF, #F2F6FC);
  border-color: #C4D0E0;
  box-shadow: var(--glass-light), 0 2px 4px rgb(29 52 86 / .025);
}
.btn-primary:is(:disabled, [aria-disabled="true"]),
.btn-ghost:is(:disabled, [aria-disabled="true"]) { box-shadow: none; }
.btn-primary:is(:disabled, [aria-disabled="true"]) {
  background: var(--subtle);
  border-color: var(--border-strong);
  color: var(--text-2);
}
.btn:focus-visible { box-shadow: 0 0 0 3px var(--surface); }

.toolbar,
.sticky-actions,
.work-actions,
.filterbar,
.mobile-nav,
.sheet {
  background: var(--glass-fill);
  box-shadow: var(--glass-shadow);
}
.mobile-nav a,
.mobile-nav button { border-radius: 16px 16px 0 0; }
.mobile-nav a[aria-current="page"],
.mobile-nav button[aria-current="page"] {
  box-shadow: inset 0 3px 0 var(--accent), var(--glass-light);
}
.sheet-links a { border-radius: 14px; }
.sheet { border-start-start-radius: 28px; border-start-end-radius: 28px; }
.sheet::backdrop { background: rgb(18 36 62 / .38); }

.band:not(.band-dark):not(.band-subtle):not(.band-ai) { background: #FFFFFF; }
.band-subtle { background: #EDF3FF; }
.band h2 { letter-spacing: -.04em; }
.band-dark {
  background: var(--band-bright);
  color: #FFFFFF;
}
.band-dark .band-note { color: rgb(255 255 255 / .86); }
/* Номер шага — цветная точка ленты: градиент бренда, белая цифра. */
.home-step-num {
  background: linear-gradient(135deg, var(--accent), var(--ai-b));
  color: #FFFFFF;
  box-shadow: 0 6px 16px -6px rgb(0 98 245 / .55);
}
.btn-on-dark { box-shadow: inset 0 1px 0 #FFFFFF, 0 4px 16px rgb(0 0 0 / .12); }
.btn-dark-ghost { background: rgb(255 255 255 / .07); }

.ai-screen .chat {
  border-color: var(--border);
  background: var(--surface);
  box-shadow: var(--shadow-1);
  animation: none;
}
.chat-form { background: #F1F5FB; box-shadow: var(--glass-light); }
.msg-user .bubble { background: var(--accent-soft); color: var(--text); }
.msg-bot .bubble {
  border-color: #CADAF1;
  border-inline-start-color: var(--accent);
  background: var(--surface);
}

/* На телефоне сохраняем лёгкий непрозрачный материал: предыдущий аудит
   уже выявил потерю плавности от backdrop-filter при длинном скролле.
   На десктопе blur получают только панели управления, без стекла на стекле. */
@media (min-width: 860px) and (hover: hover) {
  @supports ((backdrop-filter: blur(20px)) or (-webkit-backdrop-filter: blur(20px))) {
    .site-header::before,
    .app-bar::before,
    .sticky-actions,
    .work-actions,
    .filterbar {
      background: rgb(247 250 255 / .90);
      -webkit-backdrop-filter: saturate(140%) blur(20px);
      backdrop-filter: saturate(140%) blur(20px);
    }
    /* В каталоге панель вложена в неподвижную опору; дополнительный blur
       внутри неё ничего не даёт и увеличивает число композитных слоёв. */
    .catalog-selection { -webkit-backdrop-filter: none; backdrop-filter: none; }
  }
}
@media (max-width: 859.98px) {
  .site-header::before,
  .app-bar::before { -webkit-backdrop-filter: none; backdrop-filter: none; }
}
@media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
  .site-header::before, .app-bar::before, .toolbar, .sticky-actions, .work-actions,
  .filterbar, .mobile-nav, .sheet {
    background: var(--surface);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}
@media (forced-colors: active) {
  body, .card, .band, .band-dark, .band-ai, .hero-glow, .feature, .btn, .surface-accent,
  .site-header::before, .app-bar::before, .toolbar, .sticky-actions, .work-actions,
  .filterbar, .mobile-nav, .sheet, .chat-form, .bubble {
    background: Canvas;
    color: CanvasText;
    box-shadow: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  .hero-subtitle, .band-dark h2, .band-note { color: CanvasText; }
  .btn { border-color: ButtonText; }
  .btn-primary:not(:disabled):not([aria-disabled="true"]) { background: Highlight; color: HighlightText; }
  .btn-ghost { color: LinkText; }
}


/* --- Печать ---------------------------------------------------------------
   Стоит последней в слое осознанно: правила высоты боксов и полосы фильтров
   объявлены ниже по файлу, и при равной специфичности побеждает тот, кто
   позже. Печатная ветка обязана быть позже всех. */
@media print {
  /* Плашка кабинета, его подвал и липкая панель запуска на бумаге не нужны. */
  .site-header, .site-footer, .mobile-nav, .skip-link, .sheet,
  .app-bar, .app-footer, .filterbar, .sticky-actions { display: none; }
  body { background: #FFFFFF; color: #000000; padding-block-end: 0; }
  .btn-primary, .card, .badge {
    background: none;
    border-color: #999;
    color: #000;
  }
  .stat, .brand {
    color: #000;
    background: none;
    -webkit-text-fill-color: currentColor;
  }
  .card, .step, .table-scroll { box-shadow: none; border-color: #B0B7C0; }
  /* Потолок высоты снимаем обоим боксам: у .table-tall своё объявление, и без
     парной строки на бумагу уходило восемь строк из шестидесяти. */
  .table-scroll, .table-tall { max-block-size: none; overflow: visible; }
  tr, .card, .step { break-inside: avoid; }
  h1, h2, h3 { break-after: avoid; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: .8em; }}


/* --- Пульс рынка -------------------------------------------------------- */
/* Переключатель городов. Без сценариев обе панели видны, и это исправное
   состояние: переключатель убирает лишнее, а не создаёт содержимое. */
.tabs { display: flex; gap: var(--s-2); flex-wrap: wrap;
        margin-block: var(--s-4) var(--s-3); }
.tab { padding: var(--s-2) var(--s-4); border: 1px solid var(--line);
       border-radius: 999px; background: var(--surface); color: var(--ink);
       font: inherit; cursor: pointer; }
.tab:hover { border-color: var(--accent); }
.tab-on { background: var(--accent); border-color: var(--accent);
          color: var(--on-accent, #fff); }
.tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Состав аптек: панель внутри строки таблицы. Раскрытая не должна раздвигать
   колонки, поэтому содержимое живёт в своём потоке со своей прокруткой. */
.pulse-detail summary { cursor: pointer; white-space: nowrap;
                        color: var(--accent); }
.pulse-detail[open] summary { margin-block-end: var(--s-2); }
.pulse-detail-body { max-inline-size: min(60rem, 90vw); overflow-x: auto; }
.pulse-note { margin-block-start: var(--s-3); color: var(--ink-2); }
.pulse-table td.price { font-variant-numeric: tabular-nums; }
/* Название препарата длинное, а колонок шесть. Без нижней границы ширины
   браузер сжимает первую колонку до предела и рвёт слова посередине:
   «ТЕМПАЛ ГИН», «РИНОФ ЛУИМУ ЦИЛ». Читать это нельзя, поэтому колонке дана
   ширина, а таблице — своя прокрутка (она уже в .table-scroll). */
.pulse-table th:first-child, .pulse-table td:first-child {
  min-inline-size: 14rem; overflow-wrap: normal; word-break: keep-all; }
.pulse-table th:not(:first-child), .pulse-table td:not(:first-child) {
  white-space: nowrap; }
/* Таблица вынесена из .narrow: мера строки нужна тексту, не колонкам. */
.pulse-section { margin-block: var(--s-4); }
} /* @layer app */

@layer mobile {


/* Цели нажатия на телефоне: единая нижняя граница 44 px для мелких
   интерактивных элементов (C-28, A-13). На десктопе плотность выше — там
   мышь точнее. */
@media (max-width: 859.98px) {
  .btn-sm,
  .chip-q,
  .app-tabs a,
  .filepick-btn,
  .icon-link,
  .ops-tab,
  .crumbs a,
  .row-actions a,
  .app-bar .brand-sm,
  .plan-chip { min-block-size: 44px; }
  .icon-link { inline-size: 44px; block-size: 44px; }
  /* Логотип-мокрыл в кабинете: на ≤559 видна только «метка», добираем ширину. */
  .app-bar .brand-sm { min-inline-size: 44px; }
  .btn-sm, .chip-q, .ops-tab, .crumbs a {
    display: inline-flex; align-items: center;
  }
}

} /* @layer mobile */
