/* Кто молодец — стили кабинета/авторизации.
   Токены (цвет/типографика/геометрия/spacing, обе темы) вынесены в общий
   пакет web_ds (design-system-foundation, sub-unit 1) — единственный
   источник правды теперь packages/calls_analyzer_web_ds/css/tokens.css,
   подключается ниже через @import. Не дублировать корневой токен-блок
   здесь — тест-страж web/test/styles_guard_test.dart это проверяет.
   Шрифты (Inter + JetBrains Mono) теперь тоже само-хостятся из web_ds
   (design-system-foundation, sub-unit 2, задача 2.4) — woff2 лежат в
   web_ds/lib/fonts/, @font-face — в packages/calls_analyzer_web_ds/css/
   fonts.css, подключается ниже через @import. Google Fonts CDN здесь
   больше НЕ используется — тот же источник шрифтов, что и у лендинга
   (web_static), guard-тест это проверяет.
   Базовые DS-компонентные классы (.btn/.card/.chip/.notice(+.err/.ok/
   .warn/.ok a)/.empty-state) тоже переехали в общий пакет — единственный
   источник теперь packages/calls_analyzer_web_ds/css/components.css
   (design-system-foundation, sub-unit 3), подключается ниже через
   @import. Их определения здесь были удалены (не дублировать — то же
   правило, что и для tokens.css/fonts.css). Кабинетная шапка (`.km-header`
   → `.app-header`, `.km-logo-*`/`.user-bar`/`.km-nav*`/`.tariff-badge`/
   `.user-pill`) туда же переехала — design-system-foundation, sub-unit 4/5,
   §5.1 AppHeader — единственный источник теперь тот же components.css.
   ⚠️ Устарело (redesign-conformance, этап 5): `cabinetHeader()`/
   `components/header.dart` больше НЕ СУЩЕСТВУЮТ — этап 5 заменил кабинетную
   шапку колонкой-сайдбаром (`web_ds`'s `sideNav()`, `side_nav.dart`); тот
   абзац выше описывал историческую миграцию (sub-unit 4/5) ДО этого этапа,
   когда `cabinetHeader()` ещё звал `appHeader()`. Классы `.app-header`/
   `.km-nav*`/`.tariff-badge`/`.user-pill` в components.css остаются
   определены и живы — их всё ещё зовёт `appHeader()` для авторизации
   (`auth/auth_page.dart`) и лендинга (`web_static/lib/landing/
   header_island.dart`), просто больше не кабинет. Всё, что остаётся ЗДЕСЬ —
   либо ещё не перенесённые примитивы (.stat-tile/.inline-nudge), либо
   контекстные надстройки поверх перенесённых классов (`.upload-card .btn`,
   `.acc-header .btn`, …) через более специфичный селектор. Надстройка,
   меняющая не только раскладку снаружи (ширина, отступы, flex/grid), —
   долг: правка компонента в web_ds до неё не доедет. Такие места перечислены
   поимённо со счётчиком в `test/ds_markup_guard_test.dart`
   (`allowedDsOverride`), новых страж не пропустит. */
@import url('packages/calls_analyzer_web_ds/css/tokens.css');
@import url('packages/calls_analyzer_web_ds/css/fonts.css');
@import url('packages/calls_analyzer_web_ds/css/components.css');

/* ==== base ==== */
* { box-sizing: border-box; }
body { margin: 0; font: var(--fz-body)/1.5 var(--font-ui); color: var(--text); background: var(--bg); }
.wrap { max-width: 1120px; margin: 0 auto; padding: var(--sp-8) var(--sp-6); }

/* ==== StatTile ==== */
.stat-tile { display: flex; flex-direction: column; gap: var(--sp-1); }
.stat-tile .num { font: var(--w-semibold) var(--fz-num-lg) var(--font-num); color: var(--accent); }
.stat-tile .label { font-size: var(--fz-sm); color: var(--muted); }

/* ==== InlineNudge ====
   (.notice/.empty-state moved to the shared web_ds/lib/css/components.css,
   sub-unit 3 — see this file's header doc. .inline-nudge stays here, not
   yet one of the §3 primitives.) */
/* Ссылка на раздел «Тариф» (cabinet-first-run): вид прежней серой строки,
   подчёркивание и цвет текста — только на наведении, кольцо — по клавиатуре. */
.inline-nudge { font-size: var(--fz-sm); color: var(--muted); display: inline-flex; align-items: center; gap: var(--sp-2);
  text-decoration: none; }
.inline-nudge:hover { color: var(--text); text-decoration: underline; }
.inline-nudge:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); }
.inline-nudge .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }

/* ==== rows (FileRow/legacyCallRow/legacySummaryRow) ====
   .call-row/.summary-row renamed .legacy-call-row/.legacy-summary-row
   (redesign-conformance, add-design-components-batch2): those bare names
   collided with web_ds's new CallRow/SummaryRow primitives — see
   lib/components/rows.dart's doc. */
.file-row, .legacy-call-row, .legacy-summary-row { display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) 0; border-bottom: var(--bw-hair) solid var(--border); }
.file-row .err { color: var(--danger); font-size: var(--fz-sm); }
.col-num { font-family: var(--font-num); }

/* ==== Auth (login/register/forgot/reset — Ф4, DS card на auth-redesign) ====
   Tabs/error-banner used to be page-local markup (.auth-tab/.auth-tabs/
   .auth-error-banner) — auth_page.dart now renders those through the
   shared web_ds primitives (SegmentedControl/Notice), which carry their
   own `.segmented`/`.notice` styling from packages/calls_analyzer_web_ds/
   css/components.css. What's left here is layout the DS primitives don't
   own an opinion on (design.md Р-3: pure render, no margins of their own)
   — same "context-scoped override" pattern the file header above
   documents for .upload-card .btn / .acc-header .btn. */
/* Авторизация.dc.html:37 — `min-height: calc(100vh - 65px)`, `padding: 40px
   var(--page-pad)`: the card centres in the space under the header exactly
   as the prototype's does (bind-ui-to-mockup В33). */
.auth-shell { min-height: calc(100vh - 65px); display: flex; align-items: center; justify-content: center;
  padding: 40px var(--sp-6); }
/* redesign-auth-by-prototype: card chrome matches Авторизация.dc.html's
   inline style exactly (max-width 420/padding 26/box-shadow --elev-float);
   border-radius was already --r-card-lg (12px), no change needed there. */
.auth-card { width: 100%; max-width: 420px; background: var(--card); border: var(--bw-hair) solid var(--border);
  border-radius: var(--r-card-lg); box-shadow: var(--elev-float); padding: 26px; }

/* Review round 1 (BUG: 26px/800 "Сброс пароля"/"Новый пароль" instead of the
   prototype's 18px/700): a `.auth-form h1` rule used to sit here for a
   register-only page heading that no longer exists (login/register render
   no `<h1>` at all any more, only `passwordRequirementsBlock`'s siblings and
   the forgot/reset `.auth-heading-sm` headings do) — its 0,1,1 specificity
   still beat `.auth-heading-sm`'s bare 0,1,0 whenever both matched the same
   `<h1 class="auth-heading-sm">` inside `.auth-form`, so forgot/reset kept
   rendering the OLD dead rule's size/weight instead of this one's. Deleted
   rather than re-scoped: nothing renders a bare `.auth-form h1` any more, so
   there is no register heading left for it to serve. */
/* 16px under the heading — the prototype card's own flex gap (Авторизация.
   dc.html:39 `gap:16px`) between «Сброс пароля» / «Новый пароль» and what
   follows (bind-ui-to-mockup В35). */
.auth-heading-sm { font-size: 18px; font-weight: var(--w-bold); color: var(--text); margin: 0 0 var(--sp-4); }
.auth-sub { font-size: var(--fz-sm); font-weight: var(--w-regular); color: var(--muted); margin: 0 0 var(--sp-4); }
/* Register's own subtitle line is weight 500 in the prototype (a <span>,
   not a <p>) — every other .auth-sub (forgot/sent) stays the plain 400. */
.auth-sub-strong { font-weight: var(--w-medium); }

/* One 16px rhythm between everything on the card — the prototype card is a
   flex column with `gap:16px` (Авторизация.dc.html:39): tabs, fields,
   notice, button, link (bind-ui-to-mockup В33–В35). Scoped to the card:
   `.field` keeps its 20px elsewhere (cabinet forms). */
.auth-card .notice { margin-bottom: var(--sp-4); }
.auth-card .segmented { margin-bottom: var(--sp-4); }
.auth-card .field { margin-bottom: var(--sp-4); }
.auth-form > .btn { width: 100%; }

.field { margin-bottom: var(--sp-5); }
.field label { display: block; font: var(--w-semibold) var(--fz-sm) var(--font-ui); margin-bottom: var(--sp-2);
  color: var(--text); }
/* Auth-card-scoped label style (Авторизация.dc.html: 500 13px, --muted,
   6px gap to the control) — deliberately layered on TOP of the shared
   `.field label` rule above via a more specific selector, not a change to
   it: `.field` is reused by the cabinet's integrations/objects/
   template-editor/upload forms too, and this stage (redesign-auth-by-
   prototype) only re-skins the auth card. */
.auth-card .field label { font-weight: var(--w-medium); color: var(--muted); margin-bottom: 6px; }
/* A text input / select / textarea inside `.field` is styled by the DS rule
   itself (`.input, .field input…`, `.select, .field select`, `.textarea,
   .field textarea` — web_ds components.css): one implementation, no copy
   here (field_input_css_test.dart checks there is none). */
/* The template editor's «Загрузить анкету» file picker is a `<label>`
   styled as a DS button, sitting inside a .field: undo the `.field label`
   caption look (block, caption font, bottom margin that pushed the text to
   the top of the pill). */
.field .questionnaire-pick { display: inline-flex; margin-bottom: 0; font: var(--w-semibold) var(--fz-ui) var(--font-ui); }
.field-hint { font-size: var(--fz-xs); color: var(--muted); margin-top: var(--sp-1); }
.field-optional { font-weight: var(--w-regular); color: var(--muted); font-size: 0.92em; }
.field-error { font-size: var(--fz-xs); color: var(--danger); margin-top: var(--sp-1); display: none; }
.field.has-error input { border-color: var(--danger); }
.field.has-error .field-error { display: block; }

/* The consent row IS the DS `ds.checkbox()` `<label class="checkbox">` (its
   rich label carries the three legal links); only the type scale is
   auth-specific. */
.field-consent .checkbox { display: flex; align-items: flex-start; gap: var(--sp-3); cursor: pointer;
  font: var(--w-regular) var(--fz-xs) var(--font-ui); line-height: var(--lh-body); margin-bottom: 0; }
/* `.field label` (0,1,1) would otherwise flatten the DS `.checkbox` row into
   `display: block` + semibold — the consent row is a checkbox label, not a
   field caption. */
/* Descendant selector — still reaches the checkbox `<input>` however deep it
   nests inside `.checkbox`. */
.field-consent input[type="checkbox"] { width: 16px; height: 16px; padding: 0; margin: 3px 0 0; flex: none;
  border-radius: 4px; }
.field-consent a { color: var(--accent-deep); }
/* Live "Без согласия аккаунт создать нельзя." caption (Авторизация.dc.html's
   `termsOff` branch) — visible under the disabled submit button while the
   consent checkbox is unchecked. */
.auth-consent-hint { font-size: var(--fz-xs); color: var(--muted); text-align: center; margin-top: var(--sp-4); }

/* Password requirements checklist + strength meter (register/reset
   password fields — auth-redesign "Живой чек-лист пароля и надёжность").
   Live: rebuilds on every keystroke, see auth_page.dart's doc on the
   password field's onInput. */
/* 16px from the password field above (its .field margin) and between the
   strength line, the plate and the notice — the prototype card's 16px gap
   (bind-ui-to-mockup В34). */
.auth-password-meta { margin: 0 0 var(--sp-4); display: flex; flex-direction: column;
  gap: var(--sp-4); }
/* The «Слишком простой пароль» notice is the block's last child: the card-wide
   `.auth-card .notice` bottom margin would stack on the block's own and leave
   a ~50px hole above «Повтор пароля» (Б16). */
.auth-password-meta .notice { margin-bottom: 0; }
/* Grey requirements plate (redesign-auth-by-prototype, Авторизация.dc.html):
   padding 10px 12px / radius 8px / background --surface around the live
   requirement and its explanatory hint. */
.auth-checklist { display: flex; flex-direction: column; gap: var(--sp-1); padding: 10px 12px; border-radius: 8px;
  background: var(--surface); }
.auth-checklist-row { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fz-xs); color: var(--muted); }
.auth-checklist-hint { font-size: var(--fz-xs); line-height: 1.45; color: var(--faint-text, var(--muted)); }
/* «Надёжность:» muted 400, the value 600 in its strength colour, 8px apart
   (Авторизация.dc.html:53 — bind-ui-to-mockup В34). */
.auth-strength { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fz-xs); }
.auth-strength-label { font-weight: var(--w-regular); color: var(--muted); }
.auth-strength-value { font-weight: var(--w-semibold); }
.auth-strength-weak { color: var(--danger); }
.auth-strength-medium { color: var(--warn); }
.auth-strength-strong { color: var(--success); }

/* No border-top/padding-top (Авторизация.dc.html has no rule above
   "Забыли пароль?"/"← Назад ко входу" — those links float straight under
   the button with the card's own rhythm). */
.auth-foot { margin-top: var(--sp-4); display: flex; justify-content: center; }
/* Review round 1 (NL-79-style bug): "Забыли пароль?"/"← Назад ко входу" used
   to be `a(href: '#', onClick:)` — a jaspr anchor's `onClick` has no
   `preventDefault` hook, so every click ALSO followed the `href`, appending
   a stray `#` to the URL. Converted to real `<button type=button>`s
   (`auth_card.dart`), which have no default navigation to suppress in the
   first place; `.auth-link` gives them the exact link look the prototype's
   own `<a>` inline style specifies (Авторизация.dc.html:71/90/96). */
.auth-link { font: 500 13px var(--sans); color: var(--accent-deep); background: none; border: 0; padding: 0;
  cursor: pointer; align-self: center; }
.auth-link:hover { text-decoration: underline; }

/* ==== Cabinet shell (redesign-conformance, этап 5) — `Личный кабинет.
   dc.html` lines 48/120-130: the sidebar (`web_ds`'s `sideNav`) + `<main>`
   row wrapper, and the page-level h1+lead pair a few routes now render
   above their own section (see `app_cabinet.dart#_pageHead`). `--page-max`/
   `--page-pad` are prototype-only tokens (`web_ds/test/
   design_token_parity_test.dart`'s `allowedMissingInCode` — not part of the
   shared token set), so their values (1120px/24px, `design/system/tokens/
   geometry.css`) are literals here, same as `web_static/web/landing.css`'s
   own note for the identical gap. */
.cabinet-shell { max-width: 1120px; margin: 0 auto; padding: 0 24px; display: flex;
  align-items: flex-start; flex-wrap: wrap; gap: 28px; }
.cabinet-main { flex: 1 1 320px; min-width: 0; padding: 28px 0 60px; display: flex;
  flex-direction: column; gap: 32px; }
.cabinet-page-head { display: flex; flex-direction: column; gap: 6px; }
/* A screen of several blocks — an element (jaspr removes it whole), with no box: its blocks keep the column's gap. */
.cabinet-screen { display: contents; }
.cabinet-page-title { font: 700 26px var(--font-ui); margin: 0; color: var(--text); }
.cabinet-page-lead { font: 500 14px/1.5 var(--font-ui); margin: 0; color: var(--text); max-width: 640px; }

/* ==== Cabinet screens (Ф5) — UploadZone/ReportsTable containers, section shells, quota overlay ====
   `.main-view` no longer caps its own width (redesign-conformance этап 5)
   — it now sits inside `.cabinet-main`, which already bounds the whole
   row (`.cabinet-shell`'s 1120px minus the 216px sidebar), so its content
   stretches to fill whatever `.cabinet-main` gives it instead of a second,
   narrower cap on top.

   Review round 1 (mockup fidelity — `main` renders left-aligned, full
   column width, no centering): `align-items: center`/`text-align: center`/
   `margin: 0 auto` dropped (those were this file's own leftover from
   before the sidebar shell existed, when `.main-view` WAS the whole
   centered page). Vertical padding dropped too — `.cabinet-main` already
   supplies the row's own top/bottom rhythm (28px/60px); keeping BOTH would
   double it, and once `_pageHead` renders above this on `/calls` the extra
   gap would push the upload card away from its own lead line. Horizontal
   padding stays — the same `--sp-6` inset the sidebar-adjacent sections
   below (`.tariff-view`/`.templates-section`/…) already carry. */
/* Этап 5, сверка стейджинга: у `main` макета (L106) нет своего бокового/верхнего
   отступа — контент разделов стоит по левому краю заголовка страницы, ритм
   даёт `.cabinet-main` (28px/60px, gap 32px). Отступы разделов обнулены. */
.main-view { padding: 0; display: flex; flex-direction: column; gap: var(--sp-6); }
/* Обёртка экрана загрузки кабинета. Имя `upload-section`, а не
   `upload-zone`: последнее принадлежит компоненту дизайн-системы
   (`web_ds`, `.upload-zone`), и совпадение имён давало этой секции чужие
   фон, рамку, паддинг и тень — карточка внутри карточки.

   Review round 1 (`cabinet-first-run` + this stage's page-head): the outer
   `h1`/subtitle only ever renders for a brand-new tenant (`app_cabinet.
   dart`'s `_uploadSection` — `_hasCalls == false`). Once calls exist,
   `.upload-section--compact` applies instead: the outer heading is gone (an
   in-card `<h2 class="upload-card-heading">` takes its place — see below).

   Review round 2 (redesign-first-run-onboarding, sверка скриншота — L342-343
   `onbTitle`/`onbLead`): the first-run heading/lead were centered, `--w-
   black`/`--fz-h1`-sized, in a 480px-capped centered card — while the new
   onboarding panel right below it (`_onboardingPanelView`) is full-width and
   left-aligned like every other section, a visible mismatch. Brought into
   line with the mockup's own first-run typography (`h1` `700 26px`, lead
   `400 15px/1.5 var(--muted)` max-width 560) AND with every other populated
   section's own layout: left-aligned, full-width card, in BOTH states —
   `.upload-section--compact`'s card-width override is now redundant (kept,
   harmless) since the base rule already matches it. */
.upload-section-title { font: 700 26px var(--font-ui); margin: 0; color: var(--text); }
.upload-section-subtitle { font: 400 15px/1.5 var(--font-ui); color: var(--muted); margin: 0 0 var(--sp-2); max-width: 560px; }
.upload-section .card { width: 100%; max-width: none; margin: 0; }
.upload-section--compact .card { max-width: none; margin: 0; }
.upload-card-heading { font: 600 18px var(--font-ui); margin: 0 0 var(--sp-3); color: var(--text); }
.reports-container { width: 100%; max-width: 640px; text-align: left; }
.reports-container h2 { font: var(--w-bold) var(--fz-h2) var(--font-ui); margin: 0 0 var(--sp-4); }

/* Review round 1 (mockup fidelity): `main` (`Личный кабинет.dc.html`'s own
   `<main>`, L48) carries no inner max-width cap at all — every routed
   section stretches to the column's full width. `max-width: 720px;
   margin: 0 auto` below was this file's own pre-sidebar leftover (back
   when each of these WAS the whole centered page); dropped in favor of
   `max-width: none; margin: 0` — the padding stays (still this section's
   own horizontal/vertical inset, `.cabinet-main` doesn't give one). */
.section-shell { max-width: none; margin: 0; padding: 0; display: flex;
  flex-direction: column; gap: var(--sp-4); align-items: flex-start; }
.section-shell h2 { margin: 0; font: var(--w-bold) var(--fz-h2) var(--font-ui); }

/* ==== UploadZone (Ф6a-2 Task 3) — drop-zone, queue, status timer, result banners ==== */
.upload-dropzone { display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: var(--sp-2); padding: var(--sp-8) var(--sp-4); background: var(--surface);
  border: var(--bw-hair) solid var(--border); border-radius: var(--r-card);
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease); }
.upload-dropzone.is-dragover { border-color: var(--accent); background: var(--accent-soft); }
.upload-dropzone .icon { color: var(--accent); }
/* A11y utility: hide an element visually but keep it clickable.
   upload_zone.dart puts this on the native <input type=file>; the
   «Выбрать файл» ds.button opens it. Without this rule the raw
   "Choose Files" control renders on the newcomer screen. */
/* !important — утилита обязана побеждать любые правила вида `.field input`
   (width:100%, padding), иначе скрытый выбор файла внутри .field сохраняет
   ширину поля и даёт горизонтальную прокрутку страницы (редактор шаблона). */
.visually-hidden { position: absolute !important; width: 1px !important; height: 1px !important; padding: 0 !important;
  margin: -1px !important; overflow: hidden !important; clip: rect(0, 0, 0, 0) !important; white-space: nowrap !important;
  border: 0 !important; }
.upload-dropzone-text { font-size: var(--fz-body); font-weight: var(--w-medium); color: var(--text); margin: 0; }
.upload-formats-hint { font-size: var(--fz-xs); color: var(--faint); margin: var(--sp-1) 0 0; }

.queue-summary { margin-top: var(--sp-2); font-size: var(--fz-sm); color: var(--muted); font-weight: var(--w-medium); }
.queue-list { list-style: none; margin: var(--sp-2) 0 0; padding: 0; max-height: 220px; overflow-y: auto;
  border: var(--bw-hair) solid var(--border); border-radius: var(--r-card); }
.queue-row { display: flex; align-items: center; flex-wrap: wrap; column-gap: var(--sp-2); padding: 0 var(--sp-3);
  border-bottom: var(--bw-hair) solid var(--border); }
.queue-row:last-child { border-bottom: none; }
/* Дата записи файла (NL-325): справа от имени; вид поля — DS через `.field input`, высота — ряда DS sm
   (`.input.sm`: строка очереди низкая), ширину задаёт обёртка (поле DS — на всю ширину родителя). Источник —
   подсказкой (title). На узком экране уходит на свою строку, к правому краю: у имени своя основа ширины, поле не
   сжимается, строка переносится (flex-wrap); разделитель — у всей строки, не у имени, чтобы перенесённая дата
   читалась частью своего файла. */
.queue-date { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; flex: 0 0 13.5rem;
  margin: 0 0 0 auto; padding: var(--sp-1) 0; }
.queue-date input { height: var(--h-btn-sm); padding: 0 12px; font-size: var(--fz-sm); }
.queue-date-warn { font-size: var(--fz-xs); color: var(--warn-deep); text-align: right; }
/* Непринятая дата в поле (NL-325, `date_time_local_field.dart`): рамка и причина под полем — в очереди и в карточке;
   прежняя дата вернётся, когда фокус уйдёт. */
.queue-date input[aria-invalid="true"], .call-date-edit input[aria-invalid="true"] { border-color: var(--danger); }
.date-field-error { font-size: var(--fz-xs); color: var(--danger); margin-top: var(--sp-1); }
.queue-date .date-field-error { text-align: right; }
.queue-row .file-row { flex: 1 1 12rem; min-width: 0; border-bottom: none; }
.queue-remove { flex-shrink: 0; width: 22px; height: 22px; padding: 0; background: transparent;
  color: var(--faint); border: var(--bw-hair) solid var(--border); border-radius: 4px;
  font-size: var(--fz-ui); line-height: 1; cursor: pointer; }
.queue-remove:hover { background: var(--danger-soft); color: var(--danger); border-color: var(--danger-soft); }

.upload-card .btn { width: 100%; margin-top: var(--sp-5); }

.status { display: flex; flex-direction: column; align-items: center; gap: var(--sp-2);
  text-align: center; padding: var(--sp-6) 0 var(--sp-1); }
.spinner { width: 36px; height: 36px; border: 3px solid var(--border); border-top-color: var(--accent);
  border-radius: 50%; animation: upload-spin 0.8s linear infinite; }
@keyframes upload-spin { to { transform: rotate(360deg); } }
.status .elapsed { font-size: var(--fz-sm); color: var(--muted); }
.status .hint { font-size: var(--fz-xs); color: var(--faint); }
/* Карточка загрузки (NL-363): что происходит с файлом — основным цветом, не серым; ход — полосой под подписью. */
.upload-status .elapsed { font-weight: 500; color: var(--text); }

/* Прогресс загрузки по байтам (upload-survives-navigation) — нативный <progress> в токенах: значение
   доступно без своей ARIA. Без значения (сервер разбирает файл) — неопределённая полоса браузера. */
.upload-progress { -webkit-appearance: none; appearance: none; display: block; width: 100%; max-width: 320px;
  height: 6px; border: 0; border-radius: var(--r-full); background: var(--accent-soft); color: var(--accent);
  overflow: hidden; }
.upload-progress::-webkit-progress-bar { background: var(--accent-soft); border-radius: var(--r-full); }
.upload-progress::-webkit-progress-value { background: var(--accent); border-radius: var(--r-full); }
.upload-progress::-moz-progress-bar { background: var(--accent); border-radius: var(--r-full); }
/* Без значения `appearance: none` гасит родную анимацию — пустая дорожка читалась бы как «0 %».
   Своя бегущая подсветка; при «меньше движения» она стоит посередине. */
.upload-progress:indeterminate { background: linear-gradient(90deg, transparent, var(--accent), transparent)
  0 0 / 40% 100% no-repeat, var(--accent-soft); animation: upload-indeterminate 1.4s ease-in-out infinite; }
.upload-progress:indeterminate::-webkit-progress-bar { background: transparent; }
.upload-progress:indeterminate::-moz-progress-bar { background: transparent; }
@keyframes upload-indeterminate { from { background-position: -60% 0, 0 0; } to { background-position: 160% 0, 0 0; } }
@media (prefers-reduced-motion: reduce) {
  .upload-progress:indeterminate { animation: none; background-position: 50% 0, 0 0; }
}

/* Полоска загрузки вверху основной области вне «Звонков» — по образцу панели запуска макета
   («в шапке прогресс и полоса», NL-188): подпись, полоса, «К звонкам». На узком экране полоса уходит
   на свою строку. */
.upload-strip { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-2) var(--sp-3);
  padding: var(--sp-2) var(--sp-3); border: 1px solid var(--border); border-radius: var(--r-card);
  background: var(--surface-card); }
.upload-strip-label { font: 600 var(--fz-sm) var(--font-ui); color: var(--text); white-space: nowrap; }
.upload-strip .upload-progress { flex: 1 1 160px; max-width: none; }

.upload-result { margin-top: var(--sp-5); display: flex; flex-direction: column; gap: var(--sp-3); }
/* .notice.warn / .notice.ok a moved to the shared components.css (sub-unit 3). */

/* ==== OnboardingPanel («Начало работы», redesign-first-run-onboarding) —
   `components/onboarding_panel.dart`'s pure builder. Contextual classes,
   not DS ones (`ds_markup_guard_test.dart` — DS-owned names stay only
   inside `web_ds`'s own components.css); measurements follow `Личный
   кабинет.dc.html`'s own card, L134-175. The card itself is `ds.card(...,
   extraClass: 'onboarding-card')` — `padding: 0` below overrides the DS
   default (`.card { padding: var(--sp-card) }`) the same way
   `.summary-block`/`.calls-object` already layer their own rule on top of
   the shared `.card`, per that section's own comment. */
.onboarding-card { padding: 0; }
.onboarding-head { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
  padding: var(--sp-4) var(--sp-5); border-bottom: var(--bw-hair) solid var(--border); }
.onboarding-head-title { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 220px; }
/* Review round 1 (a11y): `<h2>` now, not a bare `<span>` (a heading gives
   the panel a landmark screen readers can jump to) — `margin: 0` cancels
   the browser default `<h2>` margin so the visual is unchanged. */
.onboarding-title { font: var(--w-bold) var(--fz-title) var(--sans); color: var(--text); margin: 0; }
.onboarding-head-meta { display: flex; align-items: center; gap: var(--sp-3); flex: none; }
.onboarding-progress { display: flex; flex-direction: column; gap: 3px; align-items: flex-end; }
.onboarding-progress-count { font: var(--w-semibold) var(--fz-title) var(--mono); color: var(--text); }
.onboarding-progress-label { font: var(--w-semibold) 10px var(--eyebrow-font); letter-spacing: var(--ls-mono);
  text-transform: uppercase; color: var(--faint-text); }
.onboarding-bar { height: 4px; background: var(--surface); }
.onboarding-bar-fill { height: 100%; background: var(--accent); }
.onboarding-rows { display: grid; gap: 0; align-content: start; }
.onboarding-row { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
  padding: var(--sp-3) var(--sp-5); border-bottom: var(--bw-hair) solid var(--border); }
.onboarding-row:last-child { border-bottom: none; }
.onboarding-mark { display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 26px; height: 26px; border-radius: 50%; border: var(--bw-hair) solid;
  font: var(--w-semibold) var(--fz-xs) var(--mono); }
.onboarding-mark--done { border-color: var(--success); background: var(--success-soft); color: var(--success-deep); }
.onboarding-mark--current { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-deep); }
.onboarding-mark--pending { border-color: var(--border); background: var(--surface); color: var(--faint-text); }
.onboarding-row-text { display: flex; flex-direction: column; gap: 1px; flex: 1; min-width: 200px; }
.onboarding-row-label { font: var(--w-medium) var(--fz-body) var(--sans); color: var(--text); }
.onboarding-row-label--done { color: var(--muted); }
.onboarding-row-hint { font: var(--w-regular) var(--fz-xs) var(--sans); line-height: 1.45; color: var(--muted); }
.onboarding-row-done { font: var(--w-medium) var(--fz-xs) var(--mono); color: var(--success-deep); flex: none; }
/* Direction picker under the «Указать направление бизнеса» row
   (industry-once-scoped-templates): DS select + «Сохранить», indented under
   the row text (mark 26px + gap). */
.onboarding-industry { display: flex; flex-direction: column; gap: var(--sp-2);
  padding: 0 var(--sp-5) var(--sp-3) calc(var(--sp-5) + 26px + var(--sp-3));
  border-bottom: var(--bw-hair) solid var(--border); }
.onboarding-industry-row { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.onboarding-industry-row .select { flex: 1; min-width: 200px; max-width: 360px; }

/* ==== ReportsTable (redesign-conformance, этап 6 — `redesign-calls-list-
   on-callrow`) — "Разборы": filters, объект→роль groups, foot, bulk-delete.
   `.calls-table`/`.calls-table-wrap`/`.calls-error-banner` (+ their mobile
   card-stack, below) are NOT this list's own any more — `AdminSection`'s
   own users table is this rule's other remaining consumer (`SummaryBlock`'s
   reports-history no longer renders a `<table>` at all, redesign-
   conformance этап 8 — see this file's own «SummaryBlock» section below). */
/* Review round 1 (стейджинг): the list used to sit inside its OWN grey
   card (background/border/padding) — the mockup (L106+) has object cards
   standing directly in `main`'s flow, no such wrapper. `.calls-list` is
   kept only as a plain flow/margin wrapper now, no visual chrome of its
   own — the object cards (`.calls-object`, itself `ds.card`) are the only
   "card" look on this screen. */
.calls-list { width: 100%; margin-bottom: var(--sp-7); }
.calls-list-head { display: flex; justify-content: flex-start; align-items: center;
  margin-bottom: var(--sp-3); flex-wrap: wrap; gap: var(--sp-3); }
.calls-partial-note { margin-bottom: var(--sp-3); font-size: var(--fz-sm); color: var(--muted); }
/* NL-342: строка фильтров — во всю шапку: иначе поиск переносился на свою строку и при свободном месте. */
.calls-filters { display: flex; gap: var(--sp-2); flex-wrap: wrap; align-items: center; flex: 1 1 auto; }
/* NL-342: поиск по загруженному списку — глиф лупы из общего набора Lucide перед полем DS (у Input снимка иконки
   нет; вид поля не переопределяется — только ширина). На телефоне — во всю ширину ряда. */
.calls-search { display: inline-flex; align-items: center; gap: var(--sp-2); flex: 0 1 300px; min-width: 200px; }
.calls-search > svg { flex: none; color: var(--muted); }
.calls-search > .input { width: 100%; }
@media (max-width: 480px) { .calls-search { flex: 1 1 100%; } }
/* NL-342: список грузится — скелет из трёх строк, а не «пусто». */
.calls-skeleton { display: flex; flex-direction: column; gap: var(--sp-2); }
.calls-skeleton-row { height: 56px; border-radius: var(--r-card); background: var(--surface);
  animation: calls-skeleton-pulse 1.2s ease-in-out infinite; }
@keyframes calls-skeleton-pulse { 50% { opacity: .55; } }
@media (prefers-reduced-motion: reduce) { .calls-skeleton-row { animation: none; } }
/* Идущие разборы перестали опрашиваться (5 минут) — просьба обновить. */
.calls-running-stale { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; margin-bottom: var(--sp-3); }
.calls-running-stale > .notice { flex: 1 1 260px; margin: 0; }
/* NL-331 — баннер «N звонков оценены не по шаблону сотрудника» (`components/recalc_banner_markup.dart`): над списком
   «Разборов», в «Сотрудниках» и в папке сотрудника. Строка текста — живая область и цель фокуса после кнопки. */
.recalc-banner { display: flex; align-items: center; gap: var(--sp-2) var(--sp-3); flex-wrap: wrap; min-width: 0;
  margin-bottom: var(--sp-3); padding: var(--sp-3) var(--sp-4); border: var(--bw-hair) solid var(--warn-soft);
  border-radius: var(--r-card); background: var(--card); }
.recalc-banner-text { flex: 1 1 260px; min-width: 0; margin: 0; font-size: var(--fz-sm); color: var(--text); }
/* Сказать нечего, кроме заметки: строка остаётся (живая область, цель фокуса), но места не занимает. */
.recalc-banner-text:empty { flex: 0 0 0; }
/* Под итогом запуска — звонки, которые ещё не по шаблону сотрудника (с кнопкой рядом). */
.recalc-banner-detail { flex: 1 1 260px; min-width: 0; margin: 0; font-size: var(--fz-sm); color: var(--muted); }
.recalc-banner-text:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); }
.recalc-banner-notice { flex: 1 1 100%; min-width: 0; }
.recalc-banner-notice > .notice { margin: 0; }
.employee-folder > .recalc-banner { margin-bottom: 0; }
/* Обёртка строки — одна и та же с пометкой и без (DOM строки не пересоздаётся); с пометкой — группа, названная ею. */
.calls-row { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 0; }
/* Пометка строки звонка, оценённого не по шаблону сотрудника: сразу под строкой (шаблон сотрудника — в подсказке). */
.calls-mismatch-mark { align-self: flex-start; max-width: 100%; margin: calc(var(--sp-1) - var(--sp-2)) 0 0 var(--sp-3);
  padding: 2px 7px; border-radius: var(--r-full); background: var(--warn-soft); color: var(--warn-deep);
  font: 600 11px var(--sans); letter-spacing: .02em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  cursor: help; }
/* Property filter select (Ф6c-3 Task 3) — tokenized port of cabinet.css's
   `.property-filter` (originally scoped to the header/user-bar; relocated
   here into `.calls-filters` per the Jaspr component split, `reports_table.
   dart`'s file doc) — same compact sizing, distinct from `.calls-filters
   input` above only in that a native <select> needs no fixed max-width cap
   as aggressive (property names run longer than a date input). */
.property-filter { padding: var(--sp-2) var(--sp-3); font-size: var(--fz-sm); border: var(--bw-hair) solid var(--border);
  border-radius: var(--r-chip); max-width: 220px; min-width: 0; background: var(--bg); color: var(--text); }

/* Объект→роль groups (`components/reports_table_markup.dart`'s
   `buildCallsGroups`/`buildObjectCard`/`buildRoleBlock`) — contextual
   classes, not DS ones (`ds_markup_guard_test.dart` — DS-owned names stay
   only inside `web_ds`'s own components.css). Measurements follow
   `Личный кабинет.dc.html`'s own home-tree spacing (строки 175-268) where
   this app's 2-level объект→роль shape lines up with it (see
   `domain/call_view.dart#groupCallsByPropertyRole`'s doc for why the shape
   itself differs from the 3-level snapshot sketch). */
.calls-groups { display: flex; flex-direction: column; gap: var(--sp-4); }
/* Review round 1 (redesign-first-run-onboarding, сверка L177-190): the
   mockup's object card is `Card padding="0"` with `TreeRow tone="header"`
   as its head — a full-bleed strip with its OWN `--surface` background and
   bottom border (`TreeRow.jsx`: `padding: var(--space-3) var(--space-4)`,
   `background: tone==='header' ? 'var(--surface)' : 'transparent'`), not a
   head sitting inside the card's own 24px padding with no background of
   its own. `.calls-object` zeroes the card's padding; the head and the
   roles area below both carry their own horizontal inset instead
   (`var(--sp-4)` = 16px, matching the mockup's own 16px). */
.calls-object { padding: 0; }
.calls-object-head { display: flex; align-items: baseline; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4); background: var(--surface);
  border-bottom: var(--bw-hair) solid var(--border); }
.calls-object-eyebrow { font: 600 var(--fz-micro) var(--eyebrow-font); letter-spacing: var(--ls-mono); text-transform: uppercase;
  color: var(--faint-text); flex: none; }
/* Длинные названия объекта и роли переносятся (на телефоне многоточие
   оставляло от роли «Адми…»: её вытесняла кнопка «Собрать сводный»). */
.calls-object-name { min-width: 0; font: 500 var(--fz-ui) var(--sans); color: var(--text);
  white-space: normal; overflow-wrap: anywhere; }
/* Mockup padding `12px 16px 14px` (L184) — 12/16 match `--sp-3`/`--sp-4`,
   14 has no scale token and stays literal. */
.calls-object-roles { display: flex; flex-direction: column; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4) 14px; }
.calls-object-roles-eyebrow { font: 600 var(--fz-micro) var(--eyebrow-font); letter-spacing: var(--ls-mono); text-transform: uppercase;
  color: var(--faint-text); }

.calls-role { border: var(--bw-hair) solid var(--border); border-radius: var(--r-card); background: var(--surface); }
/* Mockup role-row head padding `10px 12px` (L189) — 12 matches `--sp-3`,
   10 has no scale token and stays literal. */
/* Имя роли и её кнопка — одна строка, длинное имя роли урезается
   многоточием (полное — в title), как в макете на 390 (сверка 2026-10-01, В8). */
.calls-role-head { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: nowrap; padding: 10px var(--sp-3); }
.calls-role-head > :not(.calls-role-name) { flex: none; }
.calls-role-name { flex: 1 1 auto; min-width: 0; font: 600 var(--fz-ui) var(--sans); color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.calls-role-calls-head { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
  padding: var(--sp-2) var(--sp-3); border-top: var(--bw-hair) solid var(--border); }
.calls-role-count { flex: 1; min-width: 0; font: 500 var(--fz-sm) var(--sans); color: var(--text); white-space: nowrap; }
.calls-role-body { border-top: var(--bw-hair) solid var(--border); padding: var(--sp-3);
  display: flex; flex-direction: column; gap: var(--sp-2); }
/* Чипы сотрудников переносятся на новые строки, как в макете (сверка
   2026-10-01, В6): лента с прокруткой срезала последний чип краем карточки
   без признака прокрутки. ФИО длиннее строки не рвётся внутри пилюли
   фиксированной высоты — ужимается с многоточием. */
.calls-role-emp-filter { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; min-width: 0; }
.calls-role-emp-filter > * { flex: none; }
.calls-role-emp-filter > :not(.calls-role-emp-label) { display: block; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; line-height: calc(var(--h-btn-sm) - 2px); }
.calls-role-emp-label { font: 400 var(--fz-xs) var(--sans); color: var(--muted); }
.calls-role-list { display: flex; flex-direction: column; gap: var(--sp-2); }

/* `.calls-table`/`.calls-table-wrap` — `AdminSection`'s own users table is
   the only remaining consumer (redesign-conformance этап 8 retired
   `SummaryBlock`'s own `<table>` — see this section's own header note
   above). `.calls-error-banner` stays shared more widely: `ReportsTable`
   and `SummaryBlock` (its own error banner, unrelated to the table) both
   still use it. */
.calls-table-wrap { overflow-x: auto; }
.calls-table { width: 100%; border-collapse: collapse; font-size: var(--fz-sm); }
.calls-table th { text-align: left; padding: var(--sp-2); font-weight: var(--w-semibold); color: var(--muted);
  border-bottom: var(--bw-hair) solid var(--border); white-space: nowrap; }
.calls-table td { padding: var(--sp-2); border-bottom: var(--bw-hair) solid var(--border); vertical-align: middle; }
.calls-table tbody tr:hover { background: var(--card); }
.calls-table .col-pdf .btn { padding: 0 var(--sp-3); height: 28px; font-size: var(--fz-xs); }

.calls-error-banner { text-align: center; padding: var(--sp-6); color: var(--danger); font-size: var(--fz-sm); }
.calls-error-banner .btn { margin-top: var(--sp-2); }

.calls-foot { display: flex; justify-content: flex-start; align-items: center; margin-top: var(--sp-4);
  padding-top: var(--sp-4); border-top: var(--bw-hair) solid var(--border); gap: var(--sp-3); flex-wrap: wrap; }

.bulk-delete-confirm { margin-top: var(--sp-4); padding: var(--sp-4); background: var(--card);
  border: var(--bw-hair) solid var(--danger-soft); border-radius: var(--r-card); display: flex;
  flex-direction: column; gap: var(--sp-3); }
.bulk-delete-confirm-message { white-space: pre-line; font-size: var(--fz-sm); color: var(--text); line-height: var(--lh-body); }
.bulk-delete-confirm input { padding: var(--sp-2) var(--sp-3); border: var(--bw-hair) solid var(--border);
  border-radius: var(--r-card); background: var(--bg); color: var(--text); font-size: var(--fz-body); }
.bulk-delete-confirm-actions { display: flex; gap: var(--sp-2); justify-content: flex-end; }

/* CallCard stack on narrow screens — rows become bordered cards instead of a
   horizontally scrolled table. `AdminSection`'s own users table is this
   rule's only remaining consumer (redesign-conformance, этап 6 retired the
   calls list's own `<table>`; этап 8 retired `SummaryBlock`'s). */
@media (max-width: 640px) {
  .calls-table-wrap { overflow-x: visible; }
  .calls-table thead { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
  .calls-table, .calls-table tbody, .calls-table tr, .calls-table td { display: block; width: 100%; }
  .calls-table tr { border: var(--bw-hair) solid var(--border); border-radius: var(--r-card);
    margin-bottom: var(--sp-3); padding: var(--sp-3); background: var(--card); }
  .calls-table tbody tr:hover { background: var(--card); }
  .calls-table tr:last-child { margin-bottom: 0; }
  .calls-table td { border-bottom: none; padding: 2px 0; text-align: left; overflow-wrap: anywhere; }
  .calls-table td:first-child { display: inline-block; width: auto; margin-bottom: var(--sp-1); }
  .calls-table td[data-label]::before { content: attr(data-label); display: inline-block; min-width: 84px;
    margin-right: var(--sp-2); font-size: var(--fz-xs); font-weight: var(--w-semibold); color: var(--muted); }
}

/* ==== SummaryBlock (Ф6a-2 Task 5, re-shaped redesign-conformance этап 8 —
   `add-reports-list-and-delete`) — the "Сводные" card: header («Сводные ·
   N» + generate + «Обновить»), generate notices/status/result banners
   (reuses UploadZone's `.status`/`.spinner`/`.elapsed`/`.notice`, shared
   timer/banner look, unchanged), then `ds.summaryRow` history in a
   `ds.scrollList` (`components/summary_block_markup.dart`) or
   `ds.emptyState` — `Личный кабинет.dc.html`'s «Отчёты» screen, `#rep-sums`
   (строки ~850-878). The card itself is a plain `ds.card` (`padding="16"`
   in the mockup = `ds.CardPadding.sm` = `--sp-4`) — every class below is a
   CONTEXTUAL layout addition on top of it, not a second card look
   (`ds_markup_guard_test.dart` — DS-owned names stay only inside `web_ds`'s
   own components.css). `.calls-error-banner` (defined above, `ReportsTable`
   section) is reused as-is for the history-load error — unrelated to the
   retired `<table>` look that class family used to also cover here. ==== */
.summary-block { width: 100%; margin-bottom: var(--sp-7); }
.summary-generate-row { display: flex; align-items: center; gap: var(--sp-3);
  flex-wrap: wrap; margin-bottom: var(--sp-4); }

.reports-card { display: flex; flex-direction: column; gap: var(--sp-4); }
.reports-head { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.reports-head-title { flex: 1; min-width: 180px; font: var(--w-bold) var(--fz-title) var(--font-ui); color: var(--text); }

/* One history row — a neutral `ds.card` (padding sm = 16px) by the
   mockup's `#rep-sums` row (сверка 2026-10-01, В13): title + meta column,
   status chip, outlined «Открыть», trash — all inside the card; on a phone
   the text column takes the full width and the chip/actions wrap below it,
   every row the same width (Б17). */
.reports-row { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.reports-row-text { display: flex; flex-direction: column; gap: 2px; flex: 1 1 220px; min-width: 0; }
.reports-row-title { font: var(--w-semibold) 16px var(--font-ui); color: var(--text); /* макет: --screen-card-title, 16px */
  overflow-wrap: anywhere; }
.reports-row-meta { font-size: var(--fz-xs); color: var(--muted); }

/* Delete-confirm `ds.modal` body — the message text is a plain `<p>`, same
   spacing/line-height the rest of the cabinet's modal bodies use. */
.reports-delete-modal .modal-body p { margin: 0 0 var(--sp-3); line-height: var(--lh-body); }
.reports-delete-modal .modal-body p:last-child { margin-bottom: 0; }

/* ==== Tariff view container (Ф6b Task 5) — the `/calls/tariff` case's
   shell wrapper: `PaymentReturnBanner`/`BillingPanel`/`AmbassadorBlock`/the
   back-link mount as plain siblings (not nested components), so ONE
   `.tariff-view` owns the 720px column + gutter that `.billing-panel`
   used to own itself — `.billing-panel` now nests inside it and yields
   its own centering/padding, so it lines up with its siblings instead of
   doubling the gutter. ==== */
.tariff-view { max-width: none; margin: 0; padding: 0; }
.tariff-view .status { padding: var(--sp-8) 0; }
.tariff-back { margin-top: var(--sp-6); }

/* ==== BillingPanel (Ф6b Task 3, buy-pack surface reworked to a rail in Ф3h,
   top reworked to one «Ваш тариф» card — В20 сверки 2026-10-01) —
   "/calls/tariff": the «Ваш тариф» card (`billingPlanCard`), the shared DS
   `ds.tariffRail()` buy-pack rail (styled by `web_ds` components.css),
   billing banner. Nested inside `.tariff-view` (Task 5) — no
   max-width/margin/padding of its own so it doesn't double the wrapper's
   gutter. ==== */
.billing-panel { max-width: none; margin: 0; padding: 0; }
.tariff-page-title { font-size: var(--fz-h2); font-weight: var(--w-bold); color: var(--text);
  margin: 0 0 var(--sp-6); }

.billing-panel .tariff-block { margin-top: var(--sp-8); }

/* «Ваш тариф» — карточка макета `?go=tariff`: рамка, тень `--elev`, поля
   18/20, слева тариф, справа «Осталось» / «Запас»; на узком экране колонки
   уходят под тариф (wrap). Ни одна часть не шире карточки: минимальные
   ширины макета (240 / 150) — не больше 100%, длинное слово переносится. */
.tariff-plan-card { border: var(--bw-hair) solid var(--border); border-radius: var(--r-card); background: var(--card);
  box-shadow: var(--elev); padding: 18px var(--sp-5); }
.tariff-plan-row { display: flex; align-items: flex-start; gap: var(--sp-5); flex-wrap: wrap; }
.tariff-plan-main { display: flex; flex-direction: column; gap: var(--sp-1); flex: 1 1 0; min-width: min(240px, 100%); }
.tariff-plan-eyebrow { font: var(--w-semibold) var(--fz-micro) var(--eyebrow-font); letter-spacing: .05em; text-transform: uppercase;
  color: var(--faint-text, var(--muted)); }
.tariff-plan-title { font: var(--w-bold) 22px var(--sans); color: var(--text); overflow-wrap: anywhere; }
.tariff-plan-term { font: var(--w-regular) var(--fz-sm)/1.5 var(--sans); color: var(--muted); }
.tariff-plan-more { font: var(--w-medium) var(--fz-sm)/1.5 var(--sans); color: var(--text); overflow-wrap: anywhere; }
/* «Подписка закончится …, дальше Free …» — макет `billEndSoonText`: 500 13px, --warn-deep. */
.tariff-plan-soon { font: var(--w-medium) var(--fz-sm)/1.5 var(--sans); color: var(--warn-deep); overflow-wrap: anywhere; }
.tariff-plan-stat { display: flex; flex-direction: column; gap: 2px; min-width: min(150px, 100%); }
.tariff-plan-stat-value { font: var(--w-bold) 26px var(--mono); color: var(--text); }
.tariff-plan-stat-note { font: var(--w-regular) var(--fz-xs) var(--sans); color: var(--muted); }
.tariff-plan-card .cta-row { display: flex; align-items: center; gap: var(--sp-2) var(--sp-3); flex-wrap: wrap;
  margin-top: var(--sp-4); }
.tariff-plan-card .cta-hint { font-size: var(--fz-sm); color: var(--muted); min-width: 0; overflow-wrap: anywhere; }
/* Купленные пакеты — под линией в той же карточке, у каждого свой срок. */
.tariff-plan-packs { margin-top: var(--sp-4); padding-top: var(--sp-2); border-top: var(--bw-hair) solid var(--border); }
.tariff-plan-packs .pack-item { gap: var(--sp-3); }
.tariff-plan-packs .pack-status { min-width: 0; text-align: right; overflow-wrap: anywhere; }

.pack-item { font-size: var(--fz-sm); padding: var(--sp-2) 0; display: flex; justify-content: space-between; }
.pack-item.is-active { color: var(--text); }
.pack-item.is-depleted { color: var(--faint); }
.pack-item.is-depleted .pack-status { font-style: italic; }
.pack-item.is-expired { color: var(--warn); }
.pack-item.is-expired .pack-status { font-style: italic; }

/* The buy-pack rail itself (`.tariff-rail` + its `.tariff-rail-*` parts)
   is styled by the shared DS stylesheet (`web_ds/lib/css/components.css`),
   loaded on this page too — Ф3h dropped the bespoke `.price-card` picker in
   favour of that one shared `ds.tariffRail()`, so no local rail CSS lives
   here anymore. The DS rail sits directly in the `.tariff-block`. */

.billing-banner { margin-top: var(--sp-3); padding: var(--sp-2) var(--sp-3); font-size: var(--fz-sm);
  border-radius: var(--r-card); background: var(--surface); color: var(--text); }
.billing-banner.is-success { background: var(--success-soft); color: var(--success); }
.billing-banner.is-error { background: var(--danger-soft); color: var(--danger); }

/* pricing-foot — trust line under the buy-pack picker (landing's
   `index.html:765-766`, reused here for the same three assurances). */
.pricing-foot { margin-top: var(--sp-4); font-size: var(--fz-xs); color: var(--muted);
  display: flex; gap: var(--sp-5); flex-wrap: wrap; }

/* ==== AmbassadorBlock (Ф6b Task 4) — the "+5 разборов" widget-bonus
   collapsible section («Как увеличить бесплатный лимит вдвое ▸/▾»), mounted
   alongside `.billing-panel` under `/calls/tariff` (Task 5 wires it in).
   Ported verbatim from `frontend/public/calls/cabinet.css` (423-434) +
   `index.html`'s markup (184-186) — same class names, given its own
   top-level `margin-top`/`border-top` (not scoped under `.billing-panel
   .tariff-block`) so it reads correctly whether Task 5 nests it inside the
   panel or mounts it as a sibling. Reuses `.billing-banner` (defined above)
   for its own banner slot — the component-boundary divergence noted in
   `components/billing_panel.dart`'s file doc comment. ==== */
.ambassador-section { border-top: var(--bw-hair) solid var(--border); margin-top: var(--sp-5);
  padding-top: var(--sp-4); }
.ambassador-toggle { font-size: var(--fz-xs); color: var(--muted); text-decoration: none; padding: 0;
  border: none; background: none; cursor: pointer; }
.ambassador-toggle:hover { color: var(--accent); }
.ambassador-toggle:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); }
.ambassador-body { font-size: var(--fz-sm); color: var(--text); margin-top: var(--sp-3); }
.ambassador-body p { margin: var(--sp-2) 0; }
.ambassador-body .ambassador-title { font-size: var(--fz-ui); font-weight: var(--w-semibold);
  margin-bottom: var(--sp-2); color: var(--accent); }
.ambassador-body input[type="url"] { width: 100%; padding: var(--sp-2); border: var(--bw-hair) solid var(--border);
  border-radius: var(--r-card); font-size: var(--fz-sm); margin-top: var(--sp-2); box-sizing: border-box; }
.ambassador-body .ambassador-snippet { background: var(--surface); border: var(--bw-hair) solid var(--border);
  border-radius: var(--r-card); padding: var(--sp-2) var(--sp-3); font-family: var(--font-num);
  font-size: var(--fz-micro); word-break: break-all; margin: var(--sp-3) 0; }
.ambassador-body .ambassador-cta { display: flex; gap: var(--sp-2); align-items: center; margin-top: var(--sp-2); }
.ambassador-body .ambassador-status { display: inline-block; padding: var(--sp-1) var(--sp-3);
  background: var(--success-soft); color: var(--success); border: var(--bw-hair) solid var(--success-soft);
  border-radius: var(--r-chip); font-size: var(--fz-micro); font-weight: var(--w-semibold); margin-bottom: var(--sp-1); }
.ambassador-body .ambassador-hint { font-size: var(--fz-micro); color: var(--muted); margin-top: var(--sp-2); }
.ambassador-body .ambassador-reason { padding: var(--sp-2) var(--sp-3); background: var(--warn-soft);
  color: var(--warn); border: var(--bw-hair) solid var(--warn-soft); border-radius: var(--r-card);
  font-size: var(--fz-sm); margin-top: var(--sp-2); }

/* ==== TemplatesSection (Ф6c-2 Task 3) — the `/calls/templates` list +
   resolved-checklist viewer. `.settings-row`/`.settings-list`/
   `.settings-viewer` are ported verbatim (same class names, same shapes)
   from `frontend/public/calls/cabinet.css` (458-482) — re-homed under
   `.templates-section` (this route's own padded column, same shape as
   `.tariff-view`) instead of the legacy `#settingsDialog` card; the section
   itself has no card background here, same "routed full-page view, not a
   modal" treatment `.tariff-view`/`.section-shell` already use.
   `.settings-editor` (Task 4) reuses this same `.templates-section`
   wrapper + `.section-heading`. ==== */
.templates-section { max-width: none; margin: 0; padding: 0; }
.section-heading { margin: 0; font-size: var(--fz-h2); font-weight: var(--w-bold); color: var(--text); }
/* Generic `.hint` — cabinet.css's own bare `.hint` rule (line 111),
   reusable by any future section (objects/integrations, Ф6c-3/Ф6d), not
   just this one — unlike the other hint variants already in this file
   (`.field-hint`/`.upload-formats-hint`/…), which are each scoped to their
   own component. */
.hint { font-size: var(--fz-xs); color: var(--faint); margin-top: var(--sp-1); }
.templates-section > .hint { margin: 0 0 var(--sp-5); }

.settings-list { display: flex; flex-direction: column; gap: var(--sp-2); }
.settings-row { display: flex; justify-content: space-between; align-items: center;
  padding: var(--sp-3) var(--sp-4); background: var(--card); border: var(--bw-hair) solid var(--border);
  border-radius: var(--r-card); gap: var(--sp-3); flex-wrap: wrap; }
.settings-row-info { display: flex; flex-direction: column; gap: 2px; flex: 1 1 12em; min-width: 0; }
.settings-row-name { font-size: var(--fz-ui); font-weight: var(--w-semibold); color: var(--accent); }
.settings-row-meta { font-size: var(--fz-micro); color: var(--muted); overflow-wrap: anywhere; }
.settings-row-actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; }
.settings-row-actions .btn { height: var(--h-btn-sm); padding: 0 var(--sp-3); font-size: var(--fz-xs); }

/* Заголовок раздела с лидом (сверка 2026-10-01, В27/В29): лид — --screen-note-size
   --muted под заголовком, как у экранов макета («Роли», «Объекты»), а не
   мелкий --faint `.hint`. «Шаблоны», «Интеграции», «API-ключи». */
.section-head { display: flex; flex-direction: column; gap: 6px; margin-bottom: var(--sp-5); }
.section-lead { margin: 0; max-width: 640px; font-size: var(--screen-note-size); line-height: var(--lh-body); color: var(--muted); }

/* «Шаблоны» на языке строк «Ролей» макета (`?go=roles`, сверка 2026-10-01,
   В26/В27): одна карточка-группа, строка — серая плашка (10px 12px), имя
   16px тёмным, мета 12px, действия справа; «Создать шаблон» — внизу группы. */
.template-group { display: flex; flex-direction: column; gap: var(--sp-3); }
.template-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 10px var(--sp-3);
  border: var(--bw-hair) solid var(--border); border-radius: var(--r-card); background: var(--surface); }
.template-row-text { display: flex; flex-direction: column; gap: 2px; flex: 1 1 220px; min-width: 0; }
.template-row-name { font: var(--w-semibold) 16px var(--font-ui); color: var(--text); /* макет: --screen-card-title, 16px */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* NL-330: имя и пометка «Основной» в одну строку; имя по-прежнему сжимается с многоточием. */
.template-row-title { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
.template-main-mark { flex: none; }
.template-row-error { font-size: var(--fz-xs); color: var(--danger); overflow-wrap: anywhere; }
/* Основной аккаунта не найден в списке (ревью раунда 1) — над карточкой, с отступом до неё. */
.templates-main-missing { margin-bottom: var(--sp-3); }
.template-row-meta { font-size: var(--fz-xs); color: var(--muted); overflow-wrap: anywhere; }
.template-row-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-left: auto; }
.template-add { display: flex; padding-top: 2px; }

/* Первый вход (NL-311): «По чему оцениваем звонки?» на месте блока загрузки — карточки на плашке строки «Шаблонов»
   (ChoiceCard в ДС кабинета нет), выбранная — акцентной рамкой и «будет основным». Выбор — кнопка-радио во всю
   ширину текста карточки; действия — рядом, не внутри. */
.template-choice { display: flex; flex-direction: column; gap: var(--sp-4); }
.template-choice-head { display: flex; flex-direction: column; gap: var(--sp-2); }
.template-choice-head .upload-section-subtitle { margin: 0; }
.template-choice-cards { display: flex; flex-direction: column; gap: var(--sp-3); }
.template-choice-card.is-selected { border-color: var(--accent); background: var(--accent-soft); }
.template-choice-pick { flex: 1 1 220px; min-width: 0; display: flex; padding: 0; margin: 0; border: 0; background: none;
  color: inherit; font: inherit; text-align: left; cursor: pointer; }
.template-choice-pick:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); border-radius: 2px; }
.template-choice-criteria { flex: 1 0 100%; padding-top: var(--sp-2); border-top: var(--bw-hair) solid var(--border); }
.template-choice-submit { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-2); }
.template-choice-hint, .template-choice-loading, .calls-top-loading { font-size: var(--fz-sm); color: var(--muted); margin: 0; }
.template-choice-error { font-size: var(--fz-sm); color: var(--danger); margin: 0; overflow-wrap: anywhere; }

.settings-viewer, .settings-editor { margin-top: var(--sp-5); padding: var(--sp-5); background: var(--card);
  border: var(--bw-hair) solid var(--border); border-radius: var(--r-card-lg); }
.settings-editor { container-type: inline-size; }
.settings-editor > .section-heading { margin-bottom: var(--sp-5); overflow-wrap: anywhere; }
.settings-viewer h3 { font-size: var(--fz-title); font-weight: var(--w-semibold); color: var(--accent);
  margin: 0 0 var(--sp-3); }
.settings-viewer p { font-size: var(--fz-sm); margin: var(--sp-2) 0; }
.settings-viewer .viewer-mode, .settings-viewer .viewer-loading { color: var(--muted); font-size: var(--fz-xs); }
.settings-viewer .viewer-broken { padding: var(--sp-2) var(--sp-3); background: var(--danger-soft);
  border: var(--bw-hair) solid var(--danger); border-radius: var(--r-card); color: var(--danger); }
.settings-viewer .viewer-fetch-error { color: var(--warn); font-size: var(--fz-xs); }
.settings-viewer .viewer-note, .settings-viewer .viewer-disabled-count { color: var(--muted); }
.settings-viewer .viewer-heading { font-weight: var(--w-semibold); margin-top: var(--sp-3); }
.settings-viewer ul { margin: var(--sp-1) 0 0 18px; padding: 0; font-size: var(--fz-sm); }
.settings-viewer li { margin-bottom: var(--sp-1); }
.settings-viewer .criterion-meta { color: var(--muted); margin-left: var(--sp-2); font-size: var(--fz-micro); }
.settings-viewer .viewer-summary-notes { margin-top: var(--sp-2); padding: var(--sp-2) var(--sp-3);
  background: var(--warn-soft); border-radius: var(--r-card); font-size: var(--fz-sm); }
.settings-viewer pre { white-space: pre-wrap; font-size: var(--fz-xs); background: var(--surface);
  padding: var(--sp-2) var(--sp-3); border-radius: var(--r-card); }
.settings-viewer-actions { display: flex; gap: var(--sp-2); margin-top: var(--sp-4); }

/* ==== TemplateEditor (Ф6c-2 Task 4) — extend-builtin/full-custom criteria
   editor, ported (same class names) from `frontend/public/calls/cabinet.css`
   (483-507). `.settings-editor`'s own card chrome is folded into the
   `.settings-viewer` rule above (legacy CSS combined both selectors too).
   Its `.field select`/`.field textarea` controls are styled by the DS rules
   (web_ds components.css). */

.editor-section-label { display: block; margin-top: var(--sp-4); font-size: var(--fz-xs);
  font-weight: var(--w-medium); color: var(--muted); }

.editor-mode-toggle { display: flex; border: var(--bw-hair) solid var(--border); border-radius: var(--r-card);
  overflow: hidden; width: fit-content; margin-top: var(--sp-1); }
.editor-mode-toggle .mode-btn { padding: var(--sp-2) var(--sp-4); font-size: var(--fz-xs);
  font-weight: var(--w-medium); background: var(--card); color: var(--muted); border: none; cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease); }
.editor-mode-toggle .mode-btn + .mode-btn { border-left: var(--bw-hair) solid var(--border); }
.editor-mode-toggle .mode-btn.is-active { background: var(--accent); color: var(--accent-ink); }
.editor-mode-toggle .mode-btn:hover:not(.is-active) { background: var(--accent-soft); }

.editor-criteria-list { display: flex; flex-direction: column; gap: var(--sp-2); max-height: 280px;
  overflow-y: auto; overflow-x: hidden; padding: var(--sp-2) var(--sp-3); background: var(--surface);
  border: var(--bw-hair) solid var(--border); border-radius: var(--r-card); font-size: var(--fz-sm); }
.editor-criteria-list label { display: grid; grid-template-columns: 18px minmax(0, 1fr) auto;
  column-gap: var(--sp-2); align-items: start; cursor: pointer; padding: var(--sp-1) 0; }
/* Overrides the generic `.field input` sizing (width:100%/fixed height) —
   without this a checkbox nested in this section's `.field` would stretch
   to a full-width, input-height box. */
.editor-criteria-list input[type=checkbox] { width: auto; height: auto; margin: 3px 0 0 0; }
.editor-criteria-text { word-wrap: break-word; overflow-wrap: anywhere; line-height: var(--lh-body); }
.editor-criteria-text.is-corrupted { font-style: italic; color: var(--warn); }
.editor-criteria-cat { font-size: var(--fz-micro); color: var(--muted); max-width: 140px; padding: 2px var(--sp-2);
  background: var(--accent-soft); border-radius: var(--r-chip); align-self: start; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; }

.editor-added-criteria { display: flex; flex-direction: column; gap: var(--sp-2); margin-top: var(--sp-2); }
.editor-added-criterion { display: grid; grid-template-columns: 1fr 2fr 60px 32px; gap: var(--sp-2);
  align-items: start; }
.editor-added-criterion textarea { resize: vertical; min-height: 36px; }
/* Узкий редактор (телефон): критерий — название, вес и крестик в строку,
   описание во всю ширину под ними; метка категории базового критерия —
   под его текстом, а не отдельной колонкой, сжимавшей текст до слова. */
@container (max-width: 520px) {
  .editor-added-criterion { grid-template-columns: minmax(0, 1fr) 60px 32px; }
  .editor-added-criterion > textarea { grid-column: 1 / -1; grid-row: 2; }
  .editor-criteria-list label { grid-template-columns: 18px minmax(0, 1fr); }
  .editor-criteria-cat { grid-column: 2; justify-self: start; max-width: 100%; }
}
/* Ф3f — «+ Добавить критерий» + «Загрузить анкету» row (the label-for pick
   button is a DS `.btn` on a <label>, so it inherits the secondary look). */
.editor-added-actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; align-items: center; margin-top: var(--sp-2); }
.questionnaire-pick { cursor: pointer; }
.questionnaire-pick.is-disabled { opacity: .6; pointer-events: none; }
.questionnaire-hint { font-size: var(--fz-xs); color: var(--muted); margin: var(--sp-2) 0 0; }
.btn-remove { padding: var(--sp-1) var(--sp-2); background: transparent; color: var(--danger);
  border: var(--bw-hair) solid var(--danger-soft); border-radius: var(--r-card); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; }
.btn-remove:hover { background: var(--danger-soft); }

/* ==== Редактор списка критериев (NL-328/329, openspec template-list-editor) — разделы-плашки со счётчиком «N из M»
   и сворачиванием, как шаг мастера «Что проверяем в звонке» (`Личный кабинет.dc.html:388-449`); блок «Не разобрали»
   — как окно разбора анкеты (:2615-2745). Классы — свои (`checklist-*`, `template-create-*`), классы ДС не
   переопределяются. Узкий редактор (телефон) — @container `.settings-editor`. */
.checklist-editor { display: flex; flex-direction: column; gap: var(--sp-4); }
.checklist-editor > .section-heading { margin-bottom: 0; }
.checklist-editor .field { margin-bottom: 0; }
.field.has-error textarea { border-color: var(--danger); }
.checklist-field-error { font-size: var(--fz-xs); color: var(--danger); margin: var(--sp-1) 0 0; }
.checklist-hint, .checklist-import-hint { font-size: var(--fz-xs); color: var(--muted); margin: 0; }
.checklist-loading, .checklist-import-status { font-size: var(--fz-sm); color: var(--muted); margin: 0; }

/* NL-338: «Оценивается N критериев» над списком старого «Дополнить базовый». */
.checklist-evaluated { margin: 0 0 var(--sp-2); font-size: var(--fz-sm); font-weight: var(--w-semibold);
  color: var(--text); }
.checklist-selection { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-2) var(--sp-3);
  padding: var(--sp-2) var(--sp-3); background: var(--surface); border-radius: var(--r-card); }
.checklist-selection-count { font-size: var(--fz-sm); color: var(--muted); margin-right: auto; white-space: nowrap; }
/* V3: «Выбрать все» / «Снять все» — одна пара, переносится целиком. */
.checklist-selection-buttons { display: flex; gap: var(--sp-1); flex-wrap: nowrap; }
.checklist-edit-area { display: flex; flex-direction: column; gap: var(--sp-4); }

.checklist-sections { display: flex; flex-direction: column; gap: var(--sp-3); }
.checklist-section { background: var(--card); border: var(--bw-hair) solid var(--border); border-radius: var(--r-card);
  box-shadow: var(--elev); }
/* V4: заголовок раздела — фон поверхности и нижняя граница (макет :419); у свёрнутого границы нет. */
.checklist-section-head { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-2); padding: 12px 14px;
  background: var(--surface); border-bottom: var(--bw-hair) solid var(--border);
  border-radius: var(--r-card) var(--r-card) 0 0; }
.checklist-section.is-collapsed .checklist-section-head { border-bottom: 0; border-radius: var(--r-card); }
.checklist-section-title { flex: 1 1 220px; min-width: 0; }
.checklist-section-name { flex: 1 1 220px; min-width: 0; margin: 0; font: var(--w-semibold) var(--fz-ui) var(--font-ui);
  color: var(--text); overflow-wrap: anywhere; }
.checklist-section-count { font-size: var(--fz-xs); color: var(--muted); white-space: nowrap; }
.checklist-section-actions { display: flex; align-items: center; gap: 2px; margin-left: auto; }
.checklist-section-confirm { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3); background: var(--danger-soft); }
.checklist-section-confirm p { flex: 1 1 220px; margin: 0; font-size: var(--fz-sm); overflow-wrap: anywhere; }
.checklist-section-body { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-2);
  padding: 0 14px 14px; }
.checklist-items { align-self: stretch; list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.checklist-item { display: grid; grid-template-columns: 16px 20px minmax(0, 1fr) 72px auto; gap: var(--sp-2);
  align-items: start; padding: var(--sp-2) 0; border-bottom: var(--bw-hair) solid var(--border); }
.checklist-item.is-readonly { grid-template-columns: minmax(0, 1fr) auto; }
.checklist-item.is-off .checklist-item-text textarea, .checklist-item.is-off .checklist-item-points { color: var(--muted); }
.checklist-item.is-dragging { opacity: .5; }
.checklist-drag-handle { display: inline-flex; padding-top: 10px; color: var(--muted); cursor: grab; }
.checklist-item-text textarea { min-height: 40px; max-height: 240px; resize: vertical; field-sizing: content; }
.checklist-item-actions { display: flex; gap: 2px; padding-top: 4px; }
.checklist-item-points { font-size: var(--fz-sm); color: var(--muted); white-space: nowrap; padding-top: 2px; }
.checklist-item-errors { grid-column: 3 / -1; display: flex; flex-direction: column; }
.checklist-item-errors .checklist-field-error { margin-top: 0; }

.checklist-list-actions { display: flex; }
.checklist-numbers-out { font-size: var(--fz-sm); color: var(--warn); margin: 0; }
.checklist-size { font-size: var(--fz-sm); color: var(--muted); margin: 0; }
.checklist-actions { flex-wrap: wrap; }
.checklist-save-hint { flex-basis: 100%; font-size: var(--fz-xs); color: var(--muted); margin: 0; }
.checklist-size.is-over { color: var(--danger); font-weight: var(--w-semibold); }
.checklist-import { display: flex; flex-direction: column; gap: var(--sp-2); }
.checklist-import-row { display: flex; align-items: center; gap: var(--sp-2); }
.checklist-skipped { padding: var(--sp-3); background: var(--warn-soft); border: var(--bw-hair) solid var(--warn);
  border-radius: var(--r-card); }
.checklist-skipped h3 { margin: 0 0 var(--sp-2); font-size: var(--fz-ui); }
.checklist-skipped ul { margin: 0 0 var(--sp-3) 18px; padding: 0; font-size: var(--fz-sm); overflow-wrap: anywhere; }
.checklist-own { display: flex; flex-direction: column; gap: var(--sp-2); }
.checklist-own-title { margin: 0; font-size: var(--fz-ui); }
.checklist-advanced-panel { margin-top: var(--sp-2); }
@container (max-width: 560px) {
  .checklist-item { grid-template-columns: 16px 20px minmax(0, 1fr); }
  .checklist-item-score, .checklist-item-actions, .checklist-item-errors { grid-column: 3; }
}

/* «Создать шаблон» — три входа (design п.4). */
.template-create { display: flex; flex-direction: column; gap: var(--sp-5); }
.template-create > .section-heading { margin-bottom: 0; }
.template-create-option { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-2); }
.template-create-option h3 { margin: 0; font-size: var(--fz-ui); }
.template-create-hint { font-size: var(--fz-sm); color: var(--muted); margin: 0; }
.template-create-sources { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
.template-create-sources li { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-2); }
.template-create-advanced { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-2); }
.settings-viewer .viewer-section { font-weight: var(--w-semibold); margin-top: var(--sp-3); }

.settings-editor-actions { display: flex; gap: var(--sp-2); margin-top: var(--sp-5); }

/* app_cabinet.dart's `/calls/templates` back-link (Task 6) — sits below
   `TemplatesSection`'s own `.templates-section` column (which already owns
   the full-width column + sp-8/sp-6 padding, review round 1); this
   re-declares the same horizontal inset so the button lines up with the
   list above it, without re-adding top padding (the section's own bottom
   padding already provides that gap — same non-doubling shape as
   `.tariff-back` sitting directly inside the already-padded
   `.tariff-view`). Review round 1 dropped this rule's own `max-width:
   720px; margin: 0 auto` the same way `.templates-section`'s own did —
   this class stays in use by every routed section below EXCEPT
   `/calls/reports`, which now carries the neutral `.section-back` instead
   (identical rule, duplicated rather than renaming this one out from
   under its remaining callers — `app_cabinet.dart#_reportsView`'s own
   doc). */
.templates-back, .section-back { max-width: none; margin: 0; padding: 0; }

/* ==== ObjectsSection (Ф6c-3 Task 2) — the `/calls/objects` list + inline
   editor. `.objects-section` rides `.templates-section`'s column/heading/
   hint/list/row/editor rules as-is (both components render `classes:
   'templates-section objects-section'`/`'templates-section'` — same "routed
   full-page column" shape, no visual delta between the two sections) — this
   selector exists only as a future override hook, same reasoning as
   `.settings-row-builtin` existing even before any builtin-property concept
   does. The one genuinely missing piece this section surfaces: cabinet.css's
   `option.select-create-action` rule (506) was never ported when `UploadZone`
   introduced the class (Ф6c-2 Task 5) — added here, token-ized, so both
   selects' trailing «+ Создать шаблон →» option render consistently. ==== */
option.select-create-action { color: var(--accent); font-weight: var(--w-medium);
  border-top: var(--bw-hair) solid var(--border); }

/* «Объекты» по макету `?go=objects` (сверка 2026-10-01, В16–В18): заголовок
   с лидом в две строки и контурной «Добавить объект» справа; объект —
   секция с шапкой-полосой (фон --surface, рамка, 11px 14px), название
   крупно с многоточием, чип отрасли, тихие «Изменить» / «Удалить объект». */
.objects-head { display: flex; align-items: flex-start; gap: var(--sp-4); flex-wrap: wrap; margin-bottom: var(--sp-5); }
.objects-head-text { display: flex; flex-direction: column; gap: 6px; flex: 1 1 260px; min-width: 0; }
.objects-lead { margin: 0; max-width: 640px; font-size: var(--screen-note-size); line-height: var(--lh-body); color: var(--muted); }
.objects-lead-strong { font-weight: var(--w-medium); color: var(--text); }
.objects-list { display: flex; flex-direction: column; gap: var(--sp-4); }
.object-section { display: flex; flex-direction: column; gap: 14px; }
.object-head { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; padding: 11px 14px;
  border: var(--bw-hair) solid var(--border); border-radius: var(--r-card); background: var(--surface); }
.object-head-name { flex: 1 1 200px; min-width: 0; font: var(--w-bold) 22px var(--font-ui); color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /* макет: --screen-h3-size, 22px */
/* Телефон: название — своей строкой, чип и действия под ним (как макет на 390). */
@media (max-width: 720px) { .object-head-name { flex-basis: 100%; } }
/* NL-156: «Сводный по сотрудникам» под шапкой объекта — подпись и select в строку (как «Период» папки), под ними
   подсказка к выбранному и цена приглушённым текстом; ошибка сохранения — в этой же строке объекта. */
.object-schedule { display: flex; flex-direction: column; gap: 6px; padding: 0 14px; }
.object-schedule-field { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.object-schedule-label { font: 500 14px var(--font-ui); color: var(--text); }
.object-schedule-note { display: flex; flex-direction: column; gap: 2px; font-size: var(--fz-sm); color: var(--muted); }

/* ==== IntegrationsSection (Ф6d-2 Task 3) — the `/calls/integrations`
   account list: validity dot, per-account settings collapse, add-account
   form, delete. `.integrations-section` rides `.templates-section`'s
   column/heading/hint the same way `.objects-section` does — this selector,
   like that one, exists only as a future override hook. Ported (re-
   tokenized — cabinet.js's `buildAccountCard` used inline `style.cssText`
   throughout, no class names at all beyond `.acc-toggle-icon`/`.acc-body`/
   `.acc-objects-list`/`.acc-calls-box`, none of which are CSS-styled here
   since Task 3 doesn't render that content — see the component's own file
   doc) from `frontend/public/calls/cabinet.js`/`index.html`'s `allokaSection`
   markup (429-483). The add-account form reuses `.settings-editor`/`.field`/
   `.settings-editor-actions` as-is (see `IntegrationsSection._addForm`'s
   doc) — only the account list/card/inline-settings-form chrome below is
   new. ==== */
.acc-list { display: flex; flex-direction: column; gap: var(--sp-2); margin-bottom: var(--sp-3); }
.acc-card { background: var(--card); border: var(--bw-hair) solid var(--border); border-radius: var(--r-card);
  overflow: hidden; }
.acc-header { display: flex; align-items: center; gap: var(--sp-2); padding: var(--sp-3) var(--sp-4);
  background: var(--surface); flex-wrap: wrap; }
.acc-header .btn { height: var(--h-btn-sm); padding: 0 var(--sp-3); font-size: var(--fz-xs); }
.acc-label { flex: 1 1 8em; min-width: 0; font: var(--w-semibold) 16px var(--font-ui); color: var(--text); /* как название карточки источника в макете (--screen-card-title, 16px) */
  overflow-wrap: anywhere; }

.acc-settings-form { display: flex; flex-direction: column; gap: var(--sp-2); padding: var(--sp-3) var(--sp-4);
  border-top: var(--bw-hair) solid var(--border); background: var(--surface); }
.acc-settings-form input { width: 100%; height: var(--h-input); padding: 0 var(--sp-4); font-size: var(--fz-body);
  font-family: inherit; border: var(--bw-emph) solid var(--border); border-radius: var(--r-card);
  background: var(--bg); color: var(--text); transition: border-color var(--dur) var(--ease); }
.acc-settings-form input:focus { outline: none; border-color: var(--accent); box-shadow: var(--focus-ring); }
.acc-settings-actions { display: flex; gap: var(--sp-2); }

/* ==== ApiKeysSection — «API-ключи» under the Alloka accounts on
   /calls/integrations. Key rows reuse the `.acc-list`/`.acc-card`/
   `.acc-header`/`.acc-label` chrome above; the create form reuses
   `.settings-editor`/`.field`. Only the meta line, the rights checkbox group
   (kept OUT of `.field`, whose `input` rule would stretch the checkboxes)
   and the one-time token reveal are new. ==== */
.api-keys-section { margin-top: var(--sp-8); }
.api-key-meta { margin: 0; padding: var(--sp-2) var(--sp-4) var(--sp-3); font-size: var(--fz-xs);
  color: var(--muted); overflow-wrap: anywhere; }
.api-key-row.is-revoked .acc-label { color: var(--muted); text-decoration: line-through; }
.api-key-form { margin-top: var(--sp-5); }
.api-key-scopes { display: flex; flex-direction: column; gap: var(--sp-3); margin-bottom: var(--sp-5); }
.api-key-scopes-label { margin: 0; font: var(--w-semibold) var(--fz-sm) var(--font-ui); color: var(--text); }
.api-key-scope-title { display: block; font-weight: var(--w-semibold); }
.api-key-scope-desc { display: block; font-size: var(--fz-xs); color: var(--muted); }
.api-key-created { display: flex; flex-direction: column; gap: var(--sp-3); margin-bottom: var(--sp-5); }
.api-key-created .field { margin-bottom: 0; }
.api-key-created .api-key-token { font-family: var(--font-num); }


/* ==== AllokaImport (Ф6d-2 Task 4) — the per-account accordion body
   (`.acc-body`, mounted while `AccountCard._bodyOpen`): the `▶`/`▼` header
   toggle, the object tree, the filters row, the calls-preview list and the
   import progress bar. Re-tokenized the same way Task 3's account-card
   chrome was — cabinet.js's own `buildAccountCard`/`loadObjects` used inline
   `style.cssText` throughout, no stylesheet rules at all. `.settings-row`/
   `.settings-row-info`/`.settings-row-name`/`.settings-row-meta` (used by
   the calls-preview rows) already exist (Ф6c-2) and are reused as-is, not
   redefined here. ==== */
/* Раскрытие импорта — шеврон ДС (сверка 2026-10-01, В28), не ▶/▼ текстом. */
.acc-toggle-icon { appearance: none; background: none; border: none; padding: 0; margin: 0; font: inherit;
  color: var(--muted); cursor: pointer; user-select: none; display: inline-flex; align-items: center;
  justify-content: center; width: 28px; height: 28px; border-radius: var(--r-card); flex-shrink: 0; }
.acc-toggle-icon:hover { color: var(--text); background: var(--card); }
.acc-toggle-icon:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); }

.acc-body { padding: var(--sp-4); border-top: var(--bw-hair) solid var(--border); }
.acc-section-label { font-family: var(--eyebrow-font); font-size: var(--fz-micro); font-weight: var(--w-semibold); color: var(--muted);
  text-transform: uppercase; letter-spacing: .05em; margin-bottom: var(--sp-2); }
.acc-section-label:not(:first-child) { margin-top: var(--sp-4); }

.acc-objects-list { margin-bottom: var(--sp-2); font-size: var(--fz-sm); }
.acc-objects-list.muted { color: var(--muted); }
.acc-objects-list.error { color: var(--danger); }
.acc-object-row { display: flex; align-items: center; gap: var(--sp-2); padding: var(--sp-2) 0; flex-wrap: wrap;
  border-bottom: var(--bw-hair) solid var(--accent-soft); }
.acc-object-row:last-child { border-bottom: none; }
.acc-object-label { flex: 1 1 10em; min-width: 0; font-size: var(--fz-sm); white-space: normal; overflow-wrap: anywhere; }
.acc-object-template { font-size: var(--fz-xs); padding: 3px var(--sp-2); border: var(--bw-hair) solid var(--border);
  border-radius: var(--r-chip); max-width: 160px; background: var(--bg); color: var(--text); }

.acc-filters { display: flex; gap: var(--sp-3); flex-wrap: wrap; align-items: flex-end; margin-bottom: var(--sp-3); }
.acc-filter-field { flex: 1; min-width: 120px; }
.acc-filter-field label { display: block; font-size: var(--fz-xs); font-weight: var(--w-medium); color: var(--muted);
  margin-bottom: 4px; }
.acc-filter-field input, .acc-filter-field select { width: 100%; height: var(--h-input); padding: 0 var(--sp-3);
  font-size: var(--fz-sm); font-family: inherit; border: var(--bw-emph) solid var(--border); border-radius: var(--r-card);
  background: var(--bg); color: var(--text); box-sizing: border-box; }
.acc-filter-field input:focus, .acc-filter-field select:focus { outline: none; border-color: var(--accent);
  box-shadow: var(--focus-ring); }

.acc-calls-box { margin-top: var(--sp-2); }
.acc-calls-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--sp-2); }
.acc-calls-count { font-size: var(--fz-sm); font-weight: var(--w-semibold); }
.acc-calls-actions { display: flex; gap: var(--sp-2); }
.acc-calls-actions .btn { height: var(--h-btn-sm); padding: 0 var(--sp-3); font-size: var(--fz-xs); }
.acc-calls-checkboxes { max-height: 320px; overflow-y: auto; border: var(--bw-hair) solid var(--border);
  border-radius: var(--r-card); }
.acc-calls-footer { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: var(--sp-2);
  margin-top: var(--sp-3); }
.acc-selected-count { font-size: var(--fz-sm); color: var(--muted); }

.acc-progress { margin-top: var(--sp-4); }
.acc-progress-head { display: flex; justify-content: space-between; font-size: var(--fz-xs); color: var(--muted);
  margin-bottom: 4px; }
.acc-progress-track { height: 6px; border-radius: var(--r-chip); background: var(--accent-soft); overflow: hidden; }
.acc-progress-bar { height: 100%; background: var(--accent); transition: width var(--dur) var(--ease); }


/* ==== AdminSection (Ф6d-2 Task 5) — read-only «Клиенты» table. Reuses
   ReportsTable's `.calls-table`/`.calls-table-wrap`/`.calls-error-banner`/
   `.hint` chrome verbatim (index.html's own admin markup already shares
   those exact classes) — only the plan/sku badges below are new. The
   `.templates-section.admin-section` compound selector (below) deliberately
   overrides the shared 720px max-width regardless of stylesheet order,
   matching index.html's own wider `max-width:960px` admin panel wrapper. */
.templates-section.admin-section { max-width: 960px; }

.admin-plan-badge { display: inline-flex; align-items: center; height: var(--h-chip); padding: 0 var(--sp-3);
  border-radius: var(--r-chip); background: var(--accent-soft); color: var(--accent); font-size: var(--fz-xs);
  font-weight: var(--w-medium); }

.admin-pack-row { display: flex; align-items: center; gap: var(--sp-1); font-size: var(--fz-sm); padding: 2px 0; }
.admin-sku-badge { display: inline-flex; align-items: center; padding: 0 var(--sp-2); border-radius: var(--r-chip);
  font-size: var(--fz-micro); font-weight: var(--w-semibold); }
.admin-sku-badge.is-active { background: var(--success-soft); color: var(--success); }
.admin-sku-badge.is-depleted { background: var(--surface); color: var(--faint); }
.admin-sku-badge.is-expired { background: var(--warn-soft); color: var(--warn); }

/* ==== «Сотрудники» (`/calls/employees`) — по макету `Личный кабинет.dc.html`,
   `?go=emps` (разметка ~L1518–1590): h1 + две строки подзаголовка, одна
   карточка «Состав команды» с плоскими строками по группам шаблонов (NL-320:
   ролей нет). Числа без токена (18px паддинг карточки, 230px селект, 170px
   мин. ширина имени) — из макета, как есть. ==== */
.employees-section { display: flex; flex-direction: column; gap: var(--sp-5); }
.employees-lead { font-weight: 400; color: var(--muted); max-width: 660px; text-wrap: pretty; }
.employees-lead-strong { font-weight: 500; color: var(--text); }

.team-card { display: flex; flex-direction: column; gap: var(--sp-3); padding: 18px; }
.team-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.team-title { flex: 1; min-width: 180px; font: 700 16px var(--font-ui); color: var(--text); }
/* minmax(0, 1fr): без него колонка грида растягивается до ширины имени в
   одну строку (nowrap) и строка уезжает за край на 390px вместо переноса. */
.team-list { display: grid; grid-template-columns: minmax(0, 1fr); align-content: start; }
.team-group-title { padding: 18px 0 4px; font: 600 17px var(--font-ui); color: var(--text);
  overflow-wrap: anywhere; }
.team-empty { padding: var(--sp-2) 0 var(--sp-1); font: 400 13px/1.5 var(--font-ui); color: var(--muted); }
.team-list > .team-empty { border-top: var(--bw-hair) solid var(--border); }
.team-list > .team-empty:first-child { border-top: 0; padding-top: 0; }

.team-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 10px 0;
  border-top: var(--bw-hair) solid var(--border); }
.team-row-main { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 170px; }
.team-row-name-line { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
.team-row-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font: 500 14px var(--font-ui); color: var(--text); }
.team-row-name-line > .chip { flex: none; }
.team-row-meta { font: 400 12px var(--font-ui); color: var(--muted); }
.team-row.is-archived .team-row-name { color: var(--muted); }
.team-row-template { display: grid; gap: 4px; flex: none; width: 230px; max-width: 100%; }
.team-row-confirm { display: flex; flex: 0 0 120px; justify-content: center; }
@media (max-width: 640px) {
  .team-row-confirm { flex: none; }
  .team-row-confirm:empty { display: none; }
}
.team-rename { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-1); min-width: 200px; }
.team-rename-hint { font: 400 12px/1.4 var(--font-ui); color: var(--muted); }
/* Ошибка действия — в своей строке, во всю ширину под контролами. */
.team-row-error { flex: 1 1 100%; }
/* «влит в <имя>» на месте «Вернуть из архива» у влитого в архиве. */
.team-row-merged { font: 400 12px/1.4 var(--font-ui); color: var(--muted); overflow-wrap: anywhere; }
.team-title:focus { outline: none; }
.team-title:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.team-foot { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding-top: 10px;
  border-top: var(--bw-hair) solid var(--border); }
.team-foot-note { flex: 1; min-width: 190px; font: 400 12px/1.45 var(--font-ui); color: var(--muted); }
.team-retry { display: flex; }
/* NL-324: имя в строке — ссылка на папку сотрудника; вид — как у текста имени. */
a.team-row-link { text-decoration: none; }
a.team-row-link:hover { color: var(--accent-deep); text-decoration: underline; }
a.team-row-link:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); border-radius: 2px; }
/* NL-346: «Разборы и сводные» под строкой сотрудника — ссылкой цвета действия. */
a.team-row-folder-link { align-self: flex-start; font: 500 12px var(--font-ui); color: var(--accent-deep); text-decoration: none; }
a.team-row-folder-link:hover { text-decoration: underline; }
a.team-row-folder-link:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); border-radius: 2px; }

/* ==== Папка сотрудника (NL-324, cabinet-employee-folder) — /calls/employees/:id ====
   Макета страницы нет (design §3): шапка — как у разделов кабинета, вкладки повторяют вид сегмента ДС
   (`.segmented`), но несут роли вкладок WAI-ARIA, поэтому классы свои. */
.employee-folder { display: flex; flex-direction: column; gap: var(--sp-5); min-width: 0; }
.folder-back { align-self: flex-start; font: 500 14px var(--font-ui); color: var(--accent-deep); text-decoration: none; }
.folder-back:hover { text-decoration: underline; }
.folder-back:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); border-radius: 2px; }
.folder-title-row { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; min-width: 0; }
.folder-title { min-width: 0; overflow-wrap: anywhere; }
.folder-meta { font-weight: 400; color: var(--muted); max-width: none; overflow-wrap: anywhere; }
.folder-tabs { display: flex; gap: var(--sp-1); padding: var(--sp-1); align-self: flex-start; max-width: 100%;
  border: var(--bw-hair) solid var(--border); border-radius: var(--r-card); background: var(--surface); }
.folder-tab { min-height: 38px; padding: 4px var(--sp-4); border: none; border-radius: calc(var(--r-card) - 3px);
  cursor: pointer; font: var(--w-semibold) var(--fz-sm) var(--font-ui); background: transparent; color: var(--muted);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease); }
.folder-tab:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); }
.folder-tab.is-active { background: var(--card); color: var(--text); box-shadow: var(--elev); }
.folder-panel { display: flex; flex-direction: column; gap: var(--sp-4); min-width: 0; }
.folder-panel[hidden] { display: none; }
.folder-panel:focus { outline: none; }
.folder-panel:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); }
/* NL-346: период «Сводных» папки — тот же выбор, что в окне сводного (.summary-wizard-period): подпись, рядом «В периоде
   N разборов», под ними пресеты и календарь. */
.folder-period { display: flex; flex-direction: column; gap: var(--sp-2); }
.folder-period-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.folder-period-label { font: 600 13px var(--sans); color: var(--text); }
.folder-period-hint { font: 500 12px var(--mono); color: var(--faint-text); }
.folder-summary-failed { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-2); }
.folder-status { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-3); }
.folder-status-text { margin: 0; font: 400 14px/1.5 var(--font-ui); color: var(--muted); }
.folder-status-actions { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
/* Карточка разбора: имя в «Сотрудник» — ссылка на папку. */
a.call-detail-agent-link { text-decoration: underline; text-underline-offset: 2px; }
a.call-detail-agent-link:hover { color: var(--accent-deep); }
a.call-detail-agent-link:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); border-radius: 2px; }

/* ==== showcase (visual-check surface, Ф2 T6) ==== */
.showcase-theme { background: var(--bg); color: var(--text); padding: var(--sp-6);
  border-radius: var(--r-card-lg); margin-bottom: var(--sp-8); }
.showcase-group { margin-bottom: var(--sp-6); }
.showcase-group h3 { margin: 0 0 var(--sp-3); font: var(--w-semibold) var(--fz-title) var(--font-ui); }
.showcase-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3); }
/* DS Modal's `.modal-backdrop`/`.modal-frame` are `position: fixed` (viewport-
   relative, by design — see web_ds's own modal.dart doc) which would blow out
   of the showcase page entirely if rendered inline. `transform` on this
   wrapper establishes a new containing block for `position: fixed`
   descendants (a standard CSS trick — any transform value works, even the
   identity one below), so the modal renders scoped to this bounded, clipped
   box instead of the real viewport — a static "framed" preview, landing-
   redesign tasks.md §1.7. */
.showcase-modal-frame { position: relative; height: 420px; overflow: hidden; transform: translateZ(0);
  border: var(--bw-hair) solid var(--border); border-radius: var(--r-card-lg); }
/* Toast — тоже `position: fixed` (нижняя середина окна, by design, см.
   toast.dart). Тот же приём с `transform`, что у рамки модалки выше: обёртка
   становится точкой отсчёта, поэтому два экземпляра — светлый и тёмный —
   остаются каждый в своей половине витрины, а не ложатся друг на друга у
   настоящего низа окна. Полная ширина нужна, чтобы уведомление пользовалось
   собственным `max-width`, а не шириной, оставшейся во флекс-строке. */
.showcase-toast-frame { position: relative; width: 100%; height: 96px; overflow: hidden;
  transform: translateZ(0); }
/* Виджет первых шагов — выпадающий список: его панель `position: absolute` и
   без запаса высоты накрывает следующую группу витрины. В кабинете виджет
   живёт в шапке и перекрывает содержимое по назначению, так что запас нужен
   только здесь. */
.showcase-dropdown-room { min-height: 320px; }
/* Combobox витрины — ширина поля «Кого оцениваем» формы загрузки (макет: 320px). */
.showcase-combobox { width: 320px; max-width: 100%; }
.showcase-drp { flex: 1 1 100%; min-width: 0; }
/* Password requirements checklist preview (auth-redesign tasks.md §1.2) —
   showcase-only layout for a StatusIcon(kind/tone) row; the real card
   (SU3, `auth_page.dart`) reuses the same `.status-icon` classes but its
   own row wrapper, not this one. */
.showcase-checklist { display: flex; flex-direction: column; gap: var(--sp-1); }
.showcase-checklist-row { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fz-xs); color: var(--muted); }

/* ==== CallDetail (Ф3e) — the per-call detail modal opened from a «Мои
   разборы» row name. The modal frame / SegmentedControl / VoiceScale /
   BandBadge chrome is DS (`web_ds` components.css, @import'd at the top of
   this file); only the two panes' own layout lives here. ==== */

/* The row name reads as plain text but is a real <button> when a detail
   modal is available (has an evaluation or voice) — link-styled, left-
   aligned, keyboard-focusable. */
.call-name-btn { background: none; border: none; padding: 0; margin: 0; text-align: left;
  font: inherit; color: var(--text); cursor: pointer; }
.call-name-btn:hover { color: var(--accent); text-decoration: underline; }
.call-name-btn:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); border-radius: var(--r-chip); }

.call-detail-pane { display: flex; flex-direction: column; gap: var(--sp-4); margin-top: var(--sp-5); }
.call-detail-score { display: flex; flex-direction: column; gap: var(--sp-1); align-items: flex-start; }
.call-detail-score-value { font-family: var(--font-num); font-size: var(--fz-num-lg); font-weight: var(--w-semibold);
  color: var(--accent); }
.call-detail-score-caption { font-size: var(--fz-sm); color: var(--muted); }
.call-detail-empty-line { font-size: var(--fz-sm); color: var(--faint); margin: 0; }

/* ==== Call-detail checklist card (redesign-conformance, этап 7 —
   `redesign-call-detail-by-prototype`), `Личный кабинет.dc.html`'s
   checklist modal on SectionScore/CriterionRow. `.call-detail-modal` is
   the DS Modal's own `extraClass` hook (`web_ds/lib/src/modal.dart`) —
   the mockup's checklist modal is a wider `size="sm"` (520px) than the
   package's base `.modal` (480px); every other modal caller is untouched. */
.call-detail-modal { max-width: 520px; max-height: 90vh; }
/* Ровный шаг между блоками тела (плашка, «Сотрудник», итог, вкладки,
   содержимое вкладки), как у карточки макета. Отступом, а не flex-колонкой:
   во флексе панель вкладки получала определённую высоту, и строки
   прокручиваемого списка разделов сжимались до заголовков. */
.call-detail-modal .modal-body > * + * { margin-top: var(--sp-4); }
/* Телефон: карточка — полноэкранный лист без полей, скругления и тени
   (`Modal size="full"` макета на 390, В10). Рамку окна без отступов
   получает только окно карточки — прочие модалки остаются окном. */
@media (max-width: 480px) {
  .modal-frame:has(> .call-detail-modal) { padding: 0; align-items: stretch; }
  .call-detail-modal { max-width: none; max-height: 100dvh; height: 100dvh; border-radius: 0; box-shadow: none; }
}

.call-detail-audio-note { padding: var(--sp-3) var(--sp-4); border-radius: var(--r-card);
  background: var(--surface); border: var(--bw-hair) solid var(--border); font-size: var(--fz-sm); color: var(--muted); }

/* NL-319 «Сменить роль»: выбор роли в карточке — та же плашка, что у
   «Запись не сохранилась…», поле — общие `.field`/`.field select`; кнопки
   в ряд справа налево не переносятся на телефоне. */
.call-reevaluate { padding: var(--sp-4); border-radius: var(--r-card);
  background: var(--surface); border: var(--bw-hair) solid var(--border); }
.call-reevaluate .field { margin-bottom: var(--sp-3); }
.call-reevaluate-actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

.call-detail-agent-row { display: flex; align-items: center; gap: var(--sp-3); }
.call-detail-agent-label { font-family: var(--eyebrow-font); font-size: var(--fz-micro); font-weight: var(--w-semibold);
  letter-spacing: 0.05em; text-transform: uppercase; color: var(--muted); }
.call-detail-agent-name { font-size: var(--fz-ui); font-weight: var(--w-semibold); color: var(--text); }
/* NL-323: сотрудник выбран при загрузке — пометка после имени. */
.call-detail-agent-note { font-size: var(--fz-sm); color: var(--muted); }
/* NL-339 «Смена сотрудника разбора»: «Изменить»/«Выбрать» — у правого края строки, как в макете (имя `flex:1`); на
   телефоне строка переносится. «не определён» — приглушённо; «Закреплено…» — зелёная строка под ней (макет: 12px,
   `--success-deep`). */
.call-detail-agent-row:has(> #call-employee-change-open) { flex-wrap: wrap; row-gap: var(--sp-1); }
.call-detail-agent-row > #call-employee-change-open { margin-left: auto; }
.call-detail-agent-row .call-detail-agent-name { min-width: 0; overflow-wrap: anywhere; }
.call-detail-agent-unknown { font-weight: var(--w-regular); color: var(--muted); }
.call-detail-employee-assigned { margin: 0; font-size: var(--fz-xs); color: var(--success-deep); }
/* Дата записи (NL-325): та же строка, что «Сотрудник»; «Изменить» — сразу за датой. На телефоне переносится только
   сама дата, подпись остаётся в одну строку. */
.call-detail-date-row .call-detail-agent-label { white-space: nowrap; }
.call-detail-date-value { font-size: var(--fz-ui); color: var(--text); min-width: 0; }
/* Шаблон оценки (NL-320): та же строка; длинное имя шаблона переносится, подпись — нет. */
.call-detail-template-row .call-detail-agent-label { white-space: nowrap; }
.call-detail-template-value { font-size: var(--fz-ui); color: var(--text); min-width: 0; overflow-wrap: anywhere; }
/* NL-331: оценён не по шаблону сотрудника — его шаблон рядом, переносом на свою строку на узкой карточке. */
.call-detail-template-row { flex-wrap: wrap; row-gap: var(--sp-1); }
.call-detail-template-employee { font-size: var(--fz-sm); color: var(--warn-deep); min-width: 0; overflow-wrap: anywhere; }

.call-detail-summary { display: flex; flex-direction: column; gap: var(--sp-2); padding: var(--sp-4) var(--sp-5);
  border-radius: var(--r-card); background: var(--surface); border: var(--bw-hair) solid var(--border); }
.call-detail-summary-caption { font-family: var(--eyebrow-font); font-size: var(--fz-micro); font-weight: var(--w-semibold);
  letter-spacing: 0.05em; text-transform: uppercase; color: var(--muted); }
.call-detail-summary-row { display: flex; align-items: baseline; gap: var(--sp-2); flex-wrap: wrap; }
/* Mockup L2225-2230: "Выполнено"/"из"/"·" 400 15px, N/M 600 28px, percent
   600 22px (review round 1) — 28px/22px have no matching token (closest are
   --fz-num-md 24px / --fz-num-lg 34px, both off), so these two are literal
   px, same "no token, so a raw number" stance `.modal-title`'s 18px above
   documents. --fz-body already IS 15px, so the word rule needed no change. */
.call-detail-summary-word { font-family: var(--font-ui); font-size: var(--fz-body); color: var(--muted); }
.call-detail-summary-num { font-family: var(--font-num); font-size: 28px; font-weight: var(--w-semibold);
  line-height: 1; color: var(--text); }
.call-detail-summary-percent { font-family: var(--font-num); font-size: 22px; font-weight: var(--w-semibold);
  line-height: 1; }
.call-detail-summary-figures { display: inline-flex; align-items: baseline; gap: var(--sp-2); white-space: nowrap; }
.call-detail-summary-na { font-size: var(--fz-body); color: var(--faint-text, var(--muted)); }

.call-detail-section { border: var(--bw-hair) solid var(--border); border-radius: var(--r-card); background: var(--card);
  overflow: hidden; container-type: inline-size; }
.call-detail-section + .call-detail-section { margin-top: var(--sp-2); }
.call-detail-section-body { border-top: var(--bw-hair) solid var(--border); padding: var(--sp-2);
  display: flex; flex-direction: column; gap: 2px; }

.call-detail-transcript { max-height: 320px; overflow-y: auto; display: flex; flex-direction: column; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4); border-radius: var(--r-card); background: var(--card);
  border: var(--bw-hair) solid var(--border); }
/* `white-space: pre-line` (review round 1): a paragraph can carry SINGLE
   `\n`s the split-on-blank-lines step (buildTranscriptPane) doesn't touch —
   normal white-space collapses those into one line; pre-line preserves them
   as line breaks while still collapsing runs of spaces/tabs. */
.call-detail-transcript p { margin: 0; font-size: var(--fz-sm); line-height: 1.5; color: var(--text);
  white-space: pre-line; }

/* «Голос и подача» pane — mockup order (В11): a two-line lead (muted, then
   text 500), a hairline-boxed stack of VoiceScale rows (the design's
   `border + padding:6px 14px`), then the notes as a grey plaque. */
.voice-pane { gap: var(--sp-3); }
.voice-lead { display: flex; flex-direction: column; gap: 3px; font-size: var(--fz-sm); line-height: 1.5; }
.voice-lead-muted { color: var(--muted); }
.voice-lead-strong { color: var(--text); font-weight: var(--w-medium); }
.voice-rows { border: var(--bw-hair) solid var(--border); border-radius: var(--r-card); background: var(--card);
  overflow: hidden; padding: 6px 14px; }
.voice-rows > .voice-scale + .voice-scale { border-top: var(--bw-hair) solid var(--border); }
.voice-notes { padding: var(--sp-3) 14px; border-radius: var(--r-card); background: var(--surface);
  border: var(--bw-hair) solid var(--border); font-size: var(--fz-sm); line-height: 1.55; color: var(--text);
  text-wrap: pretty; }

/* ==== New-user helper links (Ф3d, cabinet-first-run brief §2) — a thin line
   under the upload block: «Как это работает» · «Посмотреть пример отчёта».
   Deliberately quiet — the one clear action is the upload zone above.
   Review round 2: `justify-content: center` matched the old centered
   heading/card above it — now that both are left-aligned (see
   `.upload-section-title`'s own comment), centering just these two links
   alone would be its own new mismatch. ==== */
.cabinet-help-links { display: flex; gap: var(--sp-2); align-items: center; justify-content: flex-start;
  margin-top: var(--sp-4); font-size: var(--fz-sm); }
.cabinet-help-links a { color: var(--accent); text-decoration: none; }
.cabinet-help-links a:hover { text-decoration: underline; }
.cabinet-help-links .help-links-sep { color: var(--faint); }

/* industry-once-scoped-templates: отступы карточки загрузки — подпись шаблона не липнет к зоне
   перетаскивания, предупреждение «Сначала создайте шаблон» — к кнопке «Разобрать». */
.upload-template-field { margin-top: var(--sp-4); }
/* upload-employee-combobox: «Кого оцениваем» — селект макета шириной не больше 320px (окно «Загрузить звонок»:
   `max-width:320px` у обёртки Select); подпись и пояснение — во всю ширину. Только раскладка снаружи DS-поля. */
.upload-employee-field .combobox { max-width: 320px; }
.upload-own-template-notice { margin-bottom: var(--sp-4); }
/* Предупреждение «Выбранного шаблона больше нет» внутри поля «Шаблон»: у DS-notice своего внешнего отступа нет. */
.upload-template-unavailable { margin: var(--sp-2) 0; }
/* NL-330: подпись «Шаблон» и «посмотреть критерии» в одну строку; у подписи свой нижний отступ `.field label`. */
.upload-template-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-2);
  flex-wrap: wrap; }
.upload-template-head .btn { margin-bottom: var(--sp-2); }
.upload-template-required .btn { margin-left: var(--sp-1); }
/* «Спишется N из M» над «Разобрать», причина закрытой кнопки, строка-итог под ней. */
.upload-charge, .upload-submit-reason { font-size: var(--fz-sm); color: var(--muted); margin: 0 0 var(--sp-2); }
.upload-submit-reason { color: var(--danger); }
/* Файлов больше, чем хватит баланса (ревью раунда 1): сказано, не молча. */
.upload-charge.is-short { color: var(--warn-deep); }
.upload-summary { font-size: var(--fz-xs); color: var(--muted); margin: var(--sp-2) 0 0; overflow-wrap: anywhere; }
/* Пункты шаблона — только чтение: окно «посмотреть критерии» формы загрузки и «Посмотреть пункты» экрана первого
   входа (общий просмотр, `components/template_criteria.dart`). */
.template-criteria-list { margin: 0; padding-left: var(--sp-5); display: flex; flex-direction: column; gap: var(--sp-2); }
.template-criteria-text { color: var(--text); }
.template-criteria-category { display: block; font-size: var(--fz-xs); color: var(--muted); }
.template-criteria-prompt { white-space: pre-wrap; overflow-wrap: anywhere; font-size: var(--fz-sm); }
.template-criteria .viewer-loading, .template-criteria .viewer-note { color: var(--muted); font-size: var(--fz-sm); margin: 0; }

/* NL-333: «Добавить сотрудников» — окно шире обычного (список имён), предпросмотр с прокруткой. */
.add-employees-modal { max-width: 560px; }
.add-employees-preview { display: flex; flex-direction: column; gap: var(--sp-2); }
.add-employees-summary { margin: 0; font-size: var(--fz-sm); font-weight: var(--w-semibold); color: var(--text); }
.add-employees-list { margin: 0; padding: 0; list-style: none; max-height: 220px; overflow-y: auto;
  border: var(--bw-hair) solid var(--border); border-radius: var(--r-card); }
.add-employees-row { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
  padding: 6px var(--sp-3); font-size: var(--fz-sm); color: var(--text); }
.add-employees-row + .add-employees-row { border-top: var(--bw-hair) solid var(--border); }
.add-employees-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.add-employees-mark { flex-shrink: 0; font-size: var(--fz-xs); color: var(--muted); }

/* summary-period-and-wording: окно «Собрать сводный отчёт» (мастер `sumCfg` макета — `Modal size="lg"`, 1100px; с
   открытым календарём выше обычного окна): объект и шаблон в ряд, «За какой период» с подписью «В периоде N
   разборов» моно рядом. */
.summary-wizard-modal { max-width: 1100px; max-height: min(900px, 92vh); }
.summary-wizard-fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--sp-3); }
.summary-wizard-period { display: flex; flex-direction: column; gap: var(--sp-2); }
.summary-wizard-period-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.summary-wizard-period-label { font: 600 13px var(--sans); color: var(--text); }
.summary-wizard-hint { font: 500 12px var(--mono); color: var(--faint-text); }
