/* ==========================================================================
   z.css — BatteryService · внутренний интерфейс
   Спокойный плотный стиль shadcn/ui: волосяные границы 1px, радиус 10px,
   без градиентов и крупных теней. Всё локально, без внешних ресурсов.
   Темы: светлая (по умолчанию), тёмная [data-theme="dark"],
   авто [data-theme="auto"] — следует системной настройке,
   и две альтернативные (выбор в «Профиле»): [data-theme="light-alt"]
   и [data-theme="dark-alt"].

   Внимание при правке тёмных деталей: тонирование, привязанное только к
   [data-theme="dark"], в альтернативной тёмной теме НЕ работает — она задаёт
   свои токены, но не наследует правила компонентов. Такие места перечислены
   парами селекторов (см. .z-btn--accent, .z-alert--*, .z-badge--warn).

   Разделы:
     1. Токены тем
     2. База
     3. Утилиты
     4. Лейаут страницы
     5. Переключатель темы
     6. Компоненты
     7. Карточки сервисов и логистики
     8. Админ-каркас
     9. Адаптив
    10. Печать
   ========================================================================== */


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

:root {
  color-scheme: light;

  --z-bg: #f6f5f2;
  --z-surface: #fdfdfc;
  --z-surface-2: #f1efe9;
  /* Фон «оболочки»: боковое меню и верхняя панель. Отдельный токен нужен потому,
     что раньше обе области были на `--z-surface`: в светлых палитрах он белый
     у всех наборов, и при смене схемы меню и шапка не менялись вовсе. Палитры
     переопределяют его на `--z-surface-2` (правило `[data-palette]` после блоков
     палитр), базовые темы оставляют как было — surface. */
  --z-chrome: var(--z-surface);
  --z-fg: #16150f;
  --z-muted-fg: #6f6a61;
  /* Средний тон текста (тёплая светлая): светлее заголовка, темнее
     подписи-подсказки. Умолчание для --z-text-fg. */
  --z-fg-soft: #46443d;
  --z-border: #e5e1d8;
  --z-primary: #1c1b19;
  --z-on-primary: #fbfaf7;
  --z-accent: #8c3f24;
  --z-accent-soft: #f3e7df;
  --z-success: #2f6f4f;
  --z-warning: #a8641c;
  --z-danger: #b23a32;
  --z-ring: #b9a99a;

  /* Чередование строк таблиц: чётные строки заметно отличаются от белой поверхности. */
  --z-row-alt: #f1efe9;
  --z-row-hover: #e7e3d9;

  /* Знак портала: цвет логотипа BatteryPassport (иконка-батарейка и надпись). */
  --z-brand-mark: #0ea5e9;
  --z-brand-name: #0ea5e9;

  /* производные мягкие подложки: бейджи, статусы, кольцо фокуса */
  --z-success-soft: rgba(47, 111, 79, .12);
  --z-warning-soft: rgba(168, 100, 28, .12);
  --z-danger-soft: rgba(178, 58, 50, .12);
  --z-ring-soft: rgba(185, 169, 154, .30);

  /* Подсветка найденных фрагментов в поиске. Отдельный токен, а не --z-accent-soft:
     акцент — это бренд (после перехода на палитру BatteryPassport он голубой),
     а найденный текст должно выделять тёплым оранжевым в любой теме. */
  --z-hit-bg: #fbe0b4;
  --z-hit-fg: #3a2a12;

  /* Поля ввода: отдельная подложка и граница.
     ДЕФЕКТ, который здесь исправляется: поле ввода рисовалось тем же цветом,
     что и поверхность страницы (--z-surface), с границей в один тон от фона
     (--z-border) — поле было видно, но понять, где его край, было нельзя,
     особенно в светлой теме на белой карточке. Теперь у поля своя подложка
     и заметно более контрастная граница. Значение границы выбрано расчётом:
     #8b8f96 даёт 3.25:1 к белой подложке и 3.1:1 к фону страницы (порог
     для нетекстовой границы по WCAG — 3:1); прежнее #cbc4b6 давало 1.73:1. */
  --z-field-bg: #ffffff;
  --z-field-border: #8b8f96;

  --z-radius: 10px;
  --z-radius-sm: 8px;
  --z-shadow: 0 1px 2px rgba(0, 0, 0, .05);
  /* Шрифт интерфейса. Стек взят у HARNESS (DeepSeek Harness, тема интерфейса:
     `--dsw-font-family` в его собранном клиенте) — системный набор, записанный
     одним источником; он же предлагается первым в «Настройках». На Windows стек
     разрешается в Segoe UI, на других системах — в системный шрифт. Значение
     подставляет пользователю оболочка инлайном на <html> (src/Font.php),
     поэтому здесь — только умолчание. */
  --z-font: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --z-mono: "Cascadia Mono", Consolas, Menlo, monospace;

  /* Размер текста: множитель, который сотрудник выбирает в «Настройках»
     (колонка `Users`.`FontSize` — размер основного текста в пикселях; 14px = 1).
     От него считаются ВСЕ размеры шрифта этой системы:
     `font-size: calc(13px * var(--z-text, 1))`. Иначе выбор размера менял бы
     только те строки, которым размер не задан явно, — то есть почти ничего.
     Умолчание 1: страницы без оболочки (вход, админка) выглядят как раньше. */
  --z-text: 1;
  /* Типографика: заголовки и текст — выбор сотрудника в «Настройках»
     (src/Font.php). Разделение ЯРКОСТЬЮ, а не жирностью: заголовок темнее
     и жирнее, текст светлее и обычной жирности, подписи-подсказки — самые
     светлые (их задаёт тема, --z-muted-fg).

     Яркость — доля основного цвета темы (--z-fg) в смеси с фоном (--z-bg):
     100 % — цвет как есть (максимальный контраст), меньше — светлее. Числа
     приходят инлайном на <html>, цвета остаются здесь. Ниже — запасной вид
     для браузеров без color-mix(): уровни темы, тот же смысл. */
  --z-heading-bright: 100%;
  --z-text-bright: 80%;
  --z-heading-fg: var(--z-fg);
  --z-text-fg: var(--z-fg-soft);
  --z-heading-weight: 700;
  --z-text-weight: 400;
}

/* Смешение яркости. Отдельным блоком и только там, где color-mix()
   поддержан: без него объявление стало бы недействительным в момент
   использования и цвет просто унаследовался бы от body — то есть заголовок
   потерял бы свой уровень молча. */
@supports (color: color-mix(in srgb, red, blue)) {
  :root {
    --z-heading-fg: color-mix(in srgb, var(--z-fg) var(--z-heading-bright), var(--z-bg));
    --z-text-fg: color-mix(in srgb, var(--z-fg) var(--z-text-bright), var(--z-bg));
  }
}

/* тёмная тема (явный выбор) */
[data-theme="dark"] {
  color-scheme: dark;

  --z-bg: #020617;
  --z-surface: #0f172a;
  --z-surface-2: #1e293b;
  --z-chrome: var(--z-surface);
  --z-fg: #e5e7eb;
  --z-muted-fg: #94a3b8;
  /* Средний тон текста (тёмная): светлее заголовка, темнее
     подписи-подсказки. Умолчание для --z-text-fg. */
  --z-fg-soft: #b9c0cc;
  --z-border: #1e293b;
  --z-primary: #38bdf8;
  --z-on-primary: #0b1220;
  --z-accent: #38bdf8;
  --z-accent-soft: rgba(56, 189, 248, .12);
  --z-success: #4ade80;
  --z-warning: #fbbf24;
  --z-danger: #fb7185;
  --z-ring: #0ea5e9;

  /* Чередование строк: фон чётной строки из палитры BatteryPassport (#0c1528). */
  --z-row-alt: #0c1528;
  --z-row-hover: #16243d;

  /* Знак портала: цвет логотипа BatteryPassport в тёмной теме. */
  --z-brand-mark: #38bdf8;
  --z-brand-name: #38bdf8;

  --z-success-soft: rgba(77, 163, 117, .16);
  --z-warning-soft: rgba(210, 154, 85, .16);
  --z-danger-soft: rgba(217, 106, 96, .16);
  --z-ring-soft: rgba(107, 97, 82, .55);

  /* Подсветка найденного: тот же тёплый оранжевый, что в светлой теме. */
  --z-hit-bg: rgba(251, 146, 60, .30);
  --z-hit-fg: #fde8cd;

  --z-field-bg: #0b1220;
  --z-field-border: #64748b;
}

/* авто: значения тёмной темы включаются по системной настройке */
:root[data-theme="auto"] {
  color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
  :root[data-theme="auto"] {
    --z-bg: #020617;
    --z-surface: #0f172a;
    --z-surface-2: #1e293b;
    --z-fg: #e5e7eb;
    --z-muted-fg: #94a3b8;
  /* Средний тон текста (тёмная): светлее заголовка, темнее
     подписи-подсказки. Умолчание для --z-text-fg. */
  --z-fg-soft: #b9c0cc;
    --z-border: #1e293b;
    --z-primary: #38bdf8;
    --z-on-primary: #0b1220;
    --z-accent: #38bdf8;
    --z-accent-soft: rgba(56, 189, 248, .12);
    --z-success: #4ade80;
    --z-warning: #fbbf24;
    --z-danger: #fb7185;
    --z-ring: #0ea5e9;

    /* Чередование строк: те же значения, что и в явной тёмной теме. */
    --z-row-alt: #0c1528;
    --z-row-hover: #16243d;

    /* Знак портала: цвет логотипа BatteryPassport в тёмной теме. */
    --z-brand-mark: #38bdf8;
    --z-brand-name: #38bdf8;

    --z-success-soft: rgba(77, 163, 117, .16);
    --z-warning-soft: rgba(210, 154, 85, .16);
    --z-danger-soft: rgba(217, 106, 96, .16);
    --z-ring-soft: rgba(107, 97, 82, .55);

    /* Подсветка найденного: как в явной тёмной теме. */
    --z-hit-bg: rgba(251, 146, 60, .30);
    --z-hit-fg: #fde8cd;

    --z-field-bg: #0b1220;
    --z-field-border: #64748b;
  }
}

/* --------------------------------------------------------------------------
   АЛЬТЕРНАТИВНЫЕ ТЕМЫ (выбор в разделе «Профиль»; колонка `Users`.`Theme`)

   Зачем две пары тем: основной набор — тёплая светлая и синяя тёмная (палитра
   Battery Passport). Альтернативная пара — нейтральные серые оттенки в приёме
   shadcn/ui (его палитры slate и zinc): они отличаются цветовой температурой,
   а не декором, и остаются такими же спокойными и плотными.

   Каждый набор объявляет ВСЕ токены, которые вообще меняются от темы. Это не
   дублирование ради дублирования: токен, забытый в альтернативной теме,
   остался бы от основного набора — и, например, светлая подложка под тёмным
   текстом дала бы нечитаемое поле ввода. Имена — только `--z-*`; литеральные
   цвета в разметке по-прежнему запрещены (AGENTS.md §4 п.3).

   Обе темы описаны ОДНИМ селектором `[data-theme="..."]`, без повтора внутри
   `@media`-ветки: ценность здесь — явный выбор пользователя, а не следование
   системной настройке (для этого есть значение `auto`).
   -------------------------------------------------------------------------- */

/* Альтернативная светлая: холодный нейтральный (приём shadcn «slate»). */
[data-theme="light-alt"] {
  color-scheme: light;

  --z-bg: #f8fafc;
  --z-surface: #ffffff;
  --z-surface-2: #f1f5f9;
  --z-chrome: var(--z-surface);
  --z-fg: #020817;
  --z-muted-fg: #64748b;
  /* Средний тон текста (альтернативная светлая): светлее заголовка, темнее
     подписи-подсказки. Умолчание для --z-text-fg. */
  --z-fg-soft: #3d4759;
  --z-border: #e2e8f0;
  --z-primary: #0f172a;
  --z-on-primary: #f8fafc;
  --z-accent: #1d4ed8;
  --z-accent-soft: #e8eefc;
  --z-success: #15803d;
  --z-warning: #b45309;
  --z-danger: #b91c1c;
  --z-ring: #64748b;

  --z-row-alt: #f1f5f9;
  --z-row-hover: #e2e8f0;

  --z-brand-mark: #0ea5e9;
  --z-brand-name: #0ea5e9;

  --z-success-soft: rgba(21, 128, 61, .12);
  --z-warning-soft: rgba(180, 83, 9, .12);
  --z-danger-soft: rgba(185, 28, 28, .12);
  --z-ring-soft: rgba(100, 116, 139, .28);

  --z-hit-bg: #fbe0b4;
  --z-hit-fg: #3a2a12;

  --z-field-bg: #ffffff;
  --z-field-border: #8b8f96;
}

/* Альтернативная тёмная: графитовый нейтральный (приём shadcn «zinc»). */
[data-theme="dark-alt"] {
  color-scheme: dark;

  --z-bg: #09090b;
  --z-surface: #18181b;
  --z-surface-2: #27272a;
  --z-chrome: var(--z-surface);
  --z-fg: #fafafa;
  --z-muted-fg: #a1a1aa;
  /* Средний тон текста (альтернативная тёмная): светлее заголовка, темнее
     подписи-подсказки. Умолчание для --z-text-fg. */
  --z-fg-soft: #c8c8ce;
  --z-border: #3f3f46;
  --z-primary: #fafafa;
  --z-on-primary: #18181b;
  --z-accent: #93c5fd;
  --z-accent-soft: rgba(147, 197, 253, .14);
  --z-success: #4ade80;
  --z-warning: #fbbf24;
  --z-danger: #fb7185;
  --z-ring: #a1a1aa;

  --z-row-alt: #111113;
  --z-row-hover: #232327;

  --z-brand-mark: #38bdf8;
  --z-brand-name: #38bdf8;

  --z-success-soft: rgba(77, 163, 117, .18);
  --z-warning-soft: rgba(210, 154, 85, .18);
  --z-danger-soft: rgba(217, 106, 96, .18);
  --z-ring-soft: rgba(161, 161, 170, .40);

  --z-hit-bg: rgba(251, 146, 60, .30);
  --z-hit-fg: #fde8cd;

  --z-field-bg: #0d0d0f;
  --z-field-border: #71717a;
}


/* ==========================================================================
   ПАЛИТРЫ: 16 СВЕТЛЫХ И 16 ТЁМНЫХ СХЕМ

   Зачем отдельный слой. Раньше тема была ОДНИМ значением `data-theme` из пяти
   (`auto`, `light`, `dark`, `light-alt`, `dark-alt`): выбрать «светлую с синим»
   и «тёмную с янтарём» сразу было нельзя, а кнопки в шапке подменяли выбор
   устройства, из-за чего сохранённые «Настройки» переставали действовать.
   Теперь режим и палитра — разные вещи: режим отвечает «какую схему показывать»
   (авто | светлая | тёмная), палитра — «как она выглядит».

   Как устроено.
   * `data-theme` остаётся режимом и после разрешения равен `light` или `dark`,
     поэтому все прежние правила вида `[data-theme="dark"]` работают без правок.
   * `data-palette` — ключ палитры (`light-01` … `dark-12`). Блоки ниже
     переопределяют токены и стоят ПОСЛЕ базовых тем, поэтому выигрывают при
     равной специфичности.
   * Селектор — атрибут, а не `:root`: тот же ключ вешается на карточку превью
     на странице «Темы», и карточка показывает палитру, не меняя оформление
     самой страницы.

   Что меняет палитра и что нет.
   * Меняет нейтраль (фон, поверхности, текст, границы) и акцент (ссылки, метки,
     кольцо фокуса) — это и есть выбор сотрудника.
   * НЕ меняет сигнальные цвета (`--z-success`, `--z-warning`, `--z-danger`, их
     мягкие подложки) и подсветку найденного: красный обязан остаться красным
     в любой палитре, иначе смысл цвета поедет вместе с оформлением.
   * НЕ меняет знак портала (`--z-brand-mark`, `--z-brand-name`): логотип —
     принадлежность компании, а не оформление сотрудника.

   `light-01` и `dark-01` дословно повторяют прежние основную светлую и основную
   тёмную темы — у уже выбравших людей вид не меняется; `light-02` и `dark-02`
   повторяют прежние альтернативные. Контраст каждого набора проверен расчётом
   (WCAG): текст к фону, акцент к фону, надпись к кнопке, граница поля к подложке
   поля. Литеральные цвета живут только здесь (AGENTS.md §4 п.3).
   ========================================================================== */

/* --- 16 светлых схем --- */

[data-palette="light-01"] {
  color-scheme: light;
  --z-bg: #f6f5f2; --z-surface: #fdfdfc; --z-surface-2: #f1efe9;
  --z-fg: #16150f; --z-fg-soft: #46443d; --z-muted-fg: #6f6a61;
  --z-border: #e5e1d8; --z-primary: #1c1b19; --z-on-primary: #fbfaf7;
  --z-accent: #8c3f24; --z-accent-soft: #f3e7df;
  --z-ring: #b9a99a; --z-ring-soft: rgba(185,169,154,.30);
  --z-row-alt: #f1efe9; --z-row-hover: #e7e3d9;
  --z-field-bg: #ffffff; --z-field-border: #8b8f96;
}

[data-palette="light-02"] {
  color-scheme: light;
  --z-bg: #f8fafc; --z-surface: #ffffff; --z-surface-2: #f1f5f9;
  --z-fg: #020817; --z-fg-soft: #3d4759; --z-muted-fg: #64748b;
  --z-border: #e2e8f0; --z-primary: #0f172a; --z-on-primary: #f8fafc;
  --z-accent: #1d4ed8; --z-accent-soft: #e8eefc;
  --z-ring: #64748b; --z-ring-soft: rgba(100,116,139,.28);
  --z-row-alt: #f1f5f9; --z-row-hover: #e2e8f0;
  --z-field-bg: #ffffff; --z-field-border: #8b8f96;
}

[data-palette="light-03"] {
  color-scheme: light;
  --z-bg: #fafafa; --z-surface: #ffffff; --z-surface-2: #f4f4f5;
  --z-fg: #18181b; --z-fg-soft: #3f3f46; --z-muted-fg: #6b6b73;
  --z-border: #e4e4e7; --z-primary: #18181b; --z-on-primary: #fafafa;
  --z-accent: #52525b; --z-accent-soft: #f0f0f2;
  --z-ring: #52525b; --z-ring-soft: rgba(82,82,91,.25);
  --z-row-alt: #f4f4f5; --z-row-hover: #e9e9eb;
  --z-field-bg: #ffffff; --z-field-border: #8a8a91;
}

[data-palette="light-04"] {
  color-scheme: light;
  --z-bg: #fbf7f7; --z-surface: #ffffff; --z-surface-2: #f6ecee;
  --z-fg: #1c1917; --z-fg-soft: #443c3b; --z-muted-fg: #776b69;
  --z-border: #eadfe1; --z-primary: #1c1917; --z-on-primary: #fbf7f7;
  --z-accent: #be123c; --z-accent-soft: #fdf0f3;
  --z-ring: #be123c; --z-ring-soft: rgba(190,18,60,.25);
  --z-row-alt: #f6ecee; --z-row-hover: #f0e0e4;
  --z-field-bg: #ffffff; --z-field-border: #907c86;
}

[data-palette="light-05"] {
  color-scheme: light;
  --z-bg: #f8f7fd; --z-surface: #ffffff; --z-surface-2: #f1eefb;
  --z-fg: #111827; --z-fg-soft: #3b3a4a; --z-muted-fg: #68667c;
  --z-border: #e6e2f2; --z-primary: #1b1830; --z-on-primary: #f8f7fd;
  --z-accent: #6d28d9; --z-accent-soft: #f2eefe;
  --z-ring: #6d28d9; --z-ring-soft: rgba(109,40,217,.25);
  --z-row-alt: #f1eefb; --z-row-hover: #e7e1f7;
  --z-field-bg: #ffffff; --z-field-border: #7c7a90;
}

[data-palette="light-06"] {
  color-scheme: light;
  --z-bg: #f6fbf8; --z-surface: #ffffff; --z-surface-2: #eaf5ee;
  --z-fg: #171717; --z-fg-soft: #3a4740; --z-muted-fg: #61736a;
  --z-border: #dcebe2; --z-primary: #14251c; --z-on-primary: #f6fbf8;
  --z-accent: #047857; --z-accent-soft: #eafaf1;
  --z-ring: #047857; --z-ring-soft: rgba(4,120,87,.25);
  --z-row-alt: #eaf5ee; --z-row-hover: #dcf0e4;
  --z-field-bg: #ffffff; --z-field-border: #6f8f7f;
}

[data-palette="light-07"] {
  color-scheme: light;
  --z-bg: #f5fbfa; --z-surface: #ffffff; --z-surface-2: #e8f4f2;
  --z-fg: #0b1c22; --z-fg-soft: #35494d; --z-muted-fg: #5c7377;
  --z-border: #d9e9e6; --z-primary: #0f2b33; --z-on-primary: #f5fbfa;
  --z-accent: #0f766e; --z-accent-soft: #ecf8f6;
  --z-ring: #0f766e; --z-ring-soft: rgba(15,118,110,.25);
  --z-row-alt: #e8f4f2; --z-row-hover: #d8eeea;
  --z-field-bg: #ffffff; --z-field-border: #6b8b8b;
}

[data-palette="light-08"] {
  color-scheme: light;
  --z-bg: #fbf9f4; --z-surface: #ffffff; --z-surface-2: #f4efe2;
  --z-fg: #1c1917; --z-fg-soft: #46403a; --z-muted-fg: #736b5c;
  --z-border: #e8e1cf; --z-primary: #241c12; --z-on-primary: #fbf9f4;
  --z-accent: #a1540b; --z-accent-soft: #fdf6e6;
  --z-ring: #a1540b; --z-ring-soft: rgba(161,84,11,.25);
  --z-row-alt: #f4efe2; --z-row-hover: #ebe3cf;
  --z-field-bg: #ffffff; --z-field-border: #918876;
}

[data-palette="light-09"] {
  color-scheme: light;
  --z-bg: #f7f7fc; --z-surface: #ffffff; --z-surface-2: #eeeef8;
  --z-fg: #1c1917; --z-fg-soft: #3d3a4a; --z-muted-fg: #68667c;
  --z-border: #e2e1ee; --z-primary: #1e1b38; --z-on-primary: #f7f7fc;
  --z-accent: #4338ca; --z-accent-soft: #eef0fe;
  --z-ring: #4338ca; --z-ring-soft: rgba(67,56,202,.25);
  --z-row-alt: #eeeef8; --z-row-hover: #e3e3f5;
  --z-field-bg: #ffffff; --z-field-border: #7f7d94;
}

[data-palette="light-10"] {
  color-scheme: light;
  --z-bg: #fcf7f9; --z-surface: #ffffff; --z-surface-2: #f7edf1;
  --z-fg: #111827; --z-fg-soft: #42343a; --z-muted-fg: #78666e;
  --z-border: #ecdfe5; --z-primary: #241a20; --z-on-primary: #fcf7f9;
  --z-accent: #be185d; --z-accent-soft: #fdf0f5;
  --z-ring: #be185d; --z-ring-soft: rgba(190,24,93,.25);
  --z-row-alt: #f7edf1; --z-row-hover: #f0e0e7;
  --z-field-bg: #ffffff; --z-field-border: #907c86;
}

[data-palette="light-11"] {
  color-scheme: light;
  --z-bg: #fbf7f7; --z-surface: #ffffff; --z-surface-2: #f5eaea;
  --z-fg: #0b1020; --z-fg-soft: #3d3838; --z-muted-fg: #6d6666;
  --z-border: #e9dede; --z-primary: #1a1414; --z-on-primary: #fbf7f7;
  --z-accent: #b91c1c; --z-accent-soft: #fdf0f0;
  --z-ring: #b91c1c; --z-ring-soft: rgba(185,28,28,.25);
  --z-row-alt: #f5eaea; --z-row-hover: #eedede;
  --z-field-bg: #ffffff; --z-field-border: #8d8383;
}

[data-palette="light-12"] {
  color-scheme: light;
  --z-bg: #f5fafb; --z-surface: #ffffff; --z-surface-2: #e9f3f5;
  --z-fg: #14232a; --z-fg-soft: #35494f; --z-muted-fg: #5e777f;
  --z-border: #d9e7ea; --z-primary: #122a33; --z-on-primary: #f5fafb;
  --z-accent: #0e7490; --z-accent-soft: #ecf8fb;
  --z-ring: #0e7490; --z-ring-soft: rgba(14,116,144,.25);
  --z-row-alt: #e9f3f5; --z-row-hover: #dceef1;
  --z-field-bg: #ffffff; --z-field-border: #6b8891;
}

[data-palette="light-13"] {
  color-scheme: light;
  --z-bg: #ffffff; --z-surface: #ffffff; --z-surface-2: #fafafa;
  --z-fg: #0b0b0c; --z-fg-soft: #3a3a3d; --z-muted-fg: #6b6b70;
  --z-border: #e4e4e7; --z-primary: #18181b; --z-on-primary: #ffffff;
  --z-accent: #1d4ed8; --z-accent-soft: #eef3fe;
  --z-ring: #1d4ed8; --z-ring-soft: rgba(29,78,216,.22);
  --z-row-alt: #f7f7f8; --z-row-hover: #efeff1;
  --z-field-bg: #ffffff; --z-field-border: #8a8a92;
}

[data-palette="light-14"] {
  color-scheme: light;
  --z-bg: #eef1f5; --z-surface: #f9fbfd; --z-surface-2: #e3e8ef;
  --z-fg: #101821; --z-fg-soft: #33414f; --z-muted-fg: #5a6776;
  --z-border: #d3dae3; --z-primary: #1d2a38; --z-on-primary: #f6f9fc;
  --z-accent: #2f6f9f; --z-accent-soft: #e6f0f7;
  --z-ring: #2f6f9f; --z-ring-soft: rgba(47,111,159,.25);
  --z-row-alt: #e8edf3; --z-row-hover: #dde4ec;
  --z-field-bg: #ffffff; --z-field-border: #8494a3;
}

[data-palette="light-15"] {
  color-scheme: light;
  --z-bg: #f3eee4; --z-surface: #fbf7ef; --z-surface-2: #eae2d3;
  --z-fg: #241f17; --z-fg-soft: #453d31; --z-muted-fg: #6b6152;
  --z-border: #ddd2bf; --z-primary: #2a231a; --z-on-primary: #fbf7ef;
  --z-accent: #8a5a2b; --z-accent-soft: #f6ecdd;
  --z-ring: #8a5a2b; --z-ring-soft: rgba(138,90,43,.25);
  --z-row-alt: #efe8db; --z-row-hover: #e6dccb;
  --z-field-bg: #fffdf8; --z-field-border: #9c8f79;
}

[data-palette="light-16"] {
  color-scheme: light;
  --z-bg: #eff2ee; --z-surface: #f8faf6; --z-surface-2: #e4e9e1;
  --z-fg: #16201a; --z-fg-soft: #35443b; --z-muted-fg: #5c6b61;
  --z-border: #d6ddd2; --z-primary: #1d2b23; --z-on-primary: #f6faf4;
  --z-accent: #3f6b52; --z-accent-soft: #e7f0ea;
  --z-ring: #3f6b52; --z-ring-soft: rgba(63,107,82,.25);
  --z-row-alt: #e8ece5; --z-row-hover: #dde3d9;
  --z-field-bg: #ffffff; --z-field-border: #839787;
}

/* --- 16 тёмных схем --- */

[data-palette="dark-01"] {
  color-scheme: dark;
  --z-bg: #020617; --z-surface: #0f172a; --z-surface-2: #1e293b;
  --z-fg: #e5e7eb; --z-fg-soft: #b9c0cc; --z-muted-fg: #94a3b8;
  --z-border: #1e293b; --z-primary: #38bdf8; --z-on-primary: #0b1220;
  --z-accent: #38bdf8; --z-accent-soft: rgba(56,189,248,.12);
  --z-ring: #0ea5e9; --z-ring-soft: rgba(107,97,82,.55);
  --z-row-alt: #0c1528; --z-row-hover: #16243d;
  --z-field-bg: #0b1220; --z-field-border: #64748b;
}

[data-palette="dark-02"] {
  color-scheme: dark;
  --z-bg: #09090b; --z-surface: #18181b; --z-surface-2: #27272a;
  --z-fg: #fafafa; --z-fg-soft: #c8c8ce; --z-muted-fg: #a1a1aa;
  --z-border: #3f3f46; --z-primary: #fafafa; --z-on-primary: #18181b;
  --z-accent: #93c5fd; --z-accent-soft: rgba(147,197,253,.14);
  --z-ring: #a1a1aa; --z-ring-soft: rgba(161,161,170,.40);
  --z-row-alt: #111113; --z-row-hover: #232327;
  --z-field-bg: #0d0d0f; --z-field-border: #71717a;
}

[data-palette="dark-03"] {
  color-scheme: dark;
  --z-bg: #0c0a0b; --z-surface: #191416; --z-surface-2: #241b1e;
  --z-fg: #f5f5f4; --z-fg-soft: #d6d3d1; --z-muted-fg: #a8a29e;
  --z-border: #2e2427; --z-primary: #fb7185; --z-on-primary: #1a0b10;
  --z-accent: #fb7185; --z-accent-soft: rgba(251,113,133,.14);
  --z-ring: #fb7185; --z-ring-soft: rgba(251,113,133,.35);
  --z-row-alt: #141011; --z-row-hover: #201a1c;
  --z-field-bg: #100c0d; --z-field-border: #78716c;
}

[data-palette="dark-04"] {
  color-scheme: dark;
  --z-bg: #0a0812; --z-surface: #171226; --z-surface-2: #221a38;
  --z-fg: #f4f2ff; --z-fg-soft: #cfcae6; --z-muted-fg: #a5a0bd;
  --z-border: #2c2545; --z-primary: #a78bfa; --z-on-primary: #140f24;
  --z-accent: #a78bfa; --z-accent-soft: rgba(167,139,250,.16);
  --z-ring: #a78bfa; --z-ring-soft: rgba(167,139,250,.35);
  --z-row-alt: #120e1f; --z-row-hover: #1d1733;
  --z-field-bg: #0d0a16; --z-field-border: #7c7694;
}

[data-palette="dark-05"] {
  color-scheme: dark;
  --z-bg: #04100b; --z-surface: #0d1b14; --z-surface-2: #14281e;
  --z-fg: #ecfdf5; --z-fg-soft: #c6e3d3; --z-muted-fg: #93b4a5;
  --z-border: #1e3a2c; --z-primary: #4ade80; --z-on-primary: #052013;
  --z-accent: #4ade80; --z-accent-soft: rgba(74,222,128,.14);
  --z-ring: #4ade80; --z-ring-soft: rgba(74,222,128,.32);
  --z-row-alt: #071a11; --z-row-hover: #10261a;
  --z-field-bg: #06170f; --z-field-border: #6f8f7f;
}

[data-palette="dark-06"] {
  color-scheme: dark;
  --z-bg: #04121a; --z-surface: #0b1f2a; --z-surface-2: #112c3a;
  --z-fg: #e6fbff; --z-fg-soft: #bfe0ea; --z-muted-fg: #8fb3c2;
  --z-border: #173d4e; --z-primary: #2dd4bf; --z-on-primary: #04231f;
  --z-accent: #2dd4bf; --z-accent-soft: rgba(45,212,191,.14);
  --z-ring: #2dd4bf; --z-ring-soft: rgba(45,212,191,.32);
  --z-row-alt: #061820; --z-row-hover: #0f2937;
  --z-field-bg: #061821; --z-field-border: #6b8f9c;
}

[data-palette="dark-07"] {
  color-scheme: dark;
  --z-bg: #120d07; --z-surface: #1e1710; --z-surface-2: #2b2117;
  --z-fg: #fdf6ec; --z-fg-soft: #e2d3bd; --z-muted-fg: #bda98f;
  --z-border: #3a2d1e; --z-primary: #fbbf24; --z-on-primary: #241a05;
  --z-accent: #fbbf24; --z-accent-soft: rgba(251,191,36,.14);
  --z-ring: #fbbf24; --z-ring-soft: rgba(251,191,36,.32);
  --z-row-alt: #171009; --z-row-hover: #241a10;
  --z-field-bg: #160f07; --z-field-border: #8a7a63;
}

[data-palette="dark-08"] {
  color-scheme: dark;
  --z-bg: #070b18; --z-surface: #111a2c; --z-surface-2: #1a2740;
  --z-fg: #e8edff; --z-fg-soft: #c6d0ea; --z-muted-fg: #9aa8c7;
  --z-border: #24314d; --z-primary: #818cf8; --z-on-primary: #0d1226;
  --z-accent: #818cf8; --z-accent-soft: rgba(129,140,248,.15);
  --z-ring: #818cf8; --z-ring-soft: rgba(129,140,248,.35);
  --z-row-alt: #0b1222; --z-row-hover: #152036;
  --z-field-bg: #08101f; --z-field-border: #77809c;
}

[data-palette="dark-09"] {
  color-scheme: dark;
  --z-bg: #120811; --z-surface: #1e101c; --z-surface-2: #2b1729;
  --z-fg: #fdf2f8; --z-fg-soft: #e8cddd; --z-muted-fg: #c39ab5;
  --z-border: #3b2136; --z-primary: #f472b6; --z-on-primary: #2a0a1c;
  --z-accent: #f472b6; --z-accent-soft: rgba(244,114,182,.14);
  --z-ring: #f472b6; --z-ring-soft: rgba(244,114,182,.35);
  --z-row-alt: #170b16; --z-row-hover: #241523;
  --z-field-bg: #150a14; --z-field-border: #8d7083;
}

[data-palette="dark-10"] {
  color-scheme: dark;
  --z-bg: #120707; --z-surface: #1f0e0f; --z-surface-2: #2c1618;
  --z-fg: #fef2f2; --z-fg-soft: #e8caca; --z-muted-fg: #c79c9c;
  --z-border: #3d1d1f; --z-primary: #f87171; --z-on-primary: #2a0a0a;
  --z-accent: #f87171; --z-accent-soft: rgba(248,113,113,.14);
  --z-ring: #f87171; --z-ring-soft: rgba(248,113,113,.35);
  --z-row-alt: #170a0a; --z-row-hover: #251313;
  --z-field-bg: #150808; --z-field-border: #917070;
}

[data-palette="dark-11"] {
  color-scheme: dark;
  --z-bg: #05121a; --z-surface: #0b1e28; --z-surface-2: #102b38;
  --z-fg: #e0fbff; --z-fg-soft: #b7ddea; --z-muted-fg: #86aebf;
  --z-border: #163d4d; --z-primary: #22d3ee; --z-on-primary: #06232a;
  --z-accent: #22d3ee; --z-accent-soft: rgba(34,211,238,.14);
  --z-ring: #22d3ee; --z-ring-soft: rgba(34,211,238,.32);
  --z-row-alt: #071820; --z-row-hover: #0d2733;
  --z-field-bg: #061820; --z-field-border: #688e9c;
}

[data-palette="dark-12"] {
  color-scheme: dark;
  --z-bg: #0b0f07; --z-surface: #151c10; --z-surface-2: #1f2a17;
  --z-fg: #f7fee7; --z-fg-soft: #d6e3c4; --z-muted-fg: #a8b894;
  --z-border: #2b3a20; --z-primary: #a3e635; --z-on-primary: #14200a;
  --z-accent: #a3e635; --z-accent-soft: rgba(163,230,53,.14);
  --z-ring: #a3e635; --z-ring-soft: rgba(163,230,53,.30);
  --z-row-alt: #0f150a; --z-row-hover: #1a2412;
  --z-field-bg: #0d1208; --z-field-border: #7e8f6b;
}

[data-palette="dark-13"] {
  color-scheme: dark;
  --z-bg: #000000; --z-surface: #000000; --z-surface-2: #0d0d0d;
  --z-fg: #ededed; --z-fg-soft: #c9c9c9; --z-muted-fg: #9a9a9a;
  --z-border: #1f1f1f; --z-primary: #ffffff; --z-on-primary: #000000;
  --z-accent: #60a5fa; --z-accent-soft: rgba(96,165,250,.16);
  --z-ring: #60a5fa; --z-ring-soft: rgba(96,165,250,.35);
  --z-row-alt: #0a0a0a; --z-row-hover: #151515;
  --z-field-bg: #000000; --z-field-border: #666666;
}

[data-palette="dark-14"] {
  color-scheme: dark;
  --z-bg: #22272e; --z-surface: #2d333b; --z-surface-2: #373e47;
  --z-fg: #adbac7; --z-fg-soft: #cdd9e5; --z-muted-fg: #8b98a5;
  --z-border: #444c56; --z-primary: #6cb6ff; --z-on-primary: #0d1117;
  --z-accent: #6cb6ff; --z-accent-soft: rgba(108,182,255,.16);
  --z-ring: #6cb6ff; --z-ring-soft: rgba(108,182,255,.35);
  --z-row-alt: #282e35; --z-row-hover: #333a42;
  --z-field-bg: #1c2128; --z-field-border: #6b7682;
}

[data-palette="dark-15"] {
  color-scheme: dark;
  --z-bg: #121212; --z-surface: #1e1e1e; --z-surface-2: #2a2a2a;
  --z-fg: #eaeaea; --z-fg-soft: #cfcfcf; --z-muted-fg: #a0a0a0;
  --z-border: #353535; --z-primary: #8ab4f8; --z-on-primary: #0b1220;
  --z-accent: #8ab4f8; --z-accent-soft: rgba(138,180,248,.15);
  --z-ring: #8ab4f8; --z-ring-soft: rgba(138,180,248,.32);
  --z-row-alt: #181818; --z-row-hover: #242424;
  --z-field-bg: #0e0e0e; --z-field-border: #6b6b6b;
}

[data-palette="dark-16"] {
  color-scheme: dark;
  --z-bg: #1a1614; --z-surface: #241f1c; --z-surface-2: #2f2825;
  --z-fg: #f0e9e3; --z-fg-soft: #d6cbc2; --z-muted-fg: #b0a29a;
  --z-border: #3b3230; --z-primary: #e0a06a; --z-on-primary: #201508;
  --z-accent: #e0a06a; --z-accent-soft: rgba(224,160,106,.15);
  --z-ring: #e0a06a; --z-ring-soft: rgba(224,160,106,.32);
  --z-row-alt: #1f1a17; --z-row-hover: #2a2420;
  --z-field-bg: #161210; --z-field-border: #7d6f66;
}

/* «Оболочка» палитры: боковое меню и верхняя панель берут поверхность второго
   уровня. Почему именно так: у светлых палитр `--z-surface` белый почти у всех
   наборов, и на нём смена схемы была не видна вообще. `surface-2` есть в каждом
   наборе и заметно отличается от фона и от карточек — меню и шапка меняются
   вместе с остальным, а карточки остаются светлее фона. */
[data-palette] { --z-chrome: var(--z-surface-2); }

/* ==========================================================================
   2. БАЗА
   ========================================================================== */

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

* { margin: 0; }

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

body {
  /* Размер основного текста считается от множителя --z-text: так выбор
     «размер текста» в «Настройках» действует и на текст без явного размера. */
  font-family: var(--z-font);
  font-size: calc(14px * var(--z-text, 1));
  line-height: 1.55;
  font-weight: var(--z-text-weight);
  background: var(--z-bg);
  color: var(--z-text-fg);
  -webkit-font-smoothing: antialiased;
}

/* Печать: выбранный на экране размер текста на печать НЕ переносится.
   Печатные виды (карточка и табель документооборота) — официальные формы,
   их размеры выверены и не должны зависеть от личного предпочтения на экране.
   `!important` здесь обязателен: значение приходит инлайном на <html>, а инлайн
   без пометки проигрывает правилу с `!important` (проверено в Chromium). */
@media print {
  /* Печатные формы — выверенные: ни размер, ни жирность, ни яркость текста
     с экрана на бумагу не переносятся (инлайн-значение перебивается только
     правилом с `!important`, проверено в Chromium). Яркость пришпилена
     к 100 %: печатный текст чёрный, а не «насколько выбрал сотрудник». */
  :root {
    --z-text: 1 !important;
    --z-heading-bright: 100% !important;
    --z-text-bright: 100% !important;
    --z-heading-fg: var(--z-fg) !important;
    --z-heading-weight: 700 !important;
    --z-text-fg: var(--z-fg) !important;
    --z-text-weight: 400 !important;
  }
}
img,
svg,
video { max-width: 100%; height: auto; }

h1, h2, h3, h4 {
  font-weight: var(--z-heading-weight); color: var(--z-heading-fg);
  line-height: 1.25;
  letter-spacing: -.01em;
}

h1 { font-size: calc(28px * var(--z-text, 1)); }
h2 { font-size: calc(22px * var(--z-text, 1)); }
h3 { font-size: calc(18px * var(--z-text, 1)); }
h4 { font-size: calc(16px * var(--z-text, 1)); }

a { color: var(--z-accent); text-decoration: none; }
a:hover { text-decoration: underline; }

code, kbd, pre, samp { font-family: var(--z-mono); }

button, input, select, textarea { font: inherit; }

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

:focus-visible { outline: 2px solid var(--z-ring); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}


/* ==========================================================================
   3. УТИЛИТЫ
   ========================================================================== */

.z-muted { color: var(--z-muted-fg); }
.z-mono { font-family: var(--z-mono); }
.z-small { font-size: calc(12px * var(--z-text, 1)); }

.z-flex { display: flex; align-items: center; }
.z-flex--wrap { flex-wrap: wrap; }
.z-between { display: flex; align-items: center; justify-content: space-between; }

.z-gap-8 { gap: 8px; }
.z-gap-12 { gap: 12px; }
.z-gap-16 { gap: 16px; }

.z-mt-8 { margin-top: 8px; }
.z-mt-16 { margin-top: 16px; }
.z-mt-24 { margin-top: 24px; }

.z-hidden { display: none !important; }

/* визуально скрыто, доступно скринридерам */
.z-vh {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* skip-link — виден только при фокусе с клавиатуры */
.z-skip {
  position: fixed;
  top: -60px;
  left: 16px;
  z-index: 300;
  padding: 8px 12px;
  border-radius: 8px;
  background: var(--z-primary);
  color: var(--z-on-primary);
  font-size: calc(13px * var(--z-text, 1));
  text-decoration: none;
  transition: top .15s ease;
}
.z-skip:focus { top: 12px; text-decoration: none; }


/* ==========================================================================
   4. ЛЕЙАУТ СТРАНИЦЫ
   ========================================================================== */

.z-wrap {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 24px;
}

.z-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--z-surface);
  border-bottom: 1px solid var(--z-border);
}

.z-header__inner {
  display: flex;
  align-items: center;
  gap: 16px;
  height: 56px;
}

.z-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: calc(15px * var(--z-text, 1));
  font-weight: var(--z-heading-weight);
  color: var(--z-heading-fg);
  text-decoration: none;
  white-space: nowrap;
}
.z-brand:hover { text-decoration: none; }

/* Знак портала: иконка-батарейка несёт брендовый цвет, надпись — свой цвет.
   Цвета заданы токенами (--z-brand-mark / --z-brand-name), в шаблоне литералов нет. */
.z-brand__mark { flex: 0 0 auto; color: var(--z-brand-mark); }
.z-brand__name { color: var(--z-brand-name); }

.z-nav {
  display: flex;
  align-items: center;
  gap: 2px;
}
.z-nav a {
  display: inline-flex;
  align-items: center;
  padding: 6px 10px;
  border-radius: 8px;
  font-size: calc(13px * var(--z-text, 1));
  color: var(--z-muted-fg);
  text-decoration: none;
  white-space: nowrap;
  transition: background-color .15s ease, color .15s ease;
}
.z-nav a:hover {
  background: var(--z-surface-2);
  color: var(--z-fg);
  text-decoration: none;
}
.z-nav a[aria-current],
.z-nav a.z-active {
  background: var(--z-surface-2);
  color: var(--z-fg);
  font-weight: var(--z-text-weight);
}

.z-header__right {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}

.z-userchip {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.z-userchip__ava {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  border-radius: 999px;
  border: 1px solid var(--z-border);
  background: var(--z-surface-2);
  font-size: calc(11px * var(--z-text, 1));
  font-weight: 650;
}
.z-userchip__name {
  font-size: calc(13px * var(--z-text, 1));
  font-weight: var(--z-text-weight);
  line-height: 1.2;
  white-space: nowrap;
}
.z-userchip__role {
  font-size: calc(11px * var(--z-text, 1));
  color: var(--z-muted-fg);
  line-height: 1.2;
  white-space: nowrap;
}

.z-foot {
  margin-top: 40px;
  padding: 24px 0;
  border-top: 1px solid var(--z-border);
  color: var(--z-muted-fg);
  font-size: calc(12px * var(--z-text, 1));
}


/* ==========================================================================
   5. ПЕРЕКЛЮЧАТЕЛЬ ТЕМЫ «Авто / Светлая / Тёмная»
   ========================================================================== */

.z-themes {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 2px;
  border: 1px solid var(--z-border);
  border-radius: 8px;
  /* Переключатель стоит на «оболочке» (--z-chrome = surface-2 у палитр),
     поэтому ему нужна собственная подложка: на surface-2 он бы слился с шапкой. */
  background: var(--z-surface);
}
.z-themes button,
.z-themes__btn {
  height: 26px;
  padding: 0 10px;
  border: 1px solid transparent;
  border-radius: 6px;
  background: transparent;
  color: var(--z-muted-fg);
  font-size: calc(12px * var(--z-text, 1));
  font-weight: var(--z-text-weight);
  line-height: 1;
  cursor: pointer;
  transition: background-color .15s ease, color .15s ease, border-color .15s ease;
}
.z-themes button:hover { color: var(--z-fg); }
.z-themes button[aria-pressed="true"],
.z-themes button.is-active,
.z-themes__btn.is-active {
  background: var(--z-surface);
  border-color: var(--z-border);
  color: var(--z-fg);
  font-weight: var(--z-text-weight);
  box-shadow: var(--z-shadow);
}

/* --- Выбор темы, шрифта и размера текста в «Настройках» (settings.php) ---
   Дёшево, но важно: без запрета переноса длинные подписи (например,
   «Альтернативная тёмная (графитовая)») ломают строку так, что радиокнопка
   и текст наезжают друг на друга. Список — вертикальный: подписи разной длины.
   `.z-fontpick` — тот же список для выбора шрифта и размера: сам образец
   набран тем шрифтом (или тем размером), который выбирает строка. */
.z-themepick,
.z-fontpick {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 0 0 4px;
}
.z-themepick .z-check,
.z-fontpick .z-check {
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border: 1px solid var(--z-border);
  border-radius: var(--z-radius-sm);
  background: var(--z-field-bg, var(--z-surface));
  cursor: pointer;
}
.z-themepick .z-check:hover,
.z-fontpick .z-check:hover { border-color: var(--z-ring); }
.z-themepick .z-check span,
.z-fontpick .z-check span { line-height: 1.35; }

/* Образец строки выбора: набран тем шрифтом и размером, которые эта строка
   предлагает. Собран из подписи и примера текста, поэтому переносится. */
.z-fontpick__sample {
  display: block;
  margin-top: 2px;
  color: var(--z-muted-fg);
  font-size: calc(12px * var(--z-text, 1));
}

/* --- Предпросмотр оформления в «Настройках» (settings.php) ---
   По названию («Bahnschrift») шрифт не выбрать, а короткие строки-образцы
   в списках показывают настройки по отдельности: как выглядит СТРАНИЦА, когда
   шрифт, размер, палитра, яркость и жирность действуют вместе, по ним не видно.
   Поэтому поля оформления стоят левой колонкой, а справа — пример страницы,
   закреплённый при прокрутке (`position: sticky`). Скрипт
   (assets/js/z.js, initAppearancePreview) обновляет пример на КАЖДОЕ изменение
   формы: режим (`data-theme`), палитра (`data-palette`), шрифт (`--z-font`),
   размер (`--z-text`), яркость и жирность (`--z-heading-*`, `--z-text-*`).
   Без скрипта пример показывает сохранённое оформление — страница работает.

   Цвета и размеры здесь ТОЛЬКО из токенов: пример обязан совпадать с порталом
   в любой схеме, а размер — слушаться настройки «Размер текста» (ловушка 17).
   Пример — СВОЯ область темы: `data-theme`/`data-palette` на нём переопределяют
   токены только для него, поэтому страница вокруг до сохранения не меняется. */
.z-form.z-appearance {
  display: grid;
  gap: 18px 22px;
  align-items: start;
}
/* `.z-sec` обрезает содержимое (`overflow: hidden`) ради скруглённых углов —
   и это ЛОМАЕТ position: sticky у потомков: закрепление считается внутри
   ближайшей прокручиваемой коробки, которой становится сама секция. Для
   «Настроек» обрезка не нужна (внутри нет углов, вылезающих за рамку),
   поэтому секция с примером её снимает. */
.z-sec.z-sec--appearance { overflow: visible; }
/* Поля внутри левой колонки: раньше отступ между ними давал `.z-form`
   (flex + gap). Обёртка колонки его перекрыла бы, поэтому то же расстояние
   задано здесь. */
.z-appearance__fields {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
@media (max-width: 1179px) {
  /* На узком экране колонка одна: пример показывается ПЕРВЫМ, до полей, —
     так он виден сразу, а не только после кнопки «Сохранить». */
  .z-appearance__aside { order: -1; }
}
@media (min-width: 1180px) {
  .z-form.z-appearance { grid-template-columns: minmax(0, 1fr) minmax(300px, 360px); }
  /* Пример остаётся на экране, пока листаешь настройки: 56px — шапка портала,
     ещё 16px — зазор. Высота ограничена окном: длинный пример прокручивается
     внутри себя, а не уходит за край. */
  .z-appearance__aside {
    position: sticky;
    top: 72px;
    max-height: calc(100vh - 88px);
    overflow-y: auto;
  }
}
.z-fontpreview {
  border: 1px solid var(--z-border);
  border-radius: var(--z-radius-sm);
  background: var(--z-surface-2);
  overflow: hidden;
}
.z-fontpreview__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 12px;
  border-bottom: 1px solid var(--z-border);
}
.z-fontpreview__label {
  font-size: calc(11px * var(--z-text, 1));
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--z-muted-fg);
}
.z-fontpreview__name {
  max-width: 64%;
  font-size: calc(12px * var(--z-text, 1));
  font-weight: var(--z-text-weight);
  color: var(--z-text-fg);
  text-align: right;
}
/* Мини-страница: фон темы, а карточка — поверхность, как на настоящей главной.
   Шрифт берётся из токена --z-font (его подставляет скрипт или разметка),
   поэтому начертание меняется вместе с выбором в форме.

   Токены ролей пересчитываются ЗДЕСЬ: унаследованные с :root --z-heading-fg
   и --z-text-fg уже посчитаны от цвета страницы и не изменились бы, когда
   у примера своя палитра и своя яркость. */
.z-fontpreview__paper {
  padding: 14px;
  background: var(--z-bg);
  color: var(--z-text-fg);
  font-family: var(--z-font);
  /* Жирность ТЕКСТА примера — из токена: заголовки ниже перебивают её своим
     --z-heading-weight, остальные строки наследуют выбор «Текст: жирность». */
  font-weight: var(--z-text-weight);
  --z-heading-fg: var(--z-fg);
  --z-text-fg: var(--z-fg-soft);
}
@supports (color: color-mix(in srgb, red, blue)) {
  .z-fontpreview__paper {
    --z-heading-fg: color-mix(in srgb, var(--z-fg) var(--z-heading-bright), var(--z-bg));
    --z-text-fg: color-mix(in srgb, var(--z-fg) var(--z-text-bright), var(--z-bg));
  }
}
.z-fontpreview__pagehead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  padding-bottom: 10px;
  margin-bottom: 12px;
  border-bottom: 1px solid var(--z-border);
}
.z-fontpreview__h1 {
  font-size: calc(18px * var(--z-text, 1));
  font-weight: var(--z-heading-weight);
  color: var(--z-heading-fg);
}
.z-fontpreview__user {
  font-size: calc(12px * var(--z-text, 1));
  color: var(--z-muted-fg);
  white-space: nowrap;
}
.z-fontpreview__h {
  font-size: calc(16px * var(--z-text, 1));
  font-weight: var(--z-heading-weight);
  color: var(--z-heading-fg);
  line-height: 1.3;
}
.z-fontpreview__sub {
  margin-top: 2px;
  font-size: calc(12px * var(--z-text, 1));
  color: var(--z-muted-fg);
}
.z-fontpreview__card {
  margin-top: 12px;
  padding: 10px 12px;
  border: 1px solid var(--z-border);
  border-radius: var(--z-radius-sm);
  background: var(--z-surface);
}
.z-fontpreview__cardtitle {
  font-size: calc(13.5px * var(--z-text, 1));
  font-weight: var(--z-heading-weight);
  color: var(--z-heading-fg);
}
.z-fontpreview__cardsub {
  font-size: calc(12px * var(--z-text, 1));
  color: var(--z-muted-fg);
}
.z-fontpreview__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 8px;
  padding: 6px 0;
  border-top: 1px solid var(--z-border);
  font-size: calc(12.5px * var(--z-text, 1));
}
.z-fontpreview__row:first-of-type { margin-top: 6px; }
.z-fontpreview__svc {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--z-text-fg);
}
.z-fontpreview__dim {
  font-size: calc(12px * var(--z-text, 1));
  color: var(--z-muted-fg);
  white-space: nowrap;
}
.z-fontpreview__btn {
  padding: 2px 8px;
  border: 1px solid var(--z-border);
  border-radius: 6px;
  background: var(--z-surface-2);
  color: var(--z-text-fg);
  font-size: calc(11.5px * var(--z-text, 1));
  white-space: nowrap;
}
.z-fontpreview__line {
  margin-top: 12px;
  font-size: calc(12.5px * var(--z-text, 1));
  line-height: 1.45;
  color: var(--z-text-fg);
}
/* Пояснение под примером: он показывает ЧЕРНОВИК выбранных значений,
   а сохраняется кнопкой — без этой строки легко решить, что уже записано. */
.z-fontpreview__note {
  padding: 8px 12px;
  border-top: 1px solid var(--z-border);
  font-size: calc(11.5px * var(--z-text, 1));
  line-height: 1.4;
  color: var(--z-muted-fg);
}


/* ==========================================================================
   6. КОМПОНЕНТЫ
   ========================================================================== */

/* --- карточка --- */

.z-card {
  background: var(--z-surface);
  border: 1px solid var(--z-border);
  border-radius: var(--z-radius);
  padding: 18px;
}
.z-card__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding-bottom: 12px;
  margin-bottom: 12px;
  border-bottom: 1px solid var(--z-border);
}
.z-card__title {
  font-size: calc(15px * var(--z-text, 1));
  font-weight: var(--z-heading-weight); color: var(--z-heading-fg);
  line-height: 1.3;
}
.z-card__hint {
  margin-top: 2px;
  font-size: calc(12px * var(--z-text, 1));
  color: var(--z-muted-fg);
}

/* --- секция страницы --- */

.z-section { margin-top: 28px; }
.z-section,
.z-section [id] { scroll-margin-top: 70px; }
.z-section__title {
  font-size: calc(18px * var(--z-text, 1));
  font-weight: var(--z-heading-weight); color: var(--z-heading-fg);
  margin-bottom: 12px;
  scroll-margin-top: 70px;
}

/* --- сетки --- */

.z-grid {
  display: grid;
  gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
}
.z-grid--wide { grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); }
.z-grid--tight { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }

/* --- кнопки --- */

.z-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 34px;
  padding: 0 14px;
  border: 1px solid var(--z-border);
  border-radius: var(--z-radius-sm);
  background: var(--z-surface);
  color: var(--z-text-fg);
  font-size: calc(13px * var(--z-text, 1));
  font-weight: var(--z-text-weight);
  line-height: 1;
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  transition: background-color .15s ease, border-color .15s ease, color .15s ease, opacity .15s ease;
}
.z-btn:hover:not(:disabled) {
  background: var(--z-surface-2);
  text-decoration: none;
}

.z-btn--primary {
  background: var(--z-primary);
  border-color: var(--z-primary);
  color: var(--z-on-primary);
}
.z-btn--primary:hover:not(:disabled) {
  background: var(--z-primary);
  opacity: .9;
}

.z-btn--accent {
  background: var(--z-accent);
  border-color: var(--z-accent);
  color: #fff;
}
.z-btn--accent:hover:not(:disabled) { opacity: .9; }

.z-btn--ghost {
  background: transparent;
  border-color: transparent;
}
.z-btn--ghost:hover:not(:disabled) { background: var(--z-surface-2); }

.z-btn--danger {
  background: var(--z-surface);
  border-color: var(--z-danger);
  color: var(--z-danger);
}
.z-btn--danger:hover:not(:disabled) { background: var(--z-danger-soft); }

.z-btn--small {
  height: 28px;
  padding: 0 10px;
  font-size: calc(13px * var(--z-text, 1));
}

.z-btn:disabled,
.z-btn[aria-disabled="true"] {
  opacity: .5;
  cursor: not-allowed;
}

/* на светлом акценте в тёмной теме текст должен быть тёмным (контраст);
   альтернативная тёмная тема — тоже тёмная, поэтому правило одно на обе */
[data-theme="dark"] .z-btn--accent,
[data-theme="dark-alt"] .z-btn--accent { color: var(--z-on-primary); }

@media (prefers-color-scheme: dark) {
  :root[data-theme="auto"] .z-btn--accent { color: var(--z-on-primary); }
}

/* --- поля ввода --- */

.z-input,
.z-select,
.z-textarea {
  width: 100%;
  min-height: 34px;
  padding: 6px 10px;
  border: 1px solid var(--z-field-border, var(--z-border));
  border-radius: 8px;
  background: var(--z-field-bg, var(--z-surface));
  color: var(--z-text-fg);
  font-size: calc(13px * var(--z-text, 1));
  line-height: 1.4;
  transition: border-color .15s ease, box-shadow .15s ease;
}
/* Ползунок яркости в «Настройках»: своё оформление не рисуем, только акцент
   темы (accent-color) и высота строки — иначе ползунок выпадает из системы. */
.z-range {
  width: 100%;
  min-height: 34px;
  accent-color: var(--z-accent);
  cursor: pointer;
}
.z-range__row {
  display: flex;
  align-items: center;
  gap: 12px;
}
.z-range__val {
  flex: none;
  min-width: 58px;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.z-textarea {
  min-height: 84px;
  resize: vertical;
}
.z-input::placeholder,
.z-textarea::placeholder { color: var(--z-muted-fg); }

.z-input:focus,
.z-select:focus,
.z-textarea:focus {
  outline: none;
  border-color: var(--z-ring);
  box-shadow: 0 0 0 3px var(--z-ring-soft);
}

.z-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.z-field > label,
.z-field__label {
  font-size: calc(13px * var(--z-text, 1));
  font-weight: var(--z-text-weight); color: var(--z-muted-fg);
}
.z-field__hint {
  font-size: calc(12px * var(--z-text, 1));
  color: var(--z-muted-fg);
}

.z-check {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: calc(13px * var(--z-text, 1));
}
.z-check input[type="checkbox"] {
  width: 16px;
  height: 16px;
  margin: 0;
  accent-color: var(--z-accent);
}
/* Радиокнопки — тот же акцент, что у флажков: в «Профиле» выбор темы сделан
   радиокнопками, и без этого правила они остаются браузерными по цвету. */
.z-check input[type="radio"] {
  width: 16px;
  height: 16px;
  margin: 0;
  accent-color: var(--z-accent);
}

/* --- алерты --- */

.z-alert {
  padding: 10px 12px;
  border: 1px solid var(--z-border);
  border-radius: 8px;
  background: var(--z-surface);
  font-size: calc(13px * var(--z-text, 1));
}
.z-alert--error {
  background: #fdf0ee;
  border-color: var(--z-danger);
}
.z-alert--success {
  background: var(--z-success-soft);
  border-color: var(--z-success);
}
.z-alert--info {
  background: var(--z-accent-soft);
  border-color: var(--z-border);
}
.z-alert--warn {
  background: #fdf6ec;
  border-color: var(--z-warning, #a8641c);
}

[data-theme="dark"] .z-alert--error,
[data-theme="dark-alt"] .z-alert--error { background: var(--z-danger-soft); }
[data-theme="dark"] .z-alert--warn,
[data-theme="dark-alt"] .z-alert--warn { background: var(--z-warning-soft); }

@media (prefers-color-scheme: dark) {
  :root[data-theme="auto"] .z-alert--error { background: var(--z-danger-soft); }
  :root[data-theme="auto"] .z-alert--warn { background: var(--z-warning-soft); }
}

/* --- бейджи --- */

.z-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--z-surface-2);
  color: var(--z-text-fg);
  font-size: calc(12px * var(--z-text, 1));
  line-height: 1.5;
  white-space: nowrap;
}
.z-badge--ok { background: var(--z-success-soft); color: var(--z-success); }
.z-badge--warn { background: var(--z-warning-soft); color: #8a5313; } /* затемнён ради контраста ≥ 4.5:1 */
.z-badge--err { background: var(--z-danger-soft); color: var(--z-danger); }
.z-badge--accent { background: var(--z-accent-soft); color: var(--z-accent); }

[data-theme="dark"] .z-badge--warn,
[data-theme="dark-alt"] .z-badge--warn { color: var(--z-warning); }

@media (prefers-color-scheme: dark) {
  :root[data-theme="auto"] .z-badge--warn { color: var(--z-warning); }
}

/* --- таблица --- */

.z-table {
  width: 100%;
  border-collapse: collapse;
  font-size: calc(13px * var(--z-text, 1));
}
.z-table th {
  padding: 8px 10px;
  text-align: left;
  font-size: calc(11px * var(--z-text, 1));
  font-weight: var(--z-text-weight);
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--z-muted-fg);
  border-bottom: 1px solid var(--z-border);
  white-space: nowrap;
}
.z-table td {
  padding: 8px 10px;
  border-bottom: 1px solid var(--z-border);
  vertical-align: middle;
}
/* Чередование строк таблиц: выраженная «зебра» (чётные строки залиты) и hover,
   который от заливки чётной строки отличается. Работает и на главной, и в админке;
   в тёмной теме заливка берётся из палитры BatteryPassport (#0c1528). */
.z-table tbody tr:nth-child(even) { background: var(--z-row-alt); }
.z-table tbody tr:hover { background: var(--z-row-hover); }
.z-admin-table tbody tr:nth-child(even) td { background: var(--z-row-alt); }
.z-admin-table tbody tr:hover td { background: var(--z-row-hover); }

/* ==========================================================================
   Классы страниц админки

   ДЕФЕКТ, который здесь исправляется: разметка админки применяла 25 классов
   (z-admin-*, z-vis-*, z-mig-*), которых в таблице стилей НЕ БЫЛО. Страницы
   открывались и работали, поэтому дефект не виден ни запуском, ни чтением
   кода: формы сливались в столбик без отступов, таблицы не имели ни границ,
   ни заголовков, флеш-сообщение шло обычным текстом, а пояснение, введённое
   ради предупреждения («сначала сделайте бэкап»), ничем не отличалось
   от прочих строк и не читалось.

   Важная особенность: `z-admin-table` применяется САМ по себе, без `z-table`,
   то есть базового оформления таблицы у него нет вовсе. Поэтому здесь задан
   не только вид строк, но и сам каркас таблицы.

   Границы: цвета — только семантические токены --z-*; светлая и тёмная темы
   переключаются сами, отдельных правил под тему не требуется.
   ========================================================================== */

/* --- Каркас таблицы админки (применяется без .z-table) --- */
.z-admin-table {
  width: 100%;
  border-collapse: collapse;
  font-size: calc(13px * var(--z-text, 1));
}
.z-admin-table th {
  padding: 8px 10px;
  text-align: left;
  font-size: calc(11px * var(--z-text, 1));
  font-weight: var(--z-text-weight);
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--z-muted-fg);
  border-bottom: 1px solid var(--z-border);
  white-space: nowrap;
}
.z-admin-table td {
  padding: 8px 10px;
  border-bottom: 1px solid var(--z-border);
  vertical-align: middle;
}

/* Обёртка таблиц админки: широкие таблицы прокручиваются, а не ломают вёрстку. */
.z-admin-tablewrap { overflow-x: auto; }

/* Ячейки со значением, которое переносить нельзя: идентификаторы, даты, время,
   хеши, IP. Перенос по буквам в таких значениях делает их нечитаемыми. */
.z-admin-cell-nowrap { white-space: nowrap; }
.z-admin-mono {
  font-family: var(--z-mono);
  font-size: calc(12px * var(--z-text, 1));
  color: var(--z-muted-fg);
}
/* Колонка подробностей: длинный текст переносится и не растягивает таблицу. */
.z-admin-details {
  font-size: calc(12px * var(--z-text, 1));
  color: var(--z-muted-fg);
  word-break: break-word;
  min-width: 160px;
}
/* Колонка действий: кнопки прижаты вправо и не переносятся по одной букве. */
.z-admin-actions { white-space: nowrap; text-align: right; }
/* Колонка статуса в списке миграций: метка не разрывается. */
.z-mig-status { white-space: nowrap; }

/* --- Секции админки --- */
/* Панель — карточка с закреплённой шапкой: заголовок, метки состояния,
   кнопка справа. Шапка отделена от тела, чтобы состояния читались сразу. */
.z-admin-panel { padding: 0; overflow: hidden; }
.z-admin-panel__head {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  padding: 14px 18px;
  border-bottom: 1px solid var(--z-border);
  background: var(--z-surface-2);
}
.z-admin-panel__title {
  margin: 0;
  font-size: calc(14px * var(--z-text, 1));
  font-weight: var(--z-heading-weight);
  color: var(--z-heading-fg);
}
/* Распорка: прижимает всё правое (метки, кнопку) к правому краю шапки. */
.z-admin-panel__spacer { flex: 1 1 auto; }

/* --- Плитки счётчиков на обзоре --- */
.z-admin-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 12px;
  margin-bottom: 18px;
}
.z-admin-stat { padding: 14px 16px; }
.z-admin-stat__num {
  font-size: calc(26px * var(--z-text, 1));
  font-weight: var(--z-heading-weight);
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
  color: var(--z-heading-fg);
}
.z-admin-stat__label {
  margin-top: 4px;
  font-size: calc(12px * var(--z-text, 1));
  color: var(--z-muted-fg);
}

/* --- Сообщения и пояснения --- */
/* Текст ошибки загрузки внутри панели: цвет — семантический токен, не литерал.
   Ошибка должна отличаться от обычного текста без чтения содержания. */
.z-admin-err {
  margin: 0;
  padding: 14px 18px;
  font-size: calc(13px * var(--z-text, 1));
  color: var(--z-danger);
}
/* Пояснение под панелью: приглушено, но отделено отступом.
   В migrations.php такими строками вынесено предупреждение «сначала сделайте
   бэкап» — без стиля оно не отличалось от обычного текста. */
.z-admin-hint {
  margin: 12px 18px;
  font-size: calc(12px * var(--z-text, 1));
  color: var(--z-muted-fg);
}
/* Разделитель между повторяющимися формами (sites.php, settings.php).
   Раньше линия задавалась inline-стилем прямо в разметке. */
.z-inline-sep-line {
  border: 0;
  border-top: 1px solid var(--z-border);
  margin: 12px 0;
}

/* --- Флеш-сообщение после редиректа (z_flash) --- */
.z-admin-flash {
  margin: 0 0 14px;
  padding: 10px 14px;
  border: 1px solid var(--z-border);
  border-radius: var(--z-radius-sm);
  background: var(--z-surface-2);
  font-size: calc(13px * var(--z-text, 1));
}
/* Успех и ошибка различаются мягкой заливкой и границей: знак состояния нужен
   и без цвета — поэтому сообщение всегда несёт текст, а не только тон. */
.z-admin-flash--ok {
  background: var(--z-success-soft);
  border-color: var(--z-success);
}
.z-admin-flash--error {
  background: var(--z-danger-soft);
  border-color: var(--z-danger);
}

/* --- Поля формы админки --- */
/* `.z-admin-form` — контейнер полей формы: те же роли, что у `.z-form`
   и `.z-admin-field` ниже. Флек-колонка даёт одинарную ширину полю и
   автоматически ставит их друг под другом. */
.z-admin-form {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
/* Поле формы админки: системс (z-admin-field) и новое (z-field) разделены
   сознательно — обе пары классов применены в разметке, и сводить их в одну
   значило бы править чужие страницы без нужды. */
.z-admin-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.z-admin-field__label {
  font-size: calc(13px * var(--z-text, 1));
  font-weight: var(--z-text-weight);
  color: var(--z-muted-fg);
}
/* Галочка с обязательным подтверждением (бэкап перед миграциями):
   вместе с полем и подписью в одну строку. */
.z-admin-check {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: calc(12px * var(--z-text, 1));
  color: var(--z-fg);
}

/* Панель действий формы админки: кнопки одной строкой, перенос разрешён на узком экране. */
.z-form-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 4px;
}

/* --- Сетка галочек видимости разделов --- */
.z-vis-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 8px;
}
.z-vis-item {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  border: 1px solid var(--z-border);
  border-radius: var(--z-radius-sm);
  background: var(--z-surface);
  font-size: calc(13px * var(--z-text, 1));
  cursor: pointer;
}
.z-vis-item:hover { background: var(--z-surface-2); }
.z-vis-item input { margin: 0; flex: 0 0 auto; }

/* Ссылка бокового меню админки: подсветка активного пункта — приём из системы
   (aria-current ставит сервер при отрисовке). */
.z-admin-nav__link[aria-current] {
  background: var(--z-surface-2);
  color: var(--z-fg);
  font-weight: var(--z-text-weight);
}

/* обёртка для широких таблиц без --adaptive */
.z-tablewrap { overflow-x: auto; }

/* мобильная карточная раскладка адаптивной таблицы */
@media (max-width: 640px) {
  .z-table--adaptive thead { display: none; }
  .z-table--adaptive tr {
    display: block;
    border: 1px solid var(--z-border);
    border-radius: 8px;
    margin-bottom: 10px;
    padding: 8px;
  }
  .z-table--adaptive td {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    padding: 4px 0;
    border: 0;
  }
  .z-table--adaptive td::before {
    content: attr(data-label);
    color: var(--z-muted-fg);
    min-width: 40%;
  }
}

/* --- копирование, маскирование, пары «ключ — значение» --- */

.z-copy {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--z-border);
  border-radius: 6px;
  background: var(--z-surface);
  color: var(--z-muted-fg);
  font-size: calc(12px * var(--z-text, 1));
  line-height: 1;
  cursor: pointer;
  transition: background-color .15s ease, color .15s ease;
}
.z-copy:hover {
  background: var(--z-surface-2);
  color: var(--z-fg);
}

.z-mask {
  font-family: var(--z-mono);
  font-size: calc(13px * var(--z-text, 1));
}
.z-mask[data-masked="true"] { font-size: 0; }
.z-mask[data-masked="true"]::after {
  content: "••••";
  font-size: calc(13px * var(--z-text, 1));
  letter-spacing: .08em;
  color: var(--z-muted-fg);
}

.z-kv {
  display: grid;
  grid-template-columns: 130px 1fr;
  gap: 6px 12px;
  font-size: calc(13px * var(--z-text, 1));
}
.z-kv dt,
.z-kv__k { color: var(--z-muted-fg); }
.z-kv dd,
.z-kv__v { margin: 0; }

/* --- статусы и пустое состояние --- */

.z-status-dot {
  display: inline-block;
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border-radius: 999px;
  background: var(--z-muted-fg);
  box-shadow: 0 0 0 3px var(--z-ring-soft);
}
.z-status-dot--ok {
  background: var(--z-success);
  box-shadow: 0 0 0 3px var(--z-success-soft);
}
.z-status-dot--warn {
  background: var(--z-warning);
  box-shadow: 0 0 0 3px var(--z-warning-soft);
}
.z-status-dot--err {
  background: var(--z-danger);
  box-shadow: 0 0 0 3px var(--z-danger-soft);
}

.z-empty {
  padding: 24px;
  border: 1px dashed var(--z-border);
  border-radius: 10px;
  color: var(--z-muted-fg);
  font-size: calc(13px * var(--z-text, 1));
  text-align: center;
}

/* --- диалог --- */

.z-dialog {
  width: 92vw;
  max-width: 520px;
  margin: auto;
  padding: 0;
  border: 1px solid var(--z-border);
  border-radius: 12px;
  background: var(--z-surface);
  color: var(--z-text-fg);
  box-shadow: var(--z-shadow);
}
.z-dialog::backdrop { background: rgba(0, 0, 0, .35); }

.z-dialog__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--z-border);
}
.z-dialog__head h2,
.z-dialog__title {
  font-size: calc(15px * var(--z-text, 1));
  font-weight: var(--z-heading-weight); color: var(--z-heading-fg);
}
.z-dialog__body {
  padding: 16px;
  font-size: calc(13px * var(--z-text, 1));
}
.z-dialog__foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  padding: 12px 16px;
  border-top: 1px solid var(--z-border);
}

/* Тонирование модального фона в тёмных темах: в альтернативной тёмной
   значение берётся то же, иначе окно выглядит светлее задуманного. */
[data-theme="dark"] .z-dialog::backdrop,
[data-theme="dark-alt"] .z-dialog::backdrop { background: rgba(0, 0, 0, .5); }

@media (prefers-color-scheme: dark) {
  :root[data-theme="auto"] .z-dialog::backdrop { background: rgba(0, 0, 0, .5); }
}

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

.z-hero {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding-top: 24px;
}
.z-hero h1,
.z-hero__title {
  font-size: calc(20px * var(--z-text, 1));
  font-weight: var(--z-heading-weight); color: var(--z-heading-fg);
}
.z-hero p,
.z-hero__sub {
  font-size: calc(13px * var(--z-text, 1));
  color: var(--z-muted-fg);
}

@media (min-width: 720px) {
  .z-hero {
    flex-direction: row;
    align-items: flex-end;
    justify-content: space-between;
  }
}

.z-toolbar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin: 0 0 14px;
}

.z-search {
  position: relative;
  /* Иконка позиционируется от ВСЕГО блока, а не от строки ввода. Раньше блоков
     стало два ряда (поле и строка состояния), центр блока уехал вниз — и иконка
     оказалась ниже центра поля. Теперь .z-search — строка с иконкой и полем,
     а состояние вынесено отдельным элементом (см. .z-search__state ниже). */
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 220px;
  max-width: 320px;
  min-width: 0;
}
/* Строка с иконкой и полем: от неё считается центр иконки. */
.z-search__box { position: relative; flex: 1 1 auto; min-width: 0; }
.z-search input { padding-left: 32px; }
.z-search svg,
.z-search__icon {
  position: absolute;
  top: 50%;
  left: 10px;
  width: 14px;
  height: 14px;
  transform: translateY(-50%);
  color: var(--z-muted-fg);
  pointer-events: none;
}

.z-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border: 1px solid var(--z-border);
  border-radius: 999px;
  background: var(--z-surface);
  color: var(--z-muted-fg);
  font-size: calc(12px * var(--z-text, 1));
}


/* ==========================================================================
   7. КАРТОЧКИ СЕРВИСОВ И ЛОГИСТИКИ
   ========================================================================== */

.z-service {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.z-service__top {
  display: flex;
  align-items: center;
  gap: 10px;
}
.z-service__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  border-radius: 999px;
  border: 1px solid var(--z-border);
  background: var(--z-surface-2);
  font-size: calc(12px * var(--z-text, 1));
  font-weight: 650;
}
.z-service__meta {
  font-size: calc(12px * var(--z-text, 1));
  color: var(--z-muted-fg);
}
.z-service__actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: auto;
}


/* ==========================================================================
   8. АДМИН-КАРКАС
   ========================================================================== */

.z-admin {
  display: grid;
  grid-template-columns: 240px 1fr;
  gap: 0;
  min-height: 100vh;
  background: var(--z-bg);
}

.z-admin__side {
  position: sticky;
  top: 0;
  height: 100vh;
  overflow: auto;
  padding: 18px 14px;
  border-right: 1px solid var(--z-border);
  background: var(--z-surface);
}

.z-admin__nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.z-admin__nav a {
  display: block;
  padding: 7px 10px;
  border-radius: 8px;
  font-size: calc(13px * var(--z-text, 1));
  color: var(--z-muted-fg);
  text-decoration: none;
  transition: background-color .15s ease, color .15s ease;
}
.z-admin__nav a:hover {
  background: var(--z-surface-2);
  color: var(--z-fg);
  text-decoration: none;
}
.z-admin__nav a[aria-current],
.z-admin__nav a.z-active {
  background: var(--z-surface-2);
  color: var(--z-fg);
  font-weight: var(--z-text-weight);
}

.z-admin__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 0;
  border-bottom: 1px solid var(--z-border);
}

.z-admin__content {
  width: 100%;
  max-width: 1100px;
  padding: 20px 24px;
}

/* адаптив админки: одна колонка, сайдбар сверху и статично */
@media (max-width: 900px) {
  .z-admin { grid-template-columns: 1fr; }
  .z-admin__side {
    position: static;
    height: auto;
    border-right: 0;
    border-bottom: 1px solid var(--z-border);
  }
}


/* ==========================================================================
   9. АДАПТИВ
   ========================================================================== */

@media (max-width: 900px) {
  .z-header__inner {
    flex-wrap: wrap;
    height: auto;
    min-height: 56px;
    padding: 8px 0;
    row-gap: 6px;
  }
  .z-nav {
    order: 3;
    width: 100%;
    overflow-x: auto;
    padding-bottom: 2px;
  }
  .z-userchip__role { display: none; }
}

@media (max-width: 640px) {
  .z-wrap { padding: 0 16px; }
  .z-grid,
  .z-grid--wide,
  .z-grid--tight { grid-template-columns: 1fr; }
  .z-themes button { padding: 0 8px; font-size: calc(11px * var(--z-text, 1)); }
}


/* ==========================================================================
   10. ПЕЧАТЬ
   ========================================================================== */

@media print {
  .z-header,
  .z-admin__side,
  .z-foot,
  .z-themes,
  .z-toolbar,
  .z-skip,
  .z-copy { display: none !important; }

  body { background: #fff; color: #000; }
  a { color: #000; text-decoration: underline; }
  .z-card,
  .z-table th,
  .z-table td { border-color: #ccc; }
}

/* ==========================================================================
   11. Служебные дополнения (замыкающие правила)
   ========================================================================== */

/* Атрибут hidden скрывает элемент, даже если у класса задан display:
   на него опираются фильтры поиска, пустые состояния и разметка админки. */
[hidden] { display: none !important; }

/* Аватар-инициал: чип пользователя в шапке и раздел «Дни рождения». */
.z-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  border-radius: 999px;
  border: 1px solid var(--z-border);
  background: var(--z-surface-2);
  color: var(--z-muted-fg);
  font-size: calc(11px * var(--z-text, 1));
  font-weight: 650;
}

/* Мета-колонка чипа пользователя (имя + должность). */
.z-userchip__meta {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.2;
}

/* Чекбокс-строка: синоним .z-check для неадминских форм. */
.z-checkbox {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: calc(13px * var(--z-text, 1));
}
.z-checkbox input[type="checkbox"] {
  width: 16px;
  height: 16px;
  margin: 0;
  accent-color: var(--z-accent);
}

/* Знак wordmark в шапке: не сжимается во flex-строке. */
.z-brand__icon { flex: 0 0 auto; }
.z-brand__mark { flex: 0 0 auto; }

/* Основная колонка главной. */
.z-main { padding: 20px 0 6px; }

/* Отступ 12px (пустое состояние поиска на главной). */
.z-mt-12 { margin-top: 12px; }

/* Строка чекбокса в матрице видимости разделов (admin/visibility.php). */
.z-vis-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  border-radius: 8px;
  cursor: pointer;
}
.z-vis-item:hover { background: var(--z-surface-2); }


/* ==========================================================================
   12. ОБОЛОЧКА V2 — БОКОВОЕ МЕНЮ + ВЕРХНЯЯ ПАНЕЛЬ (главная, профиль, компания)
   ========================================================================== */

/* --- боковое меню --- */
.z-side {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  z-index: 60;
  display: flex;
  flex-direction: column;
  width: 260px;
  background: var(--z-chrome);
  border-right: 1px solid var(--z-border);
}
.z-side__head { padding: 18px 16px 10px; }
.z-side__head .z-brand { gap: 10px; font-size: calc(15px * var(--z-text, 1)); }
.z-brand__text { display: flex; flex-direction: column; line-height: 1.15; }
.z-brand__text small { font-size: calc(11px * var(--z-text, 1)); font-weight: var(--z-text-weight); color: var(--z-muted-fg); }
.z-side__nav {
  flex: 1 1 auto;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px 10px 12px;
}
.z-side__link {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 10px;
  border-radius: 8px;
  color: var(--z-muted-fg);
  font-size: calc(13.5px * var(--z-text, 1));
  font-weight: var(--z-text-weight);
  text-decoration: none;
  transition: background-color .15s ease, color .15s ease;
}
.z-side__link .z-ico { width: 17px; height: 17px; flex: 0 0 auto; }
.z-side__link:hover { background: var(--z-surface); color: var(--z-fg); text-decoration: none; }
.z-side__link.is-active {
  background: var(--z-accent-soft);
  color: var(--z-accent);
  font-weight: var(--z-text-weight);
}
.z-side__foot { padding: 10px; border-top: 1px solid var(--z-border); }

/* затемнение под боковым меню на мобильном */
.z-scrim {
  position: fixed;
  inset: 0;
  z-index: 55;
  background: rgba(0, 0, 0, .42);
  opacity: 0;
  pointer-events: none;
  transition: opacity .18s ease;
}
body.z-side-open .z-scrim { opacity: 1; pointer-events: auto; }

/* --- колонка контента --- */
.z-shell { display: flex; flex-direction: column; min-height: 100vh; margin-left: 260px; }

.z-top {
  position: sticky;
  top: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  gap: 12px;
  height: 56px;
  padding: 0 20px;
  background: var(--z-chrome);
  border-bottom: 1px solid var(--z-border);
}
/* Заголовок страницы в шапке.
   `min-width: 0` и `overflow: hidden` обязательны: без них flex-элемент
   не сжимается меньше своего содержимого, и длинный заголовок выдавливает
   правый блок за край экрана. Замерено на 390 px 8 октября 2026 г.:
   «Новая запись — Договоры» (172 px) распирал документ до 441 px,
   а на странице с заголовком «Платежи» (57 px) переполнения не было —
   поэтому дефект виден не на каждой странице. */
.z-top__title {
  font-size: calc(14px * var(--z-text, 1));
  font-weight: var(--z-heading-weight); color: var(--z-heading-fg);
  white-space: nowrap;
  /* Заголовок уступает место ПЕРВЫМ: `flex: 1 1 auto` с `min-width: 0`
     позволяет ему сжаться ниже ширины своего текста и обрезаться многоточием.
     Обратный порядок (заголовок по содержимому, правый блок сжимаемый) давал
     вылезание меню пользователя за край экрана: замерено 8 октября 2026 г.
     на 390 px — правый блок сжимался до 117 px при нужных 172. */
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Правый блок шапки НЕ сжимается: переключатель темы и меню пользователя
   не имеют сокращённого вида, и обрезанными они выглядят поломкой. Место
   уступает заголовок (его содержимое дублируется в полосе возврата). */
.z-top__right { display: flex; align-items: center; gap: 10px; margin-left: auto; flex: 0 0 auto; }

.z-side-toggle { display: none; }

.z-iconbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 8px;
  background: transparent;
  color: var(--z-muted-fg);
  cursor: pointer;
  transition: background-color .15s ease, color .15s ease;
}
.z-iconbtn:hover { background: var(--z-surface); color: var(--z-fg); }
.z-iconbtn .z-ico { width: 17px; height: 17px; }

/* поиск в верхней панели и в шапках секций */
.z-search--top { flex: 0 1 240px; max-width: 240px; }
.z-search--sm { flex: 0 1 230px; max-width: 230px; }

/* --- переключатель темы иконками --- */
.z-themes--icons button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 26px;
  padding: 0;
}
.z-themes--icons button .z-ico { width: 15px; height: 15px; }

/* --- меню пользователя --- */
.z-usermenu { position: relative; }
.z-usermenu__btn {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 36px;
  padding: 0 8px 0 5px;
  border: 1px solid transparent;
  border-radius: 999px;
  background: transparent;
  color: var(--z-text-fg);
  font-size: calc(13px * var(--z-text, 1));
  font-weight: var(--z-text-weight);
  cursor: pointer;
  transition: background-color .15s ease;
}
.z-usermenu__btn:hover { background: var(--z-surface); }
.z-usermenu__name { max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.z-usermenu__chev { width: 14px; height: 14px; color: var(--z-muted-fg); }

.z-menu {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 90;
  min-width: 236px;
  padding: 6px;
  background: var(--z-surface);
  border: 1px solid var(--z-border);
  border-radius: 12px;
  box-shadow: 0 10px 30px rgba(15, 14, 13, .12);
}
.z-menu__head { padding: 8px 10px 7px; }
.z-menu__name { font-size: calc(13.5px * var(--z-text, 1)); font-weight: var(--z-heading-weight); color: var(--z-heading-fg); }
.z-menu__role { margin-top: 1px; font-size: calc(12px * var(--z-text, 1)); color: var(--z-muted-fg); }
.z-menu__sep { height: 1px; margin: 6px 4px; background: var(--z-border); }
.z-menu__item {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 7px 10px;
  border-radius: 8px;
  color: var(--z-text-fg);
  font-size: calc(13px * var(--z-text, 1));
  text-decoration: none;
}
.z-menu__item .z-ico { width: 15px; height: 15px; color: var(--z-muted-fg); }
.z-menu__item:hover { background: var(--z-surface-2); text-decoration: none; }
.z-menu__item--danger,
.z-menu__item--danger .z-ico { color: var(--z-danger); }
.z-menu__item--danger:hover { background: var(--z-danger-soft); }

/* --- контент --- */
.z-main { flex: 1 1 auto; width: 100%; padding: 22px 24px 28px; }

.z-pagehead {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin: 0 0 18px;
}
.z-pagehead h1 { font-size: calc(22px * var(--z-text, 1)); }
.z-pagehead__sub { margin-top: 3px; font-size: calc(13px * var(--z-text, 1)); color: var(--z-muted-fg); }
/* Действия справа в шапке страницы: кнопки и ссылки одной строкой.
   Нужен явно: без правила блок верстался бы случаем (flex-элемент шапки
   без своих отступов), а такой дефект виден только на глаз. */
.z-pagehead__actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

/* --- карточка-секция --- */
.z-sec {
  margin-bottom: 18px;
  background: var(--z-surface);
  border: 1px solid var(--z-border);
  border-radius: 12px;
  scroll-margin-top: 72px;
  overflow: hidden;
}
.z-sec__head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--z-border);
}
.z-sec__ico {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  border-radius: 9px;
  background: var(--z-accent-soft);
  color: var(--z-accent);
}
.z-sec__ico .z-ico { width: 18px; height: 18px; }
.z-sec__titles { flex: 1 1 auto; min-width: 0; }
.z-sec__title { font-size: calc(15px * var(--z-text, 1)); font-weight: var(--z-heading-weight); color: var(--z-heading-fg); }
.z-sec__desc { margin-top: 1px; font-size: calc(12.5px * var(--z-text, 1)); color: var(--z-muted-fg); }
.z-sec__tools { flex: 0 0 auto; margin-left: auto; display: flex; align-items: center; gap: 8px; }
.z-sec__body { padding: 16px 18px; }
.z-sec__body--flush { padding: 0; }
.z-sec__body--flush .z-table th:first-child,
.z-sec__body--flush .z-table td:first-child { padding-left: 18px; }
.z-sec__body--flush .z-table th:last-child,
.z-sec__body--flush .z-table td:last-child { padding-right: 18px; }

/* --- таблицы v2: заголовки, ячейки, действия --- */
.z-table th {
  text-transform: none;
  letter-spacing: 0;
  font-size: calc(12px * var(--z-text, 1));
  font-weight: var(--z-text-weight);
}
.z-table td { padding: 9px 12px; }
.z-table tbody tr:last-child td { border-bottom: 0; }

.z-cell-title { display: inline-flex; align-items: center; gap: 10px; min-width: 0; }
.z-cell-stack { display: flex; flex-direction: column; min-width: 0; line-height: 1.3; }
.z-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  border-radius: 8px;
  border: 1px solid var(--z-border);
  background: var(--z-surface-2);
  color: var(--z-muted-fg);
  font-size: calc(10.5px * var(--z-text, 1));
  font-weight: 700;
  letter-spacing: .02em;
}
.z-td-actions { white-space: nowrap; text-align: right; }
.z-th-actions { text-align: right; }
.z-ellipsis { max-width: 340px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.z-cell-contacts { max-width: 280px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.z-table td .z-secret { display: inline-flex; align-items: center; gap: 6px; }

/* --- вкладки категорий --- */
.z-tabs {
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--z-border);
  overflow-x: auto;
}
.z-tab {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: transparent;
  color: var(--z-muted-fg);
  font-size: calc(13px * var(--z-text, 1));
  font-weight: var(--z-text-weight);
  white-space: nowrap;
  cursor: pointer;
  transition: background-color .15s ease, color .15s ease, border-color .15s ease;
}
.z-tab:hover { background: var(--z-surface-2); color: var(--z-fg); }
.z-tab.is-active {
  background: var(--z-surface-2);
  border-color: var(--z-border);
  color: var(--z-text-fg);
  font-weight: var(--z-text-weight);
}
.z-tab__count { font-size: calc(11px * var(--z-text, 1)); color: var(--z-muted-fg); }

/* --- контакты: блоки на всю ширину --- */
.z-contacts__group { margin-bottom: 18px; }
.z-contacts__group:last-of-type { margin-bottom: 8px; }
.z-contacts__gtitle {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 8px 2px;
  font-size: calc(12px * var(--z-text, 1));
  font-weight: var(--z-heading-weight); color: var(--z-heading-fg);
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--z-muted-fg);
}
.z-contacts__gtitle .z-ico { width: 15px; height: 15px; }
.z-contacts__list { display: flex; flex-direction: column; gap: 8px; }

.z-contact {
  display: grid;
  grid-template-columns: 34px 1fr;
  column-gap: 12px;
  padding: 12px 14px;
  background: var(--z-bg);
  border: 1px solid var(--z-border);
  border-radius: 10px;
}
.z-contact__ic {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 9px;
  background: var(--z-surface);
  border: 1px solid var(--z-border);
  color: var(--z-muted-fg);
}
.z-contact__ic .z-ico { width: 16px; height: 16px; }
.z-contact__main { min-width: 0; }
.z-contact__title { font-size: calc(13.5px * var(--z-text, 1)); font-weight: var(--z-text-weight); }
.z-contact__lines { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 14px; margin-top: 2px; font-size: calc(13px * var(--z-text, 1)); }
.z-contact__phone { font-weight: var(--z-text-weight); white-space: nowrap; }
.z-contact__mono { font-family: var(--z-mono); font-size: calc(13px * var(--z-text, 1)); }
.z-contact__mono--lg { font-size: calc(14px * var(--z-text, 1)); font-weight: var(--z-text-weight); }
.z-contact__hint { font-size: calc(12.5px * var(--z-text, 1)); color: var(--z-muted-fg); }
.z-contact__note {
  grid-column: 2;
  justify-self: end;
  margin-top: 8px;
  padding: 4px 10px;
  border: 1px solid var(--z-border);
  border-radius: 999px;
  background: var(--z-surface-2);
  color: var(--z-muted-fg);
  font-size: calc(12px * var(--z-text, 1));
}
.z-contact--note { background: var(--z-accent-soft); border-color: transparent; }
.z-contact--note .z-contact__ic { color: var(--z-accent); background: var(--z-surface); }
.z-contact__text { margin-top: 2px; font-size: calc(13px * var(--z-text, 1)); line-height: 1.5; color: var(--z-text-fg); }

.z-footnote { margin-top: 4px; font-size: calc(12.5px * var(--z-text, 1)); color: var(--z-muted-fg); }

/* --- футер v2 --- */
.z-foot {
  margin-top: auto;
  padding: 18px 24px;
  border-top: 1px solid var(--z-border);
  color: var(--z-muted-fg);
  font-size: calc(12.5px * var(--z-text, 1));
}
.z-foot__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px 24px;
}
.z-foot__links { display: inline-flex; gap: 14px; }

/* --- кнопка копирования: компактная, без постоянной рамки --- */
.z-copy {
  width: 24px;
  height: 24px;
  border: 1px solid transparent;
  border-radius: 7px;
  background: transparent;
  color: var(--z-muted-fg);
}
.z-copy:hover { background: var(--z-surface-2); color: var(--z-fg); border-color: var(--z-border); }
.z-copy.is-flashing { color: var(--z-success); border-color: var(--z-success); }
.z-copy.is-copy-error { color: var(--z-danger); border-color: var(--z-danger); }

/* технический класс для буфера обмена */
.z-offscreen { position: fixed; left: -9999px; top: 0; }

/* --- адаптив оболочки --- */
@media (max-width: 900px) {
  .z-side { transform: translateX(-100%); transition: transform .2s ease; }
  body.z-side-open .z-side { transform: none; box-shadow: 0 0 40px rgba(0, 0, 0, .25); }
  .z-shell { margin-left: 0; }
  .z-side-toggle { display: inline-flex; }
  /* Зазор уменьшен с 10 до 8 px: на 390 px шапке не хватало ровно 2 px,
   и документ получал горизонтальную прокрутку. Расчёт: кнопка меню 29
   + заголовок 156 + правый блок 157 + два зазора + отступы 28 = 392 при
   экране 390. Замерено 8 октября 2026 г.: при `gap: 0` переполнение
   исчезало, отключение отступов на него не влияло. */
.z-top { padding: 0 14px; gap: 8px; }
  .z-main { padding: 16px 14px 24px; }
  .z-sec__head { padding: 12px 14px; flex-wrap: wrap; }
  .z-sec__tools { margin-left: 0; width: 100%; }
  .z-search--sm { flex: 1 1 100%; max-width: none; }
  .z-sec__body--flush .z-table th:first-child,
  .z-sec__body--flush .z-table td:first-child { padding-left: 14px; }
  .z-sec__body--flush .z-table th:last-child,
  .z-sec__body--flush .z-table td:last-child { padding-right: 14px; }
  .z-ellipsis, .z-cell-contacts { max-width: 180px; }
}
@media (max-width: 720px) {
  .z-search--top { display: none; }
  .z-usermenu__name { display: none; }
  .z-top__title { font-size: calc(13px * var(--z-text, 1)); }
  .z-pagehead h1 { font-size: calc(19px * var(--z-text, 1)); }
}

@media print {
  .z-side, .z-top, .z-scrim { display: none !important; }
  .z-shell { margin-left: 0; }
}


/* ==========================================================================
   13. ФУТЕР V2, ПРОФИЛЬ, КОМПАНИЯ
   ========================================================================== */

.z-foot__inner { display: flex; flex-direction: column; gap: 10px; }
.z-foot__user { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.z-foot__name {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: calc(13px * var(--z-text, 1));
  font-weight: var(--z-text-weight);
  color: var(--z-text-fg);
}
.z-foot__pos { font-size: calc(12.5px * var(--z-text, 1)); color: var(--z-muted-fg); }
.z-foot__mid {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 8px;
  padding: 9px 0;
  border-top: 1px solid var(--z-border);
  border-bottom: 1px solid var(--z-border);
  font-size: calc(12.5px * var(--z-text, 1));
}
.z-foot__bottom {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 6px 18px;
}
.z-foot__meta { display: inline-flex; align-items: center; gap: 8px; }

.z-online-dot {
  display: inline-block;
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border-radius: 999px;
  background: var(--z-success);
  box-shadow: 0 0 0 3px var(--z-success-soft);
}

.z-avatar--accent { background: var(--z-accent); color: #fff; border-color: transparent; }
/* Текст на акцентном фоне: в тёмных темах акцент светлый, поэтому белый
   текст на нём не читается — берётся цвет «на акценте». */
[data-theme="dark"] .z-avatar--accent,
[data-theme="dark-alt"] .z-avatar--accent { color: var(--z-on-primary); }
@media (prefers-color-scheme: dark) {
  :root[data-theme="auto"] .z-avatar--accent { color: var(--z-on-primary); }
}
.z-avatar--lg { width: 56px; height: 56px; font-size: calc(22px * var(--z-text, 1)); border-radius: 16px; }

.z-inline-sep { color: var(--z-muted-fg); }

/* страницы «Профиль» и «Компания» */
.z-stack { display: flex; flex-direction: column; gap: 16px; }
.z-grid-2 {
  display: grid;
  gap: 16px;
  grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
  margin-bottom: 18px;
}
@media (max-width: 860px) {
  .z-grid-2 { grid-template-columns: 1fr; }
}

.z-profile-hero { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.z-profile-hero__meta { min-width: 0; flex: 1 1 240px; }
.z-profile-hero__name { font-size: calc(20px * var(--z-text, 1)); font-weight: var(--z-heading-weight); color: var(--z-heading-fg); }
.z-profile-hero__sub { margin-top: 2px; font-size: calc(13px * var(--z-text, 1)); color: var(--z-muted-fg); }
.z-profile-badges { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 8px; }

.z-dl { display: flex; flex-direction: column; }
.z-dl__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 9px 0;
  border-bottom: 1px solid var(--z-border);
  font-size: calc(13px * var(--z-text, 1));
}
.z-dl__row:last-child { border-bottom: 0; }
.z-dl__k { color: var(--z-muted-fg); flex: 0 0 auto; }
.z-dl__v {
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
  min-width: 0;
  text-align: right;
}

.z-company-hero { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.z-company-hero__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  border-radius: 12px;
  background: var(--z-accent-soft);
  color: var(--z-accent);
  flex: 0 0 auto;
}
.z-company-hero__mark .z-ico { width: 24px; height: 24px; }
.z-company-hero__name { font-size: calc(18px * var(--z-text, 1)); font-weight: var(--z-heading-weight); color: var(--z-heading-fg); }
.z-company-hero__sub { margin-top: 2px; font-size: calc(13px * var(--z-text, 1)); color: var(--z-muted-fg); }

.z-company-links { display: flex; flex-direction: column; }
.z-company-links a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 0;
  border-bottom: 1px solid var(--z-border);
  font-size: calc(13px * var(--z-text, 1));
}
.z-company-links a:last-child { border-bottom: 0; }
.z-company-links a::after { content: "↗"; color: var(--z-muted-fg); font-size: calc(12px * var(--z-text, 1)); }


/* ==========================================================================
   15. РАЗДЕЛ «ДОСТУПЫ»: таблица, форма записи, сортировка, страницы
   ========================================================================== */

/* Панель раздела: «Добавить запись» и подсказка; отступ от шапки секции. */
.z-pw-tools { flex-wrap: wrap; align-items: center; padding: 12px 16px 0; }

/* Форма записи: карточка с сеткой полей в две колонки на широком экране. */
.z-pw-form { margin: 12px 16px 0; }
.z-pw-form__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 12px 14px;
  padding: 12px 0 4px;
}
.z-pw-form .z-field--full { grid-column: 1 / -1; }
.z-pw-form__actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  padding-top: 12px;
  border-top: 1px solid var(--z-border);
}

/* Ссылка сортировки в заголовке: подпись + стрелка направления. */
.z-th-sort { white-space: nowrap; }
.z-sort {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--z-muted-fg);
  font-size: calc(12px * var(--z-text, 1));
  font-weight: var(--z-text-weight);
  letter-spacing: .02em;
  text-transform: uppercase;
}
.z-sort:hover { color: var(--z-fg); text-decoration: none; }
.z-sort__arrow { font-size: calc(11px * var(--z-text, 1)); color: var(--z-accent); }

/* Форма действия в строке таблицы (удаление): не ломает инлайн-раскладку. */
.z-inline-form { display: inline; margin: 0; }

/* Постраничный вывод: кнопки и счётчик записей. */
.z-pager {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 12px 16px;
  border-top: 1px solid var(--z-border);
}
.z-pager__info { font-size: calc(12.5px * var(--z-text, 1)); color: var(--z-muted-fg); }
.z-pager .z-btn[aria-disabled="true"] { opacity: .5; cursor: default; }


/* ==========================================================================
   14. ТОЧЕЧНЫЕ ИСПРАВЛЕНИЯ И ОБОЛОЧКА (замыкающие правила)
   ========================================================================== */

/* Кнопка бокового меню — только на мобильной ширине (перебивает .z-iconbtn). */
.z-iconbtn.z-side-toggle { display: none; }
@media (max-width: 900px) {
  .z-iconbtn.z-side-toggle { display: inline-flex; }
}

/* Поворот шеврона в меню пользователя при открытии. */
.z-usermenu__chev { transition: transform .18s ease; }
.z-usermenu__btn[aria-expanded="true"] .z-usermenu__chev { transform: rotate(180deg); }

/* Имя в меню пользователя не сжимается (кнопка не прогибается под соседей). */
.z-top .z-usermenu { flex: 0 0 auto; }
.z-usermenu__btn { flex: 0 0 auto; }
.z-usermenu__name { flex: 0 0 auto; max-width: 220px; }
.z-search--top { min-width: 160px; }

/* Fallback-диалоги (<dialog> без showModal): центрируем и затемняем фон. */
.z-dialog--fallback {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    margin: 0;
    z-index: 120;
    box-shadow: 0 24px 64px rgba(15, 14, 13, .3);
}
.z-dialog--fallback::before {
    content: "";
    position: fixed;
    inset: 0;
    background: rgba(15, 14, 13, .45);
    z-index: -1;
}
/* ==========================================================================
   16. СОРТИРОВКА, ИЗБРАННОЕ И ЧЕРЕДОВАНИЕ СТРОК (замыкающие правила)
   ========================================================================== */

/* Кнопка-заголовок: приём DataTable из shadcn, перенесённый вручную.
   Стили .z-sort уже заданы в разделе 15 для серверной сортировки «Доступов» —
   здесь кнопка того же вида, но клиентская. */
.z-th-sort .z-sort {
  border: 0;
  background: transparent;
  padding: 0;
  font: inherit;
  letter-spacing: .02em;
  text-transform: uppercase;
  cursor: pointer;
}
.z-th-sort .z-sort:hover { color: var(--z-fg); text-decoration: underline; text-underline-offset: 2px; }
.z-th-sort .z-sort__label { pointer-events: none; }
.z-th-sort[aria-sort="ascending"] .z-sort,
.z-th-sort[aria-sort="descending"] .z-sort { color: var(--z-fg); }

/* Кнопка «Закрепить» / «Открепить» в строке таблицы. */
.z-fav-form { display: inline; margin: 0 0 0 4px; }
.z-fav-btn {
  border-color: transparent;
  background: transparent;
  color: var(--z-muted-fg);
}
.z-fav-btn:hover:not(:disabled) { background: var(--z-surface-2); color: var(--z-fg); }
.z-fav-btn.is-pinned { color: var(--z-accent); border-color: var(--z-border); }
.z-fav-btn span { font-size: calc(13px * var(--z-text, 1)); line-height: 1; }

/* Блок «Избранное» — карточка над списком раздела. */
.z-fav { margin: 0 0 12px; }
.z-fav .z-card__head { gap: 12px; flex-wrap: wrap; }
.z-fav .z-search { min-width: 180px; }

/* Значения закреплённой строки: у доступа и кабинета «Логистики» их два —
   логин и пароль. Один столбик внутри ячейки: на телефоне таблица становится
   набором строк (display: flex), и два отдельных <div> встали бы рядом,
   вылезая за ячейку. */
.z-fav-values { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.z-fav-values__row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
.z-fav-values__k { flex: 0 0 auto; }

/* Небольшой отступ под списком (подсказка о закреплённых записях). */
.z-mt-8 { margin-top: 8px; }

/* ==========================================================================
   17. УРОВНИ ДОСТУПА К ЗАПИСЯМ «ДОСТУПОВ» (замыкающие правила)
   ========================================================================== */

/* Чип уровня доступа: цвет — семантические токены темы, не литералы.
   view — нейтральный, secret — предупреждение (правка одного поля),
   full — акцент (полные права на запись). */
.z-chip--level { font-weight: var(--z-text-weight); }
.z-chip--view { background: var(--z-surface-2); color: var(--z-text-fg); }
.z-chip--secret { background: var(--z-warning-soft); color: var(--z-warning); }
.z-chip--full { background: var(--z-accent-soft); color: var(--z-accent); }

/* Запись, которой поделились: отличаем от своих, чтобы владелец был очевиден. */
.z-chip--shared { background: var(--z-surface-2); color: var(--z-muted-fg); }

/* Страница выдачи доступа. */
.z-pw-share .z-sec__body { padding-top: 0; }
.z-share-form { margin-top: 8px; }
.z-select--sm { display: inline-block; width: auto; min-width: 150px; min-height: 30px; padding: 4px 8px; }
.z-share-current { margin-left: 6px; }

/* Пояснение уровней под таблицей выбора. */
.z-share-legend {
  margin-top: 12px;
  padding: 10px 12px;
  border: 1px solid var(--z-border);
  border-radius: 8px;
  background: var(--z-surface-2);
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.z-share-legend__row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.z-share-subtitle { margin: 16px 0 8px; font-size: calc(14px * var(--z-text, 1)); }
.z-share-list { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 4px; }
.z-share-list li { font-size: calc(13px * var(--z-text, 1)); }
/* ==========================================================================
   18. ФОРМЫ ПО ПРИЁМАМ SHADCN (FieldGroup + Field) И ДИАЛОГ ДЕЙСТВИЯ
   Библиотека не подключается: приёмы перенесены в классы z-*.
   FieldGroup  -> .z-fieldset (группа связанных полей) + .z-legend (заголовок группы)
   Field       -> .z-field (подпись + контрол + подсказка)
   FieldLabel  -> .z-label (синоним .z-field__label)
   FieldDescription -> .z-hint (синоним .z-field__hint)
   Кнопки диалога -> .z-formactions
   ========================================================================== */

/* Группа полей: рамка-обёртка с легендой, как FieldSet в shadcn. */
.z-fieldset {
  border: 1px solid var(--z-border);
  border-radius: 10px;
  padding: 14px 16px 16px;
  margin: 0 0 14px;
  min-width: 0;
}
.z-legend {
  padding: 0 6px;
  font-size: calc(13px * var(--z-text, 1));
  font-weight: var(--z-heading-weight);
  color: var(--z-heading-fg);
}

/* Сетка полей: две колонки на широком экране, одна на узком. */
.z-formgrid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 12px 14px;
  margin-top: 10px;
}
.z-formgrid > .z-field--full { grid-column: 1 / -1; }

/* Подпись поля и подсказка: те же роли, что FieldLabel и FieldDescription. */
.z-label {
  font-size: calc(13px * var(--z-text, 1));
  font-weight: var(--z-text-weight);
  color: var(--z-muted-fg);
}
.z-hint {
  font-size: calc(12px * var(--z-text, 1));
  color: var(--z-muted-fg);
}

/* Панель действий формы: кнопки прижаты к левому краю, разделитель — тонкая линия. */
.z-formactions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  padding-top: 14px;
  margin-top: 4px;
  border-top: 1px solid var(--z-border);
}
.z-formactions__sep { flex: 1 1 auto; }

/* Форма реестра писем: карточка с внутренними отступами. */
.z-mail-form { padding: 16px; }

/* Диалог подтверждения: заголовок, описание, кнопки — как Dialog в shadcn. */
.z-dialog__desc { font-size: calc(13px * var(--z-text, 1)); color: var(--z-muted-fg); margin-top: 4px; }
.z-dialog__actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 16px;
}

/* Приглушённая метка для чужой записи в списке. */
.z-badge--muted { background: var(--z-surface-2); color: var(--z-muted-fg); }

/* ==========================================================================
   19. РЕЕСТР КОРРЕСПОНДЕНЦИИ: ЗАКРЕПЛЁННАЯ ШАПКА, ПАНЕЛЬ ДЕЙСТВИЙ, ВЫБОР СТРОКИ
   ========================================================================== */

/* --- Панель действий и поиск: ОДНА строка ---
   Кнопки идут первыми, окно поиска прижато к правому краю.
   Причина, по которой «Копировать» уезжала под «Создать письмо»: у тулбара
   был flex-wrap: wrap без своего flex-basis, поэтому он сжимался, и при
   нехватке места последняя кнопка переносилась на вторую строку, а поиск
   забирал под себя всю ширину (flex: 0 1 320px). Теперь тулбар неделим
   (flex: 0 0 auto; nowrap), а место уступает поиск — он ужимается до 140px. */
.z-mail-tools { align-items: center; flex-wrap: nowrap; gap: 10px; flex: 0 1 auto; min-width: 0; }
/* На узком месте шапка переносится сама — лучше вторая строка, чем выход за край. */
[data-mail-registry] .z-sec__head { flex-wrap: wrap; }
.z-mail-toolbar {
  display: flex;
  align-items: center;
  gap: 8px;
  /* Кнопки — единый несжимаемый блок и НЕ переносятся. Раньше тулбар мог
     сжиматься (flex-wrap: wrap без flex-basis) — из-за этого при нехватке места
     «Копировать» уезжала на вторую строку под «Создать письмо». */
  flex: 0 0 auto;
  flex-wrap: nowrap;
}
/* Поиск забирает остаток строки справа и ужимается до 140px, но не переносится. */
.z-mail-tools .z-search { margin-left: auto; flex: 1 1 140px; max-width: 340px; min-width: 140px; }
.z-mail-toolbar .z-btn:disabled { opacity: .5; cursor: default; }
/* Подсказка о выбранной строке: элемент остаётся в разметке для JS
   (initRowSelect обновляет её текст), но на экране не показывается и места
   не занимает — атрибут hidden и правило [hidden] { display: none !important }.
   Правила ниже нужны только на случай, если блок вернут в вывод. */
.z-mail-toolbar__hint { flex: 1 1 100%; order: 9; }
.z-mail-toolbar__hint[hidden] { display: none !important; }

/* --- Закрепление ---
   Две причины, по которым прежний вариант «катался» вместе со списком:
     1) у .z-sec стоит overflow: hidden — он ломает position: sticky у потомков;
     2) у обёртки таблицы .z-tablewrap стоит overflow-x: auto — sticky-заголовки
        закрепляются ВНУТРИ этой прокручиваемой коробки, а не от окна, отчего
        уезжали вниз на высоту её начала.
   Здесь обе причины сняты точечно, только для реестра: секции разрешено
   переполнение, а у таблицы реестра горизонтальная прокрутка отключена —
   колонок 13, и на десктопе они помещаются; на узких экранах закрепление
   выключается целиком, а таблица переходит в карточный вид (z-table--adaptive). */
.z-sec--stickyhead { overflow: visible; }
.z-mail-tablewrap { overflow: visible; }

/* Шапка секции (заголовок, кнопки, поиск) закрепляется под топбаром (56px). */
.z-sec--stickyhead .z-sec__head {
  position: sticky;
  top: 56px;
  z-index: 30;
  background: var(--z-surface);
  border-radius: 0;
}

/* Заголовки колонок закрепляются под шапкой секции.
   border-collapse: collapse у .z-table ломает position: sticky у <th> в Chromium
   (браузер не может закрепить ячейку схлопнутой таблицы), поэтому именно
   в таблице реестра схлопывание отключено — рамки рисуются на th и td,
   а не на границах ячеек. */
.z-mail-table { border-collapse: separate; border-spacing: 0; }
.z-mail-table thead th { border-bottom: 1px solid var(--z-border); }
/* Смещение — из переменной, которую считает initStickyHead() по фактической
   высоте топбара и шапки секции. Резервное значение (113px) нужно только до
   первого замера и на случай отключённого JS: оно равно прежнему расчёту. */
.z-sec--stickyhead .z-mail-table thead th {
  position: sticky;
  top: var(--z-mail-head-offset, 113px);
  z-index: 29;
  background: var(--z-surface);
  box-shadow: inset 0 -1px 0 var(--z-border);
}

/* Колонка выбора строки: узкая, без подписи. */
.z-th-pick, .z-td-pick {
  width: 34px;
  padding-left: 18px;
  padding-right: 4px;
}
.z-td-pick { vertical-align: middle; }

/* Маркер выбора: пустой кружок, у выбранной строки — заполненный токеном акцента. */
.z-pick {
  display: inline-block;
  width: 14px;
  height: 14px;
  border: 1px solid var(--z-border);
  border-radius: 999px;
  background: var(--z-surface);
}
tr.is-selected .z-pick {
  border-color: var(--z-accent);
  background: var(--z-accent);
  box-shadow: inset 0 0 0 3px var(--z-surface);
}

/* Выбранная строка: заметная подложка, отличается и от зебры, и от hover. */
.z-table tbody tr.is-selected { background: var(--z-accent-soft); }
.z-table tbody tr.is-selected:hover { background: var(--z-accent-soft); }
.z-table tbody tr.z-mail-row { cursor: pointer; }

/* Числовая колонка MB: по правому краю, цифры одной ширины. */
.z-num { text-align: right; font-variant-numeric: tabular-nums; }

/* Узкие экраны: шапка переносится, закрепление перекрывало бы список —
   отключаем его и отдаём таблицу карточному виду. */
@media (max-width: 900px) {
  .z-sec--stickyhead .z-sec__head,
  .z-sec--stickyhead .z-mail-table thead th {
    position: static;
  }
  .z-mail-toolbar__hint { margin-left: 0; }
}
/* Панель реестра на узком экране: поиск — своей строкой во всю ширину,
   кнопки выше него. Порог 620px, а НЕ 900px: замерено в браузере — уже с 630px
   кнопки и поиск уживаются в одной строке, и переносить их там незачем.
   Здесь тулбару снова разрешён перенос и сжатие: три кнопки в одну строку
   не влезают, и аккуратный перенос лучше, чем выход за край. */
@media (max-width: 620px) {
  .z-mail-tools { width: 100%; flex-wrap: wrap; }
  .z-mail-toolbar { flex: 1 1 100%; flex-wrap: wrap; }
  .z-mail-tools .z-search { margin-left: 0; flex: 1 1 100%; max-width: none; min-width: 0; }
}
/* --- Поле файла в редакторе письма и кнопка загрузки в списке ---
   Кнопка «Загрузить»/«Заменить» — это label вокруг input[type=file]:
   нажатие открывает системный выбор файла (браузерное поведение, JS не нужен). */
.z-filelabel { position: relative; overflow: hidden; cursor: pointer; }
.z-filelabel input[type="file"] {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}

/* Карточка прикреплённого файла: имя и размер. */
.z-filecard {
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  padding: 8px 10px;
  border: 1px solid var(--z-border);
  border-radius: 8px;
  background: var(--z-surface-2);
  max-width: 100%;
}
.z-filecard__name { font-size: calc(13px * var(--z-text, 1)); font-weight: var(--z-text-weight); }
.z-filecard__size { font-size: calc(12px * var(--z-text, 1)); color: var(--z-muted-fg); white-space: nowrap; }

/* Ряд действий с файлом. */
.z-fileactions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 4px; }
/* --- Живой поиск: индикатор состояния в поле поиска ---
   Приём TanStack Table «фильтр в процессе набора», перенесённый вручную:
   пока идёт запрос — точка-индикатор, при ошибке — красный текст,
   при результате — число найденных записей. */
/* Состояние живого поиска — отдельным блоком, чтобы не сдвигать поле. */
.z-search__state {
  flex: 0 0 auto;
  font-size: calc(12px * var(--z-text, 1));
  color: var(--z-muted-fg);
  white-space: nowrap;
}
.z-search__state.is-busy { color: var(--z-accent); }
.z-search__state.is-error { color: var(--z-danger); }

/* Пока идёт запрос, поле чуть приглушено — видно, что результат обновится. */
.z-search.is-busy .z-input { opacity: .75; }

/* Подсветка совпадений: mark внутри ячейки, цвет — токены темы. */
/* Подсветка найденных фрагментов: тёплый оранжевый токен (--z-hit-bg),
   одинаково заметный в светлой и тёмной темах. Раньше фон брался из
   --z-accent-soft, и после приведения тёмной темы к BatteryPassport
   подсветка стала голубой — неотличимой от обычного акцента интерфейса. */
mark.z-hit {
  background: var(--z-hit-bg);
  color: var(--z-hit-fg);
  border-radius: 3px;
  padding: 0 2px;
}
/* Запрет переноса в ячейке: дата, название компании. Значения короткие,
   поэтому перенос только мешает читать строку. */
.z-nw { white-space: nowrap; }
/* --- Редактор письма: крестик закрытия, ряд файла, панель действий --- */

/* Крестик в правом верхнем углу — та же роль, что у «Отмена»: уход без сохранения.
   Квадратная кнопка-иконка, размер как у .z-btn, чтобы стояла в строке заголовка. */
.z-close {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border: 1px solid var(--z-border);
  border-radius: 8px;
  background: var(--z-surface);
  color: var(--z-muted-fg);
  font-size: calc(15px * var(--z-text, 1));
  line-height: 1;
  text-decoration: none;
  transition: background-color .15s ease, color .15s ease, border-color .15s ease;
}
.z-close:hover {
  background: var(--z-surface-2);
  color: var(--z-fg);
  text-decoration: none;
}
.z-close:focus-visible { outline: 2px solid var(--z-ring); outline-offset: 2px; }

/* Ряд файла: карточка вложения и кнопки — в одной строке, с переносом на узких. */
.z-filerow { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* Карточка файла: имя не раздувает строку — обрезается с подсказкой в title. */
.z-filecard { max-width: 100%; }
.z-filecard__name {
  max-width: 340px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.z-filecard--empty { color: var(--z-muted-fg); font-weight: 400; }

/* Панель действий формы по правому краю окна: «Отмена», затем «Сохранить». */
.z-formactions--end { justify-content: flex-end; }
/* --- Подтверждение удаления вводом слова (диалог в редакторе письма) ---
   .z-mono уже объявлен выше, здесь только особенности поля подтверждения. */
.z-dialog .z-mono { letter-spacing: .12em; text-transform: uppercase; }
/* Неверное слово — рамка красная (пока пользователь печатает). */
.z-input.is-error { border-color: var(--z-danger); }
.z-dialog__actions .z-btn[disabled] { opacity: .5; cursor: default; }

/* ==========================================================================
   Страница входа (templates/auth.htm)
   ==========================================================================
   ДЕФЕКТ, который здесь исправляется: разметка страницы входа использовала
   девять классов (.z-auth-wrap, .z-auth-card, .z-auth-form и другие), которых
   в таблице стилей НЕ БЫЛО. Страница открывалась и работала — поэтому дефект
   не бросался в глаза, но собственной вёрстки у неё не было: карточка не
   центрировалась, поля не выравнивались, кнопка шла вплотную.

   Проверено перебором всех классов шаблона против правил в z.css: 9 из 24
   отсутствовали. Такие дефекты не видны ни компилятору, ни проверке
   «страница отвечает 200».

   Оформление построено по приёмам shadcn/ui (справочник —
   docs/04_research/SHADCN_LOGIN_REFERENCE.md), но на токенах проекта --z-*:
     * карточка: радиус --z-radius, граница --z-border, поверхность --z-surface;
     * поля: высота 36px (у shadcn ровно 36px, а не 40px — замерено по реестру);
     * кольцо фокуса: 3px с мягким цветом (--z-ring-soft), как в shadcn;
     * кнопка: 40px высотой (в shadcn кнопка выше поля — так и сделано);
     * состояние загрузки: вращающийся индикатор, без спиннера-картинки.
   Цвета берутся ТОЛЬКО из токенов: литеральные значения запрещены правилами
   проекта и ломают тёмную тему.
   ========================================================================== */

.z-auth-body {
  min-height: 100svh;              /* svh, а не vh: на мобильном vh «прыгает» */
  margin: 0;
  background: var(--z-bg);
  color: var(--z-fg);
  font-family: var(--z-font);
}

/* Центрирование карточки — тот же приём, что в shadcn: flex + отступы. */
.z-auth-wrap {
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  padding: 24px;
}

@media (min-width: 768px) {
  .z-auth-wrap { padding: 40px; }
}

/* Карточка: не шире 384px, как в shadcn (max-w-sm). */
.z-auth-card {
  width: 100%;
  max-width: 384px;
  padding: 28px 24px 24px;
}

.z-auth-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 20px;
}

.z-auth-title {
  margin: 0 0 4px;
  font-size: calc(21px * var(--z-text, 1));
  font-weight: var(--z-heading-weight); color: var(--z-heading-fg);
  letter-spacing: -0.01em;
}

.z-auth-sub {
  margin: 0 0 20px;
  font-size: calc(13px * var(--z-text, 1));
  line-height: 1.45;
}

.z-auth-form {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* Поле входа: высота 36px — значение из реестра shadcn (h-9 = 2.25rem).
   Общее правило .z-input даёт 34px, поэтому здесь высота задаётся явно. */
.z-auth-form .z-input {
  height: 36px;
  min-height: 36px;
  padding: 0 12px;
  font-size: calc(14px * var(--z-text, 1));
  border-radius: var(--z-radius-sm);
  /* Токен поля, а не поверхность: иначе поле входа выпадает из общего
     оформления полей, когда тема задаёт свою подложку. */
  background: var(--z-field-bg, var(--z-surface));
}

/* Кольцо фокуса на странице входа чуть заметнее общего: экран один, и
   пользователь должен сразу видеть, в каком поле он печатает. */
.z-auth-form .z-input:focus {
  border-color: var(--z-ring);
  box-shadow: 0 0 0 3px var(--z-ring-soft);
}

/* Поле пароля с кнопкой показа: кнопка прижата к правому краю. */
.z-passwrap {
  position: relative;
  display: block;
}

.z-passwrap .z-input { padding-right: 44px; }

.z-pass-toggle {
  position: absolute;
  top: 50%;
  right: 4px;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 28px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--z-muted-fg);
  cursor: pointer;
  border-radius: var(--z-radius-sm);
}

.z-pass-toggle:hover { color: var(--z-fg); }

.z-pass-toggle:focus-visible {
  outline: 2px solid var(--z-ring);
  outline-offset: 1px;
}

/* Иконки внутри кнопки показа пароля: показ/скрытие переключает атрибут
   hidden из разметки. Правило нужно, чтобы display от других селекторов
   не перебивал этот атрибут — иначе обе иконки показывались бы разом. */
.z-pass-toggle svg { display: block; }
.z-pass-toggle [hidden] { display: none !important; }

/* Кнопка входа: в shadcn она выше поля ввода (40px против 36px). */
.z-auth-submit {
  height: 40px;
  margin-top: 4px;
  font-size: calc(14px * var(--z-text, 1));
  font-weight: var(--z-text-weight);
  border-radius: var(--z-radius-sm);
  position: relative;
}

/* Состояние «идёт проверка»: индикатор вместо текста не подменяется —
   текст остаётся читаемым для программ чтения с экрана. */
.z-auth-submit[data-loading] {
  color: transparent;
}

.z-auth-submit[data-loading]::after {
  content: "";
  position: absolute;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid var(--z-on-primary);
  border-top-color: transparent;
  animation: z-auth-spin 600ms linear infinite;
}

@keyframes z-auth-spin {
  to { transform: rotate(360deg); }
}

/* Подпись под карточкой. */
.z-auth-foot {
  max-width: 384px;
  margin: 0;
  font-size: calc(12px * var(--z-text, 1));
  text-align: center;
  line-height: 1.5;
}

/* Ошибка входа: сообщение над формой, с отступом и мягким фоном. */
.z-auth-form ~ .z-alert,
.z-auth-card .z-alert {
  margin-bottom: 16px;
  font-size: calc(13px * var(--z-text, 1));
}

/* Разделитель с подписью — приём shadcn для дополнительных способов входа.
   Пока в разметке не используется, но объявлен, чтобы разметку можно было
   расширить без правки стилей. */
.z-auth-sep {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 20px 0;
  color: var(--z-muted-fg);
  font-size: calc(11px * var(--z-text, 1));
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.z-auth-sep::before,
.z-auth-sep::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--z-border);
}

/* На узком экране карточка занимает всю ширину, отступы сжимаются. */
@media (max-width: 420px) {
  .z-auth-card { padding: 22px 18px 20px; }
  .z-auth-title { font-size: calc(19px * var(--z-text, 1)); }
}

/* ==========================================================================
   20. ПОДСИСТЕМА ДОКУМЕНТООБОРОТА (/agreements/): ЖУРНАЛЫ, СПИСОК, КАРТОЧКА
   ==========================================================================
   ДЕФЕКТ, который здесь исправляется: разметка подсистемы применяла 45 классов
   (z-agr-* и вспомогательные z-*), которых в таблице стилей НЕ БЫЛО НИ ОДНОГО.
   Страницы открывались и работали — поэтому дефект не ловил ни запуск, ни чтение
   кода: вкладки журналов сливались в сплошную строку текста, панель фильтров и
   пустое состояние выглядели как элементы браузера по умолчанию (пустое состояние
   видно на скриншоте владельца). Сопоставление «класс в разметке ↔ правило
   в стилях» — единственный способ это увидеть статически.

   Границы: цвета — только семантические токены --z-*; светлая и тёмная темы
   переключаются сами, отдельных правил под тему не требуется.
   ========================================================================== */

/* --- Полоса вкладок журналов (Договоры · Платежи · Табель · …) ---
   Приём shadcn Tabs, перенесённый вручную: подчёркивание активной вкладки.
   Без этих правил вкладки — цепочка ссылок без разделителей (было на скриншоте). */
.z-agr-journals {
  display: flex;
  align-items: center;
  gap: 2px;
  margin: 0 0 14px;
  padding: 4px;
  border: 1px solid var(--z-border);
  border-radius: var(--z-radius);
  background: var(--z-surface);
  overflow-x: auto;
}
.z-agr-journals__tab {
  flex: 0 0 auto;
  padding: 7px 14px;
  border: 1px solid transparent;
  border-radius: var(--z-radius-sm);
  color: var(--z-muted-fg);
  font-size: calc(13px * var(--z-text, 1));
  font-weight: var(--z-text-weight);
  text-decoration: none;
  white-space: nowrap;
  transition: background-color .15s ease, color .15s ease, border-color .15s ease;
}
.z-agr-journals__tab:hover {
  background: var(--z-surface-2);
  color: var(--z-fg);
  text-decoration: none;
}
.z-agr-journals__tab.is-active {
  background: var(--z-accent-soft);
  border-color: var(--z-border);
  color: var(--z-accent);
  font-weight: var(--z-text-weight);
}

/* --- Панель инструментов списка: одна строка с зазором ---
   В разметке это .z-sec__toolbar с вложенными поиском, фильтром, размером
   страницы и действиями. Правила для .z-sec__toolbar в системе не было вовсе,
   а вызывающий код вставлял блок ВНЕ .z-sec — поэтому элементы шли вплотную. */
.z-sec__toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 12px 18px;
  border-bottom: 1px solid var(--z-border);
  background: var(--z-surface);
}
.z-agr-search { flex: 1 1 260px; min-width: 180px; }
.z-agr-filter { flex: 0 0 auto; min-width: 180px; }
/* Сортировка: подписи вида «Приоритет: важные сверху» — поле шире статуса,
   иначе подпись обрезается до «Приоритет: ва…». */
.z-agr-filter--sort { min-width: 240px; }
/* Фильтр по исполнителю: в вариантах полное ФИО («Фамилия Имя Отчество»),
   поэтому поле шире статуса — иначе подпись обрезается до «Капус…». */
.z-agr-filter--assignee { min-width: 220px; }
.z-agr-per { flex: 0 0 auto; min-width: 84px; }
/* «Показать только мои» и кнопка «Показать» — в той же строке панели.
   `white-space: nowrap` обязателен: без него подпись из двух слов рвётся
   на узком экране и галочка становится выше списка статусов. */
.z-agr-mine { flex: 0 0 auto; white-space: nowrap; }
.z-agr-apply { flex: 0 0 auto; }
/* Панель действий над списком.
   `flex-wrap: wrap` и `min-width: 0` обязательны для узкого экрана: без них
   панель шириной 509 px не переносится и распирает страницу до 543 px
   при экране 390. Замерено 8 октября 2026 г. в браузере на 390 px. */
.z-agr-actions {
  flex: 0 1 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
  flex-wrap: wrap;
  min-width: 0;
}
.z-agr-search .z-input,
.z-agr-filter .z-select,
.z-agr-per .z-select { min-height: 34px; }

/* --- Быстрые фильтры журнала («Все / Мои / Ждут оплаты»…)
   Отдельная строка панели: ссылки-пилюли с числом записей. Это фильтр по
   адресу, поэтому каждая кнопка — ссылка: её можно закрепить и переслать. */
.z-agr-views {
  flex: 1 1 100%;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  min-width: 0;
}
.z-agr-views__item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  border: 1px solid var(--z-border);
  border-radius: 999px;
  background: var(--z-surface);
  color: var(--z-fg);
  font-size: calc(13px * var(--z-text, 1));
  line-height: 1.2;
  text-decoration: none;
  white-space: nowrap;
}
.z-agr-views__item:hover { border-color: var(--z-accent); }
.z-agr-views__item.is-active {
  background: var(--z-accent-soft);
  border-color: var(--z-accent);
  color: var(--z-accent);
  font-weight: 600;
}
.z-agr-views__count {
  min-width: 18px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--z-surface-2);
  color: var(--z-muted-fg);
  font-size: calc(11px * var(--z-text, 1));
  text-align: center;
}
.z-agr-views__item.is-active .z-agr-views__count {
  background: var(--z-surface);
  color: var(--z-accent);
}
.z-agr-views__sum { color: var(--z-muted-fg); font-size: calc(11px * var(--z-text, 1)); }
@media (max-width: 760px) {
  .z-agr-views__sum { display: none; }
}

/* --- Таблица журнала ---
   Заготовка класса z-agr-table: своего оформления не вводит (за это отвечает
   .z-table), но именно она — якорь закрепления шапки и адаптива. */
.z-agr-table { table-layout: auto; }
.z-agr-cell { white-space: nowrap; }
/* Вторая строка составной ячейки: дата и номер счёта под номером заявки,
   кто ждёт согласования под этапом, срок оплаты под словом «Ждёт оплаты».
   Мелкая и приглушённая — чтобы не спорить с основной подписью состояния. */
.z-agr-cell__sub {
  display: block;
  margin-top: 2px;
  color: var(--z-muted-fg);
  font-size: calc(11px * var(--z-text, 1));
  line-height: 1.25;
  white-space: nowrap;
}
.z-agr-cell__sub--bad { color: var(--z-danger); }
.z-agr-cell__no { font-weight: 600; }
/* Составные ячейки платежей: подпись и пояснение переносятся по словам,
   иначе длинное ФИО согласующего или назначение растягивают таблицу за
   пределы карточки (замер 23 октября 2026 г.: 1478 px при доступных 954). */
.z-agr-cell--payee_purpose,
.z-agr-cell--approval,
.z-agr-cell--payment { white-space: normal; }
.z-agr-cell__sub--wrap,
.z-agr-cell--approval .z-agr-cell__sub,
.z-agr-cell--payment .z-agr-cell__sub { white-space: normal; }
/* «Кто держит заявку» — не служебная подпись, а имя человека: оно может
   переноситься (несколько носителей роли), но не обрезается. «Далее: …» —
   отдельная строка того же приглушённого стиля (задание 23 октября 2026 г.). */
.z-agr-cell--approval .z-agr-status { align-items: flex-start; white-space: normal; }
.z-agr-cell--approval .z-agr-status__text { white-space: normal; }
.z-agr-cell__sub--next { margin-top: 1px; }
.z-agr-cell--approval,
.z-agr-cell--payment { max-width: 200px; }
.z-agr-cell--payee_purpose { max-width: 280px; }
.z-agr-cell__main { display: block; }
/* Номер счёта — один длинный токен (20 цифр): на телефоне он не должен
   распирать карточку и страницу по горизонтали. */
.z-agr-cell--payment_no .z-agr-cell__sub { overflow-wrap: anywhere; }
.z-agr-cell--text { white-space: normal; min-width: 220px; }

/* Адаптивная карточка на телефоне: у составных ячеек подпись поля встаёт
   над значением. Без этого метка и две строки значения делились на три
   колонки flex и читались как разные поля. */
@media (max-width: 640px) {
  .z-agr-table.z-table--adaptive td.z-agr-cell--payment_no,
  .z-agr-table.z-table--adaptive td.z-agr-cell--payee_purpose,
  .z-agr-table.z-table--adaptive td.z-agr-cell--approval,
  .z-agr-table.z-table--adaptive td.z-agr-cell--payment {
    display: block;
  }
  .z-agr-table.z-table--adaptive td.z-agr-cell--payment_no::before,
  .z-agr-table.z-table--adaptive td.z-agr-cell--payee_purpose::before,
  .z-agr-table.z-table--adaptive td.z-agr-cell--approval::before,
  .z-agr-table.z-table--adaptive td.z-agr-cell--payment::before {
    display: block;
    min-width: 0;
    margin-bottom: 2px;
  }
}
.z-agr-cell--money { text-align: right; font-variant-numeric: tabular-nums; }
.z-agr-cell--date { color: var(--z-muted-fg); }

/* Ссылка-сортировка в шапке: приём DataTable из shadcn, перенесённый вручную. */
.z-agr-sort {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: inherit;
  text-decoration: none;
}
.z-agr-sort:hover { color: var(--z-fg); text-decoration: underline; text-underline-offset: 2px; }
.z-agr-sort__mark { color: var(--z-accent); font-size: calc(11px * var(--z-text, 1)); }

/* Фильтр исполнителя в заголовке столбца (TD-2026-0196): список стоит под
   подписью «Исполнитель», шириной в столбец. Толщина шрифта обычная — это
   поле, а не заголовок; в печати его нет (сервер не рисует разметку). */
.z-agr-thfilter { margin-top: 4px; }
.z-agr-thfilter .z-select {
  width: 100%;
  min-width: 0;
  min-height: 30px;
  padding: 4px 26px 4px 8px;
  font-weight: 400;
  font-size: calc(12px * var(--z-text, 1));
}
/* --- Статус: точка + подпись + тон.
   Подпись обязательна: статус не передаётся одним цветом (ТЗ §8.6). --- */
.z-agr-status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: calc(12.5px * var(--z-text, 1));
  white-space: nowrap;
}
.z-agr-status__dot {
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border-radius: 999px;
  background: var(--z-muted-fg);
}
.z-agr-status--ok .z-agr-status__dot { background: var(--z-success); }
.z-agr-status--warn .z-agr-status__dot { background: var(--z-warning); }
.z-agr-status--bad .z-agr-status__dot { background: var(--z-danger); }
.z-agr-status--ok .z-agr-status__text { color: var(--z-success); }
.z-agr-status--warn .z-agr-status__text { color: var(--z-warning); }
.z-agr-status--bad .z-agr-status__text { color: var(--z-danger); }
.z-agr-status--muted .z-agr-status__text { color: var(--z-muted-fg); }
/* Статус вне белого списка журнала: пунктирная точка — отметка дефекта данных. */
.z-agr-status--unknown .z-agr-status__dot {
  background: transparent;
  border: 1px dashed var(--z-danger);
}

/* --- Меню ячейки списка: состояние задачи и признак Wiki -------------------
   Задание владельца 20.10.2026: «при наведении на статус должно всплывать
   меню смены статуса, у столбца Wiki — выбор wiki/задача».

   Панель стоит внутри ячейки, но позиционируется `position: fixed`: обёртка
   таблицы прокручивается (`.z-tablewrap` → overflow-x: auto), и обычный
   абсолютный блок был бы обрезан её краем — ровно так уже молча не работал
   `position: sticky` из-за чужого overflow (ловушка 26). Координаты ставит
   `initCellMenus` (`assets/js/z.js`); без скрипта меню не открывается,
   а смена состояния остаётся в карточке — там она есть всегда. */
.z-cellmenu { position: relative; display: inline-block; }
.z-cellmenu > .z-agr-status,
.z-cellmenu > .z-chip { cursor: default; }
/* Признак «здесь есть меню»: при наведении подчёркиваем то, к чему оно
   относится. Без подсказки всплывающее меню никто не найдёт. */
.z-cellmenu:hover > .z-agr-status .z-agr-status__text,
.z-cellmenu:hover > .z-chip {
  text-decoration: underline dotted;
  text-underline-offset: 3px;
}
.z-cellmenu__panel {
  position: fixed;
  z-index: 90;
  display: none;
  min-width: 186px;
  padding: 6px;
  background: var(--z-surface);
  border: 1px solid var(--z-border);
  border-radius: 12px;
  box-shadow: 0 10px 30px rgba(15, 14, 13, .12);
}
.z-cellmenu__panel.is-open { display: block; }
.z-cellmenu__title {
  padding: 6px 10px 7px;
  font-size: calc(11px * var(--z-text, 1));
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--z-muted-fg);
}
.z-cellmenu__list { display: flex; flex-direction: column; gap: 2px; margin: 0; }
.z-cellmenu__item {
  display: block;
  width: 100%;
  padding: 7px 10px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--z-text-fg);
  font: inherit;
  font-size: calc(13px * var(--z-text, 1));
  text-align: left;
  white-space: nowrap;
}
button.z-cellmenu__item { cursor: pointer; }
button.z-cellmenu__item:hover { background: var(--z-surface-2); color: var(--z-fg); }
button.z-cellmenu__item:focus-visible { outline: 2px solid var(--z-fg); outline-offset: 1px; }
.z-cellmenu__item.is-current { color: var(--z-muted-fg); }

/* --- Пейджер: счётчик слева, переходы справа --- */
.z-agr-pager {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 10px 18px;
  border-top: 1px solid var(--z-border);
  font-size: calc(12.5px * var(--z-text, 1));
}
.z-agr-pager__nav { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-left: auto; }
.z-agr-pager__page { color: var(--z-muted-fg); }
.z-agr-pager__nav .z-btn[aria-disabled="true"] { opacity: .5; pointer-events: none; }

/* --- Пустое и аварийное состояние.
   Четыре вида различаются текстом и тоном: «журнал пуст», «фильтр отсёк всё»,
   «нет прав», «ошибка выборки» — смешивать их нельзя (ТЗ §8.2, §16). --- */
.z-agr-empty {
  margin: 0 0 18px;
  padding: 28px 20px;
  border: 1px solid var(--z-border);
  border-radius: 12px;
  background: var(--z-surface);
  text-align: center;
}
.z-agr-empty__head { font-size: calc(15px * var(--z-text, 1)); font-weight: var(--z-heading-weight); color: var(--z-heading-fg); }
.z-agr-empty__text {
  max-width: 560px;
  margin: 6px auto 0;
  color: var(--z-muted-fg);
  font-size: calc(13px * var(--z-text, 1));
}
.z-agr-empty__actions { display: flex; justify-content: center; gap: 8px; flex-wrap: wrap; margin-top: 14px; }
/* Состояние «журнал пуст» — этот класс ставит код (z-agr-empty--empty).
   Правило явное, а не «по умолчанию»: проверка соответствия «класс из
   разметки ↔ правило в стилях» ловит именно такие пропуски. */
.z-agr-empty--empty { border-style: solid; }
.z-agr-empty--denied { border-color: var(--z-border); background: var(--z-surface-2); }
.z-agr-empty--error { border-color: var(--z-danger); background: var(--z-danger-soft); }
.z-agr-empty--filtered { border-style: dashed; }

/* --- Карточка записи: строка заголовка, поля, маршрут, действия --- */
.z-agr-card__head {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin: 0 0 14px;
}
.z-agr-card__no { font-size: calc(18px * var(--z-text, 1)); font-weight: var(--z-heading-weight); color: var(--z-heading-fg); }
/* Статус задачи — ВЫБОР ИЗ СПИСКА вместо текста (задание владельца
   20 октября 2026 г.). Стоит в одну линию с номером, растягиваться на всю
   шапку ему незачем: список по содержимому, кнопка — маленькая. Выравнивание
   отдельным правилом, потому что `.z-inline-form` даёт display: inline
   и внутри flex-строки список с кнопкой расспались бы по базовой линии. */
.z-agr-statusform { display: inline-flex; align-items: center; gap: 6px; margin: 0; }
.z-agr-statusform .z-select { width: auto; min-width: 170px; max-width: 260px; }
.z-agr-statusform .z-btn { flex: none; }
.z-agr-card__actions { margin-bottom: 18px; }
.z-agr-card__actions .z-sec__body { padding: 12px 18px; }

/* Реквизиты: dl-сетка «подпись → значение», группа — заголовок на всю строку. */
.z-agr-fields {
  display: grid;
  grid-template-columns: minmax(180px, 260px) 1fr;
  gap: 1px 18px;
  margin: 0;
}
.z-agr-fields__group {
  grid-column: 1 / -1;
  margin: 0;
  padding: 14px 0 6px;
  border-bottom: 1px solid var(--z-border);
  color: var(--z-muted-fg);
  font-size: calc(11px * var(--z-text, 1));
  font-weight: var(--z-heading-weight); color: var(--z-heading-fg);
  letter-spacing: .05em;
  text-transform: uppercase;
}
.z-agr-fields__group:first-child { padding-top: 0; }
.z-agr-fields__label { margin: 0; padding: 7px 0; color: var(--z-muted-fg); font-size: calc(13px * var(--z-text, 1)); }
/* pre-wrap — потому что текст этих полей набран человеком или агентом
   (критерий результата, комментарий): перенос строки в нём значим, а без
   правила многострочное описание сливалось бы в один абзац. */
.z-agr-fields__value { margin: 0; padding: 7px 0; font-size: calc(13px * var(--z-text, 1)); word-break: break-word; white-space: pre-wrap; }

/* Поле причины в форме действия: своя ширина, иначе растягивается на всю строку. */
.z-agr-reason { display: inline-block; width: auto; min-width: 240px; margin-right: 8px; }

/* Маршрут согласования: таблица как обычная, но с плотными строками. */
.z-agr-route td { vertical-align: top; }

/* Печатный вид: только содержимое, без панелей и навигации. */
.z-agr-print { padding: 0; }
.z-agr-print h1 { margin: 0 0 12px; font-size: calc(18px * var(--z-text, 1)); }

/* Форма записи: подпись «обязательное поле» и скрытые-только-на-экране подписи. */
.z-agr-form .z-form__actions { justify-content: flex-end; }
.z-agr-form .z-req { color: var(--z-danger); }

/* Строка формы, недоступная из-за зависимости: поле отключено галочкой
   (см. assets/js/z.js, initAgrDependencies). Класс применяет скрипт, поэтому
   без правила строка не получала бы никакого признака — и человек видел бы
   неработающее поле как обычное, не понимая причины.

   Притушение берётся токеном, а не литеральным цветом: значение обязано
   переключаться вместе с темой. Погашенное поле верхней строкой помечено
   через opacity, а не отдельным цветом — так оно одинаково гасится
   в светлой и тёмной темах. */
.z-agr-row--off .z-form__control { opacity: 0.55; }
.z-agr-row--off input,
.z-agr-row--off select { cursor: not-allowed; }

/* Вспомогательные классы, которых не было в системе, но которые уже
   применяет разметка подсистемы (не второй стиль, а недостающие звенья). */

/* .z-form / .z-form__* — простая сетка формы для страниц, где не используется
   приём fieldset + .z-formgrid (образец — mail/edit.php). */
.z-form { display: flex; flex-direction: column; gap: 14px; }
.z-form__group { display: flex; flex-direction: column; gap: 12px; }
.z-form__row { display: flex; gap: 12px; flex-wrap: wrap; }
.z-form__row > * { flex: 1 1 220px; min-width: 0; }
.z-form__label { font-size: calc(13px * var(--z-text, 1)); font-weight: var(--z-text-weight); color: var(--z-muted-fg); }
.z-form__control { display: flex; flex-direction: column; gap: 6px; }
.z-form__actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 4px; }

/* Уведомление: то же назначение, что у .z-alert, но с ролью status/alert
   в разметке подсистемы. Тона — токенами темы. */
.z-notice {
  margin: 0 0 14px;
  padding: 10px 12px;
  border: 1px solid var(--z-border);
  border-left-width: 3px;
  border-radius: 8px;
  background: var(--z-surface-2);
  font-size: calc(13px * var(--z-text, 1));
}
.z-notice--ok { border-left-color: var(--z-success); background: var(--z-success-soft); }
.z-notice--muted { border-left-color: var(--z-border); background: var(--z-surface-2); color: var(--z-muted-fg); }
.z-notice--danger { border-left-color: var(--z-danger); background: var(--z-danger-soft); }

/* Прочее, что применяет разметка подсистемы. */
.z-list { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 4px; }
.z-locked { color: var(--z-muted-fg); }
.z-req { color: var(--z-danger); font-weight: 700; }
.z-pad-12 { padding: 12px; }
.z-link { color: var(--z-accent); text-decoration: none; }
.z-link:hover { text-decoration: underline; text-underline-offset: 2px; }
.z-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.z-table__check { width: 34px; padding-left: 18px; padding-right: 4px; }
.z-table__check input[type="checkbox"] { width: 16px; height: 16px; margin: 0; accent-color: var(--z-accent); }
.z-chip--warn { background: var(--z-warning-soft); color: var(--z-warning); }
.z-chip--muted { background: var(--z-surface-2); color: var(--z-muted-fg); }
/* Состояние «активно» в админке документооборота (подразделения, справочники).
   Тон — успех; цвет берётся токеном, поэтому в тёмной теме читается так же. */
.z-chip--ok { background: var(--z-success-soft); color: var(--z-success); }
/* Состояние «отозвано», «провалено», «не согласовано». Класс применялся
   в разметке (ApiClient, TaskStage, Signoff), но в стилях его не было —
   такие состояния рисовались нейтральной меткой и не отличались от обычных
   (найдено проверкой 15 октября 2026 г.). */
.z-chip--err { background: var(--z-danger-soft); color: var(--z-danger); }

/* --- Поле времени (своё, вместо input[type=time]) ---

   Ширина — по содержимому «чч:мм», а не на всю строку: поле стоит рядом
   с полем даты и не должно растягивать форму. Цифры табличные, чтобы поле
   не «дышало» при смене значения. */
.z-timefield { position: relative; display: inline-flex; align-items: center; gap: 6px; }
.z-timefield__input {
  width: 6.5ch;
  text-align: center;
  font-variant-numeric: tabular-nums;
  padding-left: 6px;
  padding-right: 6px;
}
.z-timefield__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid var(--z-border);
  border-radius: var(--z-radius);
  background: var(--z-surface);
  color: var(--z-muted-fg);
  cursor: pointer;
}
.z-timefield__btn:hover { color: var(--z-fg); border-color: var(--z-accent); }
.z-timefield__btn svg { width: 18px; height: 18px; }
/* Список шагов: 6–8 строк с прокруткой, выбранный пункт подкрашен. */
.z-timepick {
  position: absolute;
  z-index: 40;
  top: calc(100% + 4px);
  left: 0;
  min-width: 96px;
  max-height: 220px;
  overflow-y: auto;
  padding: 4px;
  border: 1px solid var(--z-border);
  border-radius: var(--z-radius);
  background: var(--z-surface);
  box-shadow: 0 8px 24px rgba(0, 0, 0, .18);
}
.z-timepick[hidden] { display: none; }
.z-timepick__item {
  display: block;
  width: 100%;
  padding: 5px 10px;
  border: 0;
  border-radius: calc(var(--z-radius) - 2px);
  background: transparent;
  color: var(--z-text-fg);
  font: inherit;
  font-variant-numeric: tabular-nums;
  text-align: left;
  cursor: pointer;
}
.z-timepick__item:hover,
.z-timepick__item.is-active { background: var(--z-surface-2); }
.z-timepick__item[aria-selected="true"] { color: var(--z-accent); font-weight: var(--z-text-weight); }
.z-timepick__sep { height: 1px; margin: 4px 2px; background: var(--z-border); }

/* --- Список задач: галочка завершения и раскрытие подзадач (приёмы Asana) ---

   Галочка слева от названия завершает задачу одним нажатием — это та же
   серверная операция, что кнопка на доске и в карточке, отдельного пути нет.
   Шеврон со счётчиком раскрывает подзадачи прямо в строке (<details>),
   поэтому работает и без скрипта. */
.z-agr-tasks__done { width: 34px; padding-right: 0; }
.z-agr-taskcell { display: flex; align-items: flex-start; gap: 6px; }
.z-agr-done {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
}
.z-agr-done__box {
  display: block;
  width: 16px;
  height: 16px;
  border: 2px solid var(--z-border);
  border-radius: 4px;
  background: var(--z-surface);
}
.z-agr-done:hover .z-agr-done__box { border-color: var(--z-accent); background: var(--z-accent-soft); }
.z-agr-done--static { cursor: default; }
.z-agr-done--static:hover .z-agr-done__box { border-color: var(--z-border); background: var(--z-surface); }
.z-agr-done.is-done .z-agr-done__box { border-color: var(--z-success); background: var(--z-success); }
.z-agr-done.is-done .z-agr-done__box::after {
  content: '✓';
  display: block;
  margin-top: -3px;
  color: var(--z-surface);
  font-size: calc(13px * var(--z-text, 1));
  line-height: 16px;
  text-align: center;
}
.z-agr-subtree__toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  height: 20px;
  padding: 0 5px;
  border: 1px solid var(--z-border);
  border-radius: 999px;
  background: var(--z-surface-2);
  color: var(--z-muted-fg);
  font-size: calc(11px * var(--z-text, 1));
  line-height: 1;
  cursor: pointer;
  list-style: none;
}
.z-agr-subtree__toggle::-webkit-details-marker { display: none; }
.z-agr-subtree[open] .z-agr-subtree__toggle { color: var(--z-accent); border-color: var(--z-accent); }
.z-agr-subtree__list {
  margin: 6px 0 0;
  padding: 0 0 0 10px;
  border-left: 2px solid var(--z-border);
  list-style: none;
}
.z-agr-subtree__list li { padding: 2px 0; font-size: calc(13px * var(--z-text, 1)); }
.z-agr-subtree__list li.is-done { color: var(--z-muted-fg); text-decoration: line-through; }
.z-agr-subtree__mark { color: var(--z-muted-fg); }

/* --- Вложения документа (ТЗ §9.1) ---

   Смысл оформления: список файлов и история версий должны читаться без
   пояснений. Текущая версия выделена, заменённые приглушены — по этому
   признаку видно, какой файл действует, не открывая историю.
   Цвета — только токены --z-*; обе темы переключаются сами. */
.z-agr-files__ico { flex: 0 0 auto; color: var(--z-muted-fg); }
/* Таблица вложений: колонки со служебными значениями не разъезжаются,
   имя файла переносится — длинные имена бывают часто. */
.z-agr-files td:first-child { min-width: 220px; }
.z-agr-files__name {
  margin-left: 6px;
  color: var(--z-accent);
  text-decoration: none;
  word-break: break-word;
}
.z-agr-files__name:hover { text-decoration: underline; text-underline-offset: 2px; }

/* Форма загрузки: поле выбора файла и кнопка в одну строку, с отступом сверху. */
.z-agr-upload {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--z-border);
}
.z-agr-upload .z-field__label { margin: 0; }

/* Окно загрузки как зона перетаскивания (задание 23.10.2026).
   Форма с [data-file-dropzone] — не строка, а рамка: файл можно бросить
   в любое её место. Вид остаётся обычной формой, пока скрипт не выполнился. */
.z-dropzone {
  display: block;
  margin-top: 14px;
  padding: 14px;
  border: 1px dashed var(--z-border);
  border-radius: var(--z-radius-sm);
  background: var(--z-surface-2);
  transition: border-color .15s ease, background-color .15s ease, box-shadow .15s ease;
}
.z-dropzone.is-dragover {
  border-color: var(--z-accent);
  background: var(--z-accent-soft);
  box-shadow: 0 0 0 3px var(--z-accent-soft);
}
.z-dropzone__hint {
  margin: 0 0 8px;
  font-size: calc(13px * var(--z-text, 1));
  color: var(--z-muted-fg);
}
.z-dropzone__hint::before {
  content: "";
  display: inline-block;
  width: 14px;
  height: 14px;
  margin-right: 6px;
  vertical-align: -2px;
  background: currentColor;
  /* Стрелка «вниз в лоток» рисуется маской, чтобы не тянуть картинку. */
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 3v12'/%3E%3Cpath d='m7 10 5 5 5-5'/%3E%3Cpath d='M5 21h14'/%3E%3C/svg%3E") center / 14px 14px no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 3v12'/%3E%3Cpath d='m7 10 5 5 5-5'/%3E%3Cpath d='M5 21h14'/%3E%3C/svg%3E") center / 14px 14px no-repeat;
}
.z-dropzone__state {
  min-height: 18px;
  margin: 8px 0 0;
  font-size: calc(12px * var(--z-text, 1));
}
.z-dropzone__state.is-error { color: var(--z-danger); }
.z-dropzone__state.is-ok { color: var(--z-success); }
.z-dropzone .z-form__row { margin: 0; }
/* В зоне файл выбирают кнопкой, поэтому подсказка и поле идут сверху вниз. */
.z-dropzone .z-form__control { width: 100%; }

/* Поле файла в строке таблицы не должно растягивать ячейку на всю ширину. */
.z-agr-filein {
  display: inline-block;
  max-width: 220px;
  vertical-align: middle;
}

/* История версий: каждая версия — строка с номером, именем, сведениями
   и состоянием. Приём из ТЗ §8.9 (z-versions). */
.z-versions {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--z-border);
  border-radius: var(--z-radius-sm);
  overflow: hidden;
  margin-top: 8px;
}
.z-versions__row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 8px 12px;
  border-bottom: 1px solid var(--z-border);
  background: var(--z-surface);
  font-size: calc(13px * var(--z-text, 1));
}
.z-versions__row:last-child { border-bottom: 0; }
/* Текущая версия: лёгкая заливка тем же приёмом, что «зебра» таблиц. */
.z-versions__row.is-current { background: var(--z-surface-2); }
.z-versions__ver {
  flex: 0 0 auto;
  min-width: 34px;
  font-weight: var(--z-text-weight);
  color: var(--z-text-fg);
}
.z-versions__name {
  flex: 1 1 220px;
  min-width: 0;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  word-break: break-word;
}
.z-versions__meta { flex: 2 1 260px; min-width: 0; }
.z-versions__state { flex: 0 0 auto; }
.z-versions__act { flex: 0 0 auto; margin-left: auto; }

/* На узком экране строки версий и формы вложений складываются в столбик:
   горизонтальная прокрутка таблицы версий на телефоне нечитаема. */
@media (max-width: 640px) {
  .z-agr-upload { flex-direction: column; align-items: stretch; }
  .z-agr-filein { max-width: none; width: 100%; }
  .z-versions__row { flex-direction: column; align-items: flex-start; gap: 4px; }
  .z-versions__act { margin-left: 0; }
}

/* --- Возврат к списку (8 октября 2026) ---

   Крестик в шапке и полоса возврата: после создания записи человек попадал
   в карточку, откуда вернуться к списку было нечем. Разметку собирает
   src/Back.php, здесь — только оформление.

   Крестик стоит в верхней панели правее заголовка, а не в дальнем углу
   справа: там уже переключатель темы и меню пользователя, и третий элемент
   в том же углу читался бы как ещё одна настройка, а не как «выйти». */

/* Крестик возврата: оформление берётся у существующего `.z-close`
   (он же у крестика редактора письма), здесь — только место в верхней панели
   и размер под её высоту. Второй способ рисовать крестик не создаётся. */
.z-top .z-backclose {
  width: 30px;
  height: 30px;
  margin-left: 2px;
  border-color: transparent;
  background: transparent;
}

/* Полоса возврата: первый элемент страницы, до содержимого. */
.z-backbar {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 12px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--z-border);
}
.z-backbar__btn { flex: none; margin-left: auto; }

/* Цепочка «раздел / журнал / номер»: видно, где находишься. */
.z-crumbs {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  min-width: 0;
  font-size: calc(13px * var(--z-text, 1));
  color: var(--z-muted-fg);
}
.z-crumbs__link { color: var(--z-muted-fg); text-decoration: none; }
.z-crumbs__link:hover { color: var(--z-fg); text-decoration: underline; }
.z-crumbs__sep { color: var(--z-border); }
.z-crumbs__here { color: var(--z-text-fg); font-weight: var(--z-text-weight); }

/* Подпись под кнопками формы: снимает двусмысленность «Отмены». */
.z-form__note { margin: 8px 0 0; text-align: right; }

/* Диалог «уйти без сохранения»: внутри — готовый механизм z-dialog,
   здесь только расположение содержимого. */
.z-backdialog__body { padding: 16px; }
.z-backdialog__title { margin: 0 0 6px; font-size: calc(15px * var(--z-text, 1)); font-weight: var(--z-heading-weight); color: var(--z-heading-fg); }
.z-backdialog__text { margin: 0 0 14px; color: var(--z-muted-fg); }
.z-backdialog__actions { display: flex; gap: 8px; justify-content: flex-end; flex-wrap: wrap; }

/* На узком экране кнопка возврата переносится под цепочку и растягивается:
   втискивать её в одну строку с цепочкой значило бы обрезать подпись. */
@media (max-width: 640px) {
  .z-backbar__btn { margin-left: 0; width: 100%; justify-content: center; }

  /* Крестик в шапке на узком экране скрыт: ту же роль выполняет кнопка
     в полосе возврата, а она во всю ширину и с подписью. Два элемента
     с одним действием на тесном экране отнимают место у правого блока
     (тема и меню пользователя) и дают переполнение по горизонтали. */
  .z-top .z-backclose { display: none; }
}

/* --- Поля ввода: дата с календарём, месяц, файл (8 октября 2026) ---

   Нативные поля `input[type=date]` и `input[type=file]` показывают подписи
   и календарь на языке БРАУЗЕРА, а не страницы: на машине с английским
   интерфейсом сотрудник видит «Choose File» и «No file chosen». Поэтому
   поля заменены своими — с русскими подписями и русским календарём.

   Значения при этом не меняются: в базу по-прежнему уходит ISO. */

.z-datefield { position: relative; display: inline-flex; align-items: center; gap: 6px; max-width: 100%; }
.z-datefield__input { width: 132px; }
.z-datefield__btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; padding: 0;
  border: 1px solid var(--z-border); border-radius: var(--z-radius);
  background: var(--z-surface); color: var(--z-muted-fg); cursor: pointer;
}
.z-datefield__btn:hover { color: var(--z-fg); border-color: var(--z-accent); }
.z-datefield__btn svg { width: 18px; height: 18px; }

/* Поле с неверной датой: рамка и мягкий фон цвета опасности. Токены
   дизайн-системы — цвет переключится вместе с темой. Текст, который видит
   человек, задаёт браузер по setCustomValidity: цвет не единственный
   носитель смысла (требование ТЗ §8.6). */
.z-input--err { border-color: var(--z-danger); background: var(--z-danger-soft); }

/* Календарь: свой, на русском. Открывается кнопкой рядом с полем. */
.z-cal {
  position: absolute; z-index: 40; top: calc(100% + 4px); left: 0;
  width: 264px; padding: 8px;
  background: var(--z-surface); border: 1px solid var(--z-border);
  /* Тень — существующий токен дизайн-системы. Своего «среднего» размера
     в системе нет: необъявленный токен молча не дал бы тени вовсе. */
  border-radius: var(--z-radius); box-shadow: var(--z-shadow);
}
.z-cal[hidden] { display: none; }
.z-cal__head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 6px; }
.z-cal__title { font-size: calc(13px * var(--z-text, 1)); font-weight: var(--z-heading-weight); color: var(--z-heading-fg); }
.z-cal__nav {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; padding: 0; cursor: pointer;
  border: 1px solid var(--z-border); border-radius: var(--z-radius);
  background: var(--z-surface); color: var(--z-muted-fg);
}
.z-cal__nav:hover { color: var(--z-fg); border-color: var(--z-accent); }
.z-cal__nav svg { width: 14px; height: 14px; }
.z-cal__grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.z-cal__wd { text-align: center; font-size: calc(11px * var(--z-text, 1)); color: var(--z-muted-fg); padding: 2px 0; }
.z-cal__day {
  text-align: center; font-size: calc(12px * var(--z-text, 1)); padding: 5px 0; cursor: pointer;
  border: 1px solid transparent; border-radius: 6px; background: none; color: var(--z-text-fg);
}
.z-cal__day:hover { background: var(--z-surface-2); }
.z-cal__day--other { color: var(--z-muted-fg); opacity: .55; }
.z-cal__day--weekend { color: var(--z-danger); }
.z-cal__day--today { border-color: var(--z-accent); }
.z-cal__day--current { background: var(--z-accent); color: #fff; }
/* День вне допустимых границ: виден, но не выбирается. Приглушён и без
   курсора-указателя — так видно, что это запрет, а не ошибка отрисовки. */
.z-cal__day--off { color: var(--z-muted-fg); opacity: .4; cursor: not-allowed; }
.z-cal__day--off:hover { background: none; }
.z-cal__foot { margin-top: 6px; display: flex; gap: 6px; justify-content: flex-end; }

/* Поле месяца: месяц словом и год числом вместо нативного input[type=month]. */
.z-monthfield { display: inline-flex; align-items: center; gap: 6px; }
.z-monthfield__month { width: 132px; }
.z-monthfield__year { width: 84px; }

/* Поле файла: нативный ввод скрыт, на экране — своя кнопка и имя файла. */
.z-filefield { display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap; max-width: 100%; }
.z-filefield__btn { cursor: pointer; }
.z-filefield__btn svg { width: 16px; height: 16px; }
.z-filefield__name { overflow-wrap: anywhere; }

/* --- Inbox уведомлений (ТЗ §9.3) ---
   Непрочитанное выделяется ДВУМЯ признаками: жирной темой события и мягкой
   заливкой строки. Одного цвета мало: смысл не должен теряться при печати
   или для человека, который цвета не различает (ТЗ §8.6). */
.z-agr-notes .is-unread td { background: var(--z-accent-soft); }
.z-agr-notes td { vertical-align: top; }

/* --- Задачи: дерево, список, доска (ТЗ §7.6) ---

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

   Доска — CSS-грид из четырёх колонок; перенос по горизонтали на узком экране
   вместо сжатия: колонки уже 200 px теряют читаемость карточек.
   ========================================================================== */
.z-agr-tasks {
  display: grid;
  grid-template-columns: minmax(220px, 280px) 1fr;
  gap: 16px;
  align-items: start;
}
.z-agr-tasks__side { position: sticky; top: 12px; }
.z-agr-tasks__main { min-width: 0; }

/* Дерево навигации: пункты одного вида, отступ показывает уровень. */
.z-agr-tree { display: flex; flex-direction: column; }
.z-agr-tree__item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  font-size: calc(13.5px * var(--z-text, 1));
  color: var(--z-text-fg);
  text-decoration: none;
  border-left: 2px solid transparent;
}
.z-agr-tree__item:hover { background: var(--z-surface-2); }
.z-agr-tree__item.is-active {
  background: var(--z-accent-soft);
  border-left-color: var(--z-accent);
  font-weight: var(--z-text-weight);
}
.z-agr-tree__item .z-badge { margin-left: auto; }
.z-agr-tree__group {
  padding: 10px 12px 4px;
  font-size: calc(11.5px * var(--z-text, 1));
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--z-muted-fg);
}

/* Панель над списком: вкладки представления и группировка. */
.z-agr-tasks__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin-bottom: 12px;
}
.z-agr-tasks__group {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: calc(12.5px * var(--z-text, 1));
  color: var(--z-muted-fg);
  /* Перенос и сжатие: без них группа карточки распирает документ до 399 px
     при экране 390 (замерено в браузере 8 октября 2026 г.). */
  flex-wrap: wrap;
  min-width: 0;
}

/* Быстрая строка создания: ввод + кнопка в одну линию. */
.z-agr-quick {
  display: flex;
  gap: 8px;
  align-items: center;
}
.z-agr-quick .z-input { flex: 1 1 auto; min-width: 0; }

/* Заголовок группы строк в списке. */
.z-agr-rowhead {
  padding: 10px 12px 6px;
  font-weight: var(--z-text-weight);
  font-size: calc(13px * var(--z-text, 1));
  background: var(--z-surface-2);
  border-top: 1px solid var(--z-border);
}

/* Просроченная задача: сигнал цветом И текстом в бейдже рядом — цвет не
   единственный носитель смысла (доступность, §8.6). */
.z-agr-overdue { color: var(--z-danger); font-weight: var(--z-text-weight); }
.z-agr-overdue-row td { background: var(--z-danger-soft); }

/* Доска: четыре колонки, карточки внутри. */
.z-board {
  display: grid;
  grid-template-columns: repeat(4, minmax(200px, 1fr));
  gap: 12px;
  overflow-x: auto;
}
.z-board__col {
  background: var(--z-surface-2);
  border: 1px solid var(--z-border);
  /* Токен --z-radius-md в дизайн-системе НЕ ОБЪЯВЛЕН (есть только --z-radius
     и --z-radius-sm): var() от несуществующего токена браузер молча
     игнорирует, и колонка получила бы прямые углы без единой ошибки. */
  border-radius: var(--z-radius);
  min-width: 200px;
}
.z-board__head {
  padding: 10px 12px;
  font-size: calc(13px * var(--z-text, 1));
  font-weight: var(--z-heading-weight); color: var(--z-heading-fg);
  border-bottom: 1px solid var(--z-border);
  display: flex;
  align-items: center;
  gap: 8px;
}
.z-board__body { padding: 10px; display: flex; flex-direction: column; gap: 8px; }
.z-board__card {
  background: var(--z-surface);
  border: 1px solid var(--z-border);
  border-radius: var(--z-radius-sm);
  padding: 10px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.z-board__title {
  font-size: calc(13.5px * var(--z-text, 1));
  font-weight: var(--z-heading-weight);
  color: var(--z-heading-fg);
  text-decoration: none;
}
.z-board__title:hover { text-decoration: underline; }

/* Чек-лист и комментарии: список с кнопками в строке. */
.z-agr-checklist li,
.z-agr-comments li {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding: 6px 0;
  border-bottom: 1px solid var(--z-border);
}
.z-agr-comments li { display: block; }
/* Отчёт по диалогу приходит комментарием с переводами строк (HARNESS,
   скилл bsg-portal-task): pre-wrap сохраняет его вид. */
.z-agr-comments li > div { white-space: pre-wrap; }
.z-agr-checklist li:last-child,
.z-agr-comments li:last-child { border-bottom: 0; }

/* Узкий экран: дерево становится верхней панелью, колонки прокручиваются. */
@media (max-width: 900px) {
  .z-agr-tasks { grid-template-columns: 1fr; }
  .z-agr-tasks__side { position: static; }
  .z-board { grid-template-columns: repeat(4, 200px); }
}

/* --- График отпусков: гант (ТЗ §7.5) ---

   Назначение — обзор года: по строкам сотрудники, по колонкам месяцы.

   Почему месяцы, а не дни: 365 колонок не читаются ни на экране, ни на бумаге,
   а вопрос «кто когда в отпуске» решается на уровне месяцев. Точные даты и
   номера заявок остаются в подсказке полосы и в списке заявок.

   Разметка строится CSS-гридом: 1 колонка под имя + 12 равных колонок месяцев.
   Полосы ложатся АБСОЛЮТНО поверх сетки (left/width в процентах) — так они
   делят год пропорционально и не ломаются при смене ширины экрана.
   ========================================================================== */
.z-gantt {
  overflow-x: auto;
}
.z-gantt__head,
.z-gantt__row {
  display: grid;
  grid-template-columns: minmax(180px, 240px) 1fr;
  align-items: center;
  gap: 8px;
}
.z-gantt__head {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--z-surface-2);
  border-bottom: 1px solid var(--z-border);
  font-size: calc(12px * var(--z-text, 1));
  color: var(--z-muted-fg);
}
.z-gantt__row {
  border-bottom: 1px solid var(--z-border);
  min-height: 38px;
}
.z-gantt__row:last-child { border-bottom: 0; }
.z-gantt__name {
  display: flex;
  flex-direction: column;
  padding: 6px 8px;
  font-size: calc(13px * var(--z-text, 1));
  min-width: 0;
}
.z-gantt__months {
  position: relative;
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  min-height: 30px;
}
.z-gantt__month,
.z-gantt__cell {
  border-left: 1px solid var(--z-border);
  text-align: center;
}
.z-gantt__month { padding: 4px 0; }
.z-gantt__cell { min-height: 30px; }

/* Полосы: слой поверх сетки. Не перехватывают клики — под ними клетки месяцев. */
.z-gantt__bars {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.z-gantt__bar {
  position: absolute;
  top: 6px;
  height: 8px;
  border-radius: 999px;
  pointer-events: auto;
}
/* План — фоном: приглушённая полоса, видно сквозь неё. */
.z-gantt__bar--plan {
  background: var(--z-accent-soft);
  border: 1px solid var(--z-accent);
  height: 6px;
  top: 4px;
}
/* Факт — поверх: плотная полоса ниже плана, чтобы обе читались при совпадении. */
.z-gantt__bar--fact {
  background: var(--z-accent);
  top: 16px;
}
.z-gantt__bar--recalled {
  background: var(--z-muted-fg);
  top: 16px;
  opacity: .7;
}

/* Легенда под графиком: поясняет, какая полоса что означает. */
.z-gantt__legend {
  display: inline-block;
  width: 22px;
  height: 8px;
  border-radius: 999px;
  vertical-align: middle;
}
.z-gantt__legend--plan {
  background: var(--z-accent-soft);
  border: 1px solid var(--z-accent);
}
.z-gantt__legend--fact { background: var(--z-accent); }

/* Узкий экран: год не влезает — график прокручивается по горизонтали,
   а имя сотрудника остаётся видимым (липкая первая колонка). */
@media (max-width: 720px) {
  .z-gantt__head,
  .z-gantt__row {
    grid-template-columns: 120px 1fr;
    min-width: 560px;
  }
  .z-gantt__name {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--z-bg);
  }
}

/* --- Печатный табель: матрица «сотрудник × дни» (ТЗ §7.3, мотив Т-13) ---

   Отличие от печати списка: здесь не строки, а ЛИСТ. Отсюда правила:
     * клетки узкие и моноширинные — в строку влезают все 31 день;
     * выходные приглушены фоном: по ним видно, где норма, а где отклонение;
     * часы внутри клетки мелким шрифтом — иначе код и часы не различить;
     * строки итогов справа выделены весом, а не цветом: документ печатают
       в чёрно-белом виде.
   ========================================================================== */
.z-agr-ts-print { max-width: none; font-size: calc(11px * var(--z-text, 1)); }
.z-agr-ts-print__table { width: 100%; border-collapse: collapse; table-layout: fixed; }
.z-agr-ts-print__table th,
.z-agr-ts-print__table td {
  border: 1px solid var(--z-border);
  padding: 2px 3px;
  text-align: center;
  font-size: calc(10.5px * var(--z-text, 1));
  overflow: hidden;
}
.z-agr-ts-print__table th { background: var(--z-surface-2); font-weight: 600; }
.z-agr-ts-print__name {
  width: 180px;
  text-align: left !important;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.z-agr-ts-print__cell {
  font-family: var(--z-mono);
  position: relative;
  min-width: 18px;
}
/* Выходные: фон приглушён; в печати заменяется серой заливкой, заданной явно. */
.z-agr-ts-print__cell.is-weekend,
.z-agr-ts-print th.is-weekend { background: var(--z-surface-2); }
.z-agr-ts-print__hours {
  display: block;
  font-size: calc(8.5px * var(--z-text, 1));
  color: var(--z-muted-fg);
  line-height: 1;
}
.z-agr-ts-print__sum { font-weight: 700; white-space: nowrap; }

@media print {
  .z-agr-ts-print { font-size: calc(9px * var(--z-text, 1)); }
  .z-agr-ts-print__table th,
  .z-agr-ts-print__table td { border-color: #000; }
  .z-agr-ts-print__cell.is-weekend,
  .z-agr-ts-print th.is-weekend { background: #eee; }
}

/* --- Печатные формы документов (ТЗ §9.2) ---

   Назначение — ДОКУМЕНТ на бумаге, а не экран. Отсюда правила:
     * ширины ограничены, чтобы строки не расползались по листу;
     * цвета — только токены, плюс отдельные правила печати: в чёрно-белом
       виде подписи и линии должны оставаться видимыми;
     * ссылки, кнопки и меню в разметке печатных видов отсутствуют вовсе —
       на бумаге они бесполезны.

   Формы (.z-print-doc) собираются Export.php: заявление на отпуск (§7.5)
   и лист ознакомления (§7.4).
   ========================================================================== */
.z-print-doc {
  max-width: 760px;
  margin: 0 auto;
  color: var(--z-fg);
  font-size: calc(14px * var(--z-text, 1));
  line-height: 1.5;
}
.z-print-doc__head {
  margin-bottom: 20px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--z-border);
}
.z-print-doc__org {
  font-size: calc(12px * var(--z-text, 1));
  color: var(--z-muted-fg);
  text-transform: uppercase;
  letter-spacing: .06em;
}
.z-print-doc__title {
  margin: 6px 0 4px;
  font-size: calc(18px * var(--z-text, 1));
  font-weight: 700;
}
.z-print-doc__meta {
  font-size: calc(12px * var(--z-text, 1));
  color: var(--z-muted-fg);
}
.z-print-doc__addr {
  margin: 0 0 6px;
  text-align: right;
  font-size: calc(13px * var(--z-text, 1));
}
.z-print-doc__h2 {
  margin: 18px 0 14px;
  text-align: center;
  font-size: calc(15px * var(--z-text, 1));
  font-weight: 700;
  letter-spacing: .04em;
}
.z-print-doc__note {
  margin: 10px 0;
  padding: 8px 10px;
  border: 1px solid var(--z-warning);
  border-radius: var(--z-radius-sm);
  background: var(--z-warning-soft);
  font-size: calc(13px * var(--z-text, 1));
}
.z-print-doc__table {
  width: 100%;
  border-collapse: collapse;
  margin: 12px 0;
}
.z-print-doc__table th,
.z-print-doc__table td {
  padding: 6px 8px;
  border: 1px solid var(--z-border);
  text-align: left;
  vertical-align: top;
  font-size: calc(13px * var(--z-text, 1));
}
.z-print-doc__table th {
  width: 190px;
  font-weight: 600;
  color: var(--z-muted-fg);
  background: var(--z-surface-2);
}
/* Места для подписи и даты: линия, а не подчёркивание текста — человек
   должен видеть, где расписаться. */
.z-print-doc__signs {
  display: flex;
  flex-wrap: wrap;
  gap: 24px 40px;
  margin-top: 28px;
}
.z-print-doc__sign {
  min-width: 220px;
  font-size: calc(13px * var(--z-text, 1));
  color: var(--z-muted-fg);
}
.z-print-sign,
.z-print-line {
  display: block;
  margin-top: 22px;
  border-bottom: 1px solid var(--z-fg);
}
/* Линия в строке текста (для суммы прописью) — не блок, а вставка. */
.z-print-doc__table .z-print-line {
  display: inline-block;
  min-width: 180px;
  margin-top: 0;
}
.z-print-doc__more { margin: 22px 0 4px; font-size: calc(13px * var(--z-text, 1)); }
.z-print-doc__space {
  height: 26px;
  border-bottom: 1px solid var(--z-border);
  margin-bottom: 10px;
}

/* Печать: убираем экранное оформление и добавляем разрывы так, чтобы
   документ не рвался посреди подписей. Цвета заменяются на черно-белые
   самим браузером, но границы задаются явно: без них линии пропадут. */
@media print {
  body { background: #fff; }
  .z-print-doc { max-width: none; }
  .z-print-doc__signs { page-break-inside: avoid; }
  .z-print-doc__table th {
    background: transparent;
    border: 1px solid #000;
  }
  .z-print-doc__table td { border: 1px solid #000; }
  .z-print-sign,
  .z-print-line,
  .z-print-doc__space { border-bottom-color: #000; }
}

/* Закрепление шапки таблицы журнала — тем же механизмом, что у реестра
   корреспонденции: секция обязана разрешать переполнение, иначе position: sticky
   у потомков не работает, а обёртка таблицы не должна прокручиваться по
   горизонтали. Смещение считает initStickyHead() и кладёт в переменную. */
.z-agr-tablewrap { overflow: visible; }
.z-sec--stickyhead .z-agr-table thead th {
  position: sticky;
  top: var(--z-mail-head-offset, 113px);
  z-index: 29;
  background: var(--z-surface);
  box-shadow: inset 0 -1px 0 var(--z-border);
}
.z-agr-table { border-collapse: separate; border-spacing: 0; }

/* Узкие экраны: закрепление перекрывало бы список — выключаем, таблица
   переходит в карточный вид (.z-table--adaptive). */
@media (max-width: 900px) {
  .z-sec--stickyhead .z-agr-table thead th { position: static; }
  .z-agr-search { flex: 1 1 100%; }
  .z-agr-actions { margin-left: 0; }
  .z-agr-fields { grid-template-columns: 1fr; gap: 0; }
  .z-agr-fields__label { padding-bottom: 0; }
  .z-agr-fields__value { padding-top: 2px; margin-bottom: 8px; }
}

/* --- Отправка договора на согласование: исполнитель по каждому этапу ---
   Спецификация договоров требует, чтобы у этапа был конкретный сотрудник.
   Этапы идут столбцом: подписи разные по длине, в строку они не укладываются. */
.z-agr-submitform {
  display: flex;
  flex-direction: column;
  gap: 12px;
  align-items: flex-start;
  min-width: 260px;
}
.z-agr-submitform__steps {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
}
.z-agr-submitform__step {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* --- Срок с единицей измерения: «число» + «единица» в одной строке ---
   Два поля — одно значение: число без единицы бессмысленно, поэтому они
   визуально связаны и не разъезжаются при переносе. */
.z-agr-term {
  display: flex;
  align-items: center;
  gap: 8px;
}
.z-agr-term .z-input:first-child { flex: 1 1 auto; min-width: 72px; }
.z-agr-term .z-input:last-child { flex: 0 0 auto; width: auto; min-width: 110px; }
@media (max-width: 640px) {
  .z-agr-term { flex-wrap: wrap; }
  .z-agr-term .z-input:last-child { min-width: 0; width: 100%; }
}

/* ==========================================================================
   ТАБЕЛЬ: матрица «сотрудник × дни месяца» (agreements/timesheet.php, ТЗ §7.3)
   ==========================================================================
   Плотная таблица с закреплённой первой колонкой и закреплённой шапкой.
   Закрепление — отдельным правилом и с раздельными фонами: без фона ячейка
   при прокрутке становится прозрачной и на цифрах читается текст строки. */
.z-agr-ts-filter { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.z-agr-ts-filter .z-select { min-width: 120px; }
.z-agr-ts-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  border-top: 1px solid var(--z-border);
}

/* Таблица табеля: ячейки теснее обычных — 31 колонка должна помещаться. */
.z-tt { font-variant-numeric: tabular-nums; }
.z-tt th,
.z-tt td {
  padding: 4px 3px;
  text-align: center;
  white-space: nowrap;
}
.z-tt thead th {
  font-size: calc(11px * var(--z-text, 1));
  font-weight: 600;
  color: var(--z-muted-fg);
}

/* Закреплённые: шапка сверху, первая колонка слева.
   Через токены темы: иначе в тёмной теме закреплённая клетка светила бы белым. */
.z-tt thead th { position: sticky; top: 0; z-index: 2; background: var(--z-surface); }
.z-tt .z-tt__name {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--z-surface);
  text-align: left;
  padding-left: 18px;
  padding-right: 12px;
  min-width: 220px;
  max-width: 320px;
  white-space: normal;
}
.z-tt thead .z-tt__name { z-index: 3; }

.z-tt__pos { display: block; font-size: calc(11px * var(--z-text, 1)); color: var(--z-muted-fg); }
.z-tt__day--weekend { background: var(--z-surface-2); }
.z-tt__sum {
  font-weight: 600;
  text-align: right;
  padding-right: 18px;
  background: var(--z-surface);
}

/* Клетка: код и часы — два маленьких поля в один столбик. */
.z-tt__input,
.z-tt__hours {
  width: 100%;
  min-width: 26px;
  padding: 2px 2px;
  border: 1px solid var(--z-field-border, var(--z-border));
  border-radius: 4px;
  background: var(--z-field-bg, var(--z-surface));
  color: var(--z-fg);
  font-family: var(--z-mono);
  font-size: calc(12px * var(--z-text, 1));
  text-align: center;
}
.z-tt__hours { margin-top: 2px; font-size: calc(10.5px * var(--z-text, 1)); color: var(--z-muted-fg); }
.z-tt__input:focus,
.z-tt__hours:focus {
  outline: none;
  border-color: var(--z-ring);
  box-shadow: 0 0 0 2px var(--z-ring-soft);
}
/* Режим «только чтение»: код и часы — текстом, без рамок полей. */
.z-tt__code { display: block; font-family: var(--z-mono); font-size: calc(12px * var(--z-text, 1)); }
.z-tt__hours-ro { display: block; font-size: calc(10.5px * var(--z-text, 1)); color: var(--z-muted-fg); }

/* Легенда кодов под таблицей. */
.z-tt__legend { display: flex; flex-direction: column; gap: 6px; }
.z-tt__legend .z-chip { margin-right: 4px; }

/* Узкие экраны: горизонтальная прокрутка матрицы — это нормально, важно её не
   потерять. Закрепление первой колонки сохраняется: без него непонятно, чья строка. */
@media (max-width: 900px) {
  .z-tt .z-tt__name { min-width: 150px; max-width: 200px; font-size: calc(12px * var(--z-text, 1)); }
  .z-tt__input, .z-tt__hours { min-width: 22px; font-size: calc(11px * var(--z-text, 1)); }
}

/* ==========================================================================
   Wiki задач: закладка (дерево разделов), блок темы и панель фильтров
   (задание владельца от 16 октября 2026 г.)
   --------------------------------------------------------------------------
   Размеры — только через множитель `--z-text` выбранного сотрудником размера
   (AGENTS.md §6, ловушка 17), цвета — только токены темы. Панель фильтров
   одна на список, доску и Wiki, поэтому её стили живут здесь, а не на
   странице: три копии разошлись бы при первой правке.
   ========================================================================== */
.z-wiki-filters { margin-bottom: 12px; }
.z-wiki-filters__row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 10px 12px;
}
.z-wiki-filters .z-field { margin: 0; }
.z-wiki-filters__search { flex: 1 1 220px; min-width: 180px; }
.z-wiki-filters__actions { display: flex; gap: 8px; margin-left: auto; }

/* Столбец «Wiki» в списке задач: метка-ссылка у отмеченной задачи и кнопка
   «отметить» у остальных. Кнопка выглядит меткой, но остаётся кнопкой формы. */
.z-wiki-cell { width: 78px; white-space: nowrap; }
.z-wiki-mark {
  font: inherit;
  cursor: pointer;
  border: 1px solid var(--z-border);
  background: var(--z-surface);
  color: var(--z-muted-fg);
}
.z-wiki-mark:hover { border-color: var(--z-accent); color: var(--z-accent); }

/* Теги: снятие тега — своя форма внутри метки (вложенные формы недопустимы). */
.z-wiki-tags { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 8px; }
.z-wiki-tag { display: inline-flex; align-items: center; gap: 4px; }
.z-wiki-tag__x {
  border: 0;
  background: none;
  color: inherit;
  cursor: pointer;
  padding: 0 2px;
  font-size: calc(14px * var(--z-text, 1));
  line-height: 1;
}
.z-wiki-tag__x:hover { color: var(--z-danger); }

/* Блок Wiki у задачи: три части в ряд, на узком экране — в столбик. */
.z-wiki-block__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 16px;
}
.z-wiki-block__part h3 { margin-top: 0; }
.z-wiki-readers li { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.z-wiki-newsection { margin-top: 10px; }
.z-wiki-newsection > summary {
  cursor: pointer;
  color: var(--z-accent);
  font-size: calc(13px * var(--z-text, 1));
}

/* Закладка Wiki: узел дерева разделов. Отступ уровня приходит инлайном
   переменной `--z-wiki-depth` — так вложенность видна и остаётся произвольной. */
.z-wiki-node { margin: 0 0 14px calc(var(--z-wiki-depth, 0) * 16px); }
.z-wiki-node--bare { margin-left: 0; }
.z-wiki-node__head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 0;
  font-size: calc(14px * var(--z-text, 1));
  font-weight: var(--z-heading-weight);
  color: var(--z-heading-fg);
}
.z-wiki-node__empty { padding: 2px 0 8px; }
.z-wiki-tree__no { width: 110px; }
.z-wiki-tree__note { margin-top: 4px; max-width: 620px; white-space: pre-wrap; }
.z-wiki-side__tags { display: flex; flex-wrap: wrap; gap: 6px; padding: 6px 12px 10px; }

/* Управление деревом разделов: свёрнутый блок — разрушающие действия не
   должны попадаться под руку при чтении дерева. */
.z-wiki-manage { margin-top: 14px; }
.z-wiki-manage__summary {
  padding: 10px 14px;
  cursor: pointer;
  font-size: calc(13.5px * var(--z-text, 1));
  font-weight: var(--z-heading-weight);
  color: var(--z-heading-fg);
}
.z-wiki-quick { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 12px; }
.z-wiki-quick .z-input { flex: 1 1 200px; min-width: 0; }

/* --- Управление Wiki: описание раздела, порядок и «＋ подраздел» в дереве ---
   Управление стоит рядом с разделом, который оно меняет: раньше порядок и
   подразделы заводились только в свёрнутом блоке внизу закладки, и человек
   уходил из дерева, чтобы вернуться в него. Кнопки мелкие и без рамки —
   в дереве они служебные, а не главные. */
.z-wiki-node__desc {
  margin: -2px 0 6px;
  max-width: 720px;
  white-space: pre-wrap;
}
.z-wiki-node__tools {
  display: flex;
  align-items: center;
  gap: 4px;
  margin: 0 0 6px;
  flex-wrap: wrap;
}
.z-wiki-node__tool {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 24px;
  height: 24px;
  padding: 0 6px;
  border: 1px solid var(--z-border);
  border-radius: 6px;
  background: var(--z-surface);
  color: var(--z-muted-fg);
  font-size: calc(12px * var(--z-text, 1));
  line-height: 1;
  cursor: pointer;
}
.z-wiki-node__tool:hover { border-color: var(--z-accent); color: var(--z-accent); }
.z-wiki-node__add { position: relative; }
.z-wiki-node__add > summary { list-style: none; }
.z-wiki-node__add > summary::-webkit-details-marker { display: none; }
.z-wiki-node__addform {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin: 8px 0 10px;
  padding: 8px;
  border: 1px solid var(--z-border);
  border-radius: 8px;
  background: var(--z-surface-2);
}
.z-wiki-node__addform .z-input { flex: 1 1 180px; min-width: 0; }

/* --- Обсуждение темы в блоке Wiki --- */
.z-wiki-comments__list {
  list-style: none;
  margin: 0 0 10px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: 320px;
  overflow-y: auto;
}
.z-wiki-comments__item {
  padding: 8px 10px;
  border: 1px solid var(--z-border);
  border-radius: 8px;
  background: var(--z-surface-2);
}
.z-wiki-comments__body {
  margin-top: 2px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.z-wiki-comments__form { display: flex; flex-direction: column; gap: 8px; }
.z-wiki-comments__form .z-form__actions { margin-top: 0; }

/* --- «Новая тема Wiki» в закладке: свёрнутая форма, чтобы не занимать
   место над деревом, но и не прятаться в другой раздел. --- */
.z-wiki-newtopic { margin-bottom: 12px; }
.z-wiki-newtopic__summary {
  padding: 10px 14px;
  cursor: pointer;
  font-size: calc(13.5px * var(--z-text, 1));
  font-weight: var(--z-heading-weight);
  color: var(--z-heading-fg);
}

@media (max-width: 700px) {
  .z-wiki-filters__actions { margin-left: 0; }
  .z-wiki-node { margin-left: 0; }
}

/* --- «API портала»: блок кода с копированием и шаги инструкции -----------
   Появились вместе со страницей /api.php: инструкция «положи файл в каталог
   навыков» состоит из команд, и команду надо копировать целиком, а не
   перепечатывать — опечатка в пути выглядит как «навык не подключился».
   Размеры — через множитель --z-text, как везде (ловушка 17 в AGENTS.md). */

.z-code {
  margin: 0;
  /* Сверху оставлена полоса под кнопку копирования: иначе кнопка ложится
     на первую строку команды. */
  padding: 34px 14px 12px;
  border: 1px solid var(--z-border);
  border-radius: 10px;
  background: var(--z-surface-2);
  color: var(--z-fg);
  font-size: calc(12.5px * var(--z-text, 1));
  line-height: 1.55;
  /* Строки переносятся, а не уезжают вправо: инструкция читается на телефоне
     целиком, а длинная команда копируется кнопкой и не требует горизонтальной
     прокрутки вслепую. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.z-code__wrap {
  position: relative;
  margin: 8px 0;
}

/* Кнопка копирования — в углу блока; отступ сверху у кода её не перекрывает,
   потому что она вынесена над текстом, а не поверх первой строки. */
.z-code__wrap > .z-copy {
  position: absolute;
  top: 8px;
  right: 8px;
}

.z-steps {
  margin: 8px 0 0;
  padding-left: 22px;
}

.z-steps > li + li { margin-top: 10px; }

.z-steps__hint {
  margin-top: 6px;
  font-size: calc(12.5px * var(--z-text, 1));
  color: var(--z-muted-fg);
}

.z-h3 {
  margin: 4px 0 6px;
  font-size: calc(14px * var(--z-text, 1));
  font-weight: var(--z-heading-weight);
  color: var(--z-heading-fg);
}

/* ==========================================================================
   11. ПЕРЕКЛЮЧАТЕЛЬ РЕЖИМА И ГАЛЕРЕЯ ТЕМ (themes.php)

   Переключатель в шапке стал ФОРМОЙ: нажатие сохраняет режим на сервере
   (`Users`.`Theme`), а не только в браузере. Раньше это были кнопки, писавшие
   выбор в localStorage, и сохранённое в «Настройках» оформление переставало
   действовать — владелец видел это как «настройки сбиваются».

   Превью палитры — обычная разметка на ТЕХ ЖЕ токенах: карточка несёт ключ
   `data-palette`, блок палитры в разделе 1 переопределяет токены на ней самой,
   поэтому миниатюра рисуется той палитрой, которую выбирают. Литеральных
   цветов здесь нет (AGENTS.md §4 п.3).
   ========================================================================== */

.z-themes form { margin: 0; }
.z-themes--icons button {
  width: 30px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.z-themes .z-ico { width: 16px; height: 16px; }

.z-palette-group + .z-palette-group { margin-top: 28px; }
.z-palette-grid {
  display: grid;
  gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(256px, 1fr));
}

.z-palette-card {
  display: flex;
  flex-direction: column;
  background: var(--z-surface);
  border: 1px solid var(--z-border);
  border-radius: var(--z-radius);
  overflow: hidden;
}
.z-palette-card.is-current {
  border-color: var(--z-accent);
  box-shadow: inset 0 0 0 1px var(--z-accent);
}
.z-palette-card__preview {
  padding: 12px;
  background: var(--z-bg);
  color: var(--z-fg);
  border-bottom: 1px solid var(--z-border);
}
.z-palette-card__meta {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px 12px 12px;
  flex: 1 1 auto;
}
.z-palette-card__name {
  font-size: calc(13.5px * var(--z-text, 1));
  font-weight: var(--z-heading-weight);
  color: var(--z-heading-fg);
}
.z-palette-card__foot {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: auto;
  padding-top: 6px;
}

/* --- миниатюра: окно приложения, собранное из токенов палитры --- */
.z-pv {
  border: 1px solid var(--z-border);
  border-radius: var(--z-radius-sm);
  background: var(--z-surface);
  overflow: hidden;
}
.z-pv__top {
  display: flex;
  align-items: center;
  gap: 4px;
  height: 18px;
  padding: 0 6px;
  background: var(--z-surface-2);
  border-bottom: 1px solid var(--z-border);
}
.z-pv__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--z-accent);
}
.z-pv__pill {
  width: 14px;
  height: 4px;
  border-radius: 2px;
  background: var(--z-border);
}
.z-pv__pill--accent { background: var(--z-primary); }
.z-pv__body { display: flex; min-height: 104px; }
.z-pv__side {
  flex: 0 0 46px;
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 7px 6px;
  background: var(--z-surface-2);
  border-right: 1px solid var(--z-border);
}
.z-pv__nav {
  height: 4px;
  border-radius: 2px;
  background: var(--z-border);
}
.z-pv__nav.is-on { background: var(--z-accent); }
.z-pv__main {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px;
}
.z-pv__title {
  width: 58%;
  height: 7px;
  border-radius: 2px;
  background: var(--z-fg);
}
.z-pv__line {
  width: 90%;
  height: 4px;
  border-radius: 2px;
  background: var(--z-muted-fg);
}
.z-pv__line--short { width: 64%; }
.z-pv__row { display: flex; align-items: center; gap: 5px; }
.z-pv__btn {
  width: 34px;
  height: 12px;
  border-radius: 4px;
  background: var(--z-primary);
}
.z-pv__chip {
  width: 26px;
  height: 12px;
  border-radius: 999px;
  background: var(--z-accent-soft);
  border: 1px solid var(--z-accent);
}
.z-pv__table {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 3px;
}
.z-pv__table span { height: 6px; border-radius: 2px; background: var(--z-row-alt); }
.z-pv__table span:nth-child(2n) { background: var(--z-surface-2); }
.z-pv__field {
  height: 11px;
  border-radius: 3px;
  background: var(--z-field-bg);
  border: 1px solid var(--z-field-border);
}

.z-break { word-break: break-all; }

/* --- Карточка выбора схемы — форма ------------------------------------------
   Владелец: «выбор должен меняться не только при клике по кнопке, но и при
   клике на любую часть окна с вариантом темы». Поэтому карточка — <form>,
   а обработчик в z.js отправляет её по клику на любое место (кроме самой
   кнопки — она отправит сама). Без скрипта остаётся кнопка. */
form.z-palette-card {
  cursor: pointer;
  transition: border-color .15s ease, box-shadow .15s ease;
}
form.z-palette-card:hover { border-color: var(--z-ring); box-shadow: var(--z-shadow); }
form.z-palette-card:focus-within { border-color: var(--z-accent); }

/* --- Всплывающее превью на 50 % от настоящего ------------------------------
   Окно — 720×500, потому что внутри лежит макет страницы 1440×1000,
   уменьшенный вдвое (transform: scale(.5)). Позицию считает скрипт: окно
   должно помещаться в экран у любой карточки. */
.z-palette-pop {
  position: fixed;
  z-index: 90;
  display: none;
  width: 720px;
  height: 500px;
  overflow: hidden;
  border: 1px solid var(--z-border);
  border-radius: var(--z-radius);
  background: var(--z-bg);
  box-shadow: 0 24px 64px rgba(0, 0, 0, .32);
}
.z-palette-pop.is-open { display: block; }
.z-palette-pop__frame {
  width: 1440px;
  height: 1000px;
  transform: scale(.5);
  transform-origin: top left;
}

/* --- Макет страницы портала для большого превью ----------------------------
   Размеры в пикселях здесь НЕ умножаются на `--z-text`: это картинка
   натуральной страницы, а не текст интерфейса — масштабирует её
   transform в два раза. Цвета — только токены палитры. */
.z-pv2 {
  display: flex;
  width: 1440px;
  min-height: 1000px;
  background: var(--z-bg);
  color: var(--z-fg);
  font-family: var(--z-font);
  font-size: 15px;
  line-height: 1.4;
}
.z-pv2__side {
  flex: 0 0 260px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 18px 12px;
  background: var(--z-chrome);
  border-right: 1px solid var(--z-border);
}
.z-pv2__brand { display: flex; align-items: center; gap: 10px; padding: 0 6px 10px; }
.z-pv2__mark { width: 26px; height: 26px; border-radius: 7px; background: var(--z-brand-mark); }
.z-pv2__bname { font-size: 17px; font-weight: var(--z-heading-weight); color: var(--z-brand-name); }
.z-pv2__nav { display: flex; flex-direction: column; gap: 2px; }
.z-pv2__link { padding: 8px 10px; border-radius: 8px; font-size: 15px; color: var(--z-muted-fg); }
.z-pv2__link.is-on { background: var(--z-accent-soft); color: var(--z-accent); }
.z-pv2__body { flex: 1 1 auto; display: flex; flex-direction: column; min-width: 0; }
.z-pv2__top {
  display: flex;
  align-items: center;
  gap: 14px;
  height: 56px;
  padding: 0 24px;
  background: var(--z-chrome);
  border-bottom: 1px solid var(--z-border);
}
.z-pv2__page { font-size: 16px; font-weight: var(--z-heading-weight); color: var(--z-fg); }
.z-pv2__search {
  margin-left: auto;
  min-width: 260px;
  padding: 7px 12px;
  border: 1px solid var(--z-field-border);
  border-radius: 8px;
  background: var(--z-field-bg);
  color: var(--z-muted-fg);
  font-size: 14px;
}
.z-pv2__seg {
  display: flex;
  gap: 2px;
  padding: 2px;
  border: 1px solid var(--z-border);
  border-radius: 8px;
  background: var(--z-surface);
}
.z-pv2__seg i { width: 28px; height: 24px; border-radius: 6px; }
.z-pv2__seg i.is-on { background: var(--z-surface-2); border: 1px solid var(--z-border); }
.z-pv2__user { display: flex; align-items: center; gap: 8px; font-size: 15px; color: var(--z-fg); }
.z-pv2__user::before {
  content: "";
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--z-accent-soft);
  border: 1px solid var(--z-border);
}
.z-pv2__main { display: flex; flex: 1 1 auto; flex-direction: column; gap: 6px; padding: 22px 24px 28px; }
.z-pv2__h1 { font-size: 26px; font-weight: var(--z-heading-weight); color: var(--z-fg); }
.z-pv2__sub { font-size: 14px; color: var(--z-muted-fg); margin-bottom: 14px; }
.z-pv2__card { border: 1px solid var(--z-border); border-radius: 10px; background: var(--z-surface); overflow: hidden; }
.z-pv2__cardhead { display: flex; align-items: center; gap: 12px; padding: 16px; border-bottom: 1px solid var(--z-border); }
.z-pv2__ico { width: 36px; height: 36px; border-radius: 9px; background: var(--z-accent-soft); }
.z-pv2__cardtitle { font-size: 17px; font-weight: var(--z-heading-weight); color: var(--z-fg); }
.z-pv2__cardsub { font-size: 13px; color: var(--z-muted-fg); }
.z-pv2__cardsearch {
  margin-left: auto;
  padding: 7px 12px;
  border: 1px solid var(--z-field-border);
  border-radius: 8px;
  background: var(--z-field-bg);
  color: var(--z-muted-fg);
  font-size: 14px;
}
.z-pv2__tabs { display: flex; gap: 8px; padding: 10px 16px; border-bottom: 1px solid var(--z-border); }
.z-pv2__tabs span { padding: 5px 10px; border: 1px solid var(--z-border); border-radius: 7px; font-size: 13px; color: var(--z-muted-fg); }
.z-pv2__tabs span.is-on { background: var(--z-surface-2); color: var(--z-fg); }
.z-pv2__tr {
  display: grid;
  grid-template-columns: 1.5fr 1fr .6fr;
  gap: 12px;
  align-items: center;
  height: 46px;
  padding: 0 16px;
  border-top: 1px solid var(--z-border);
}
.z-pv2__tr:nth-child(even) { background: var(--z-row-alt); }
.z-pv2__tr--head {
  height: 38px;
  background: var(--z-surface-2);
  color: var(--z-muted-fg);
  font-size: 11px;
  letter-spacing: .06em;
}
.z-pv2__svc b { display: block; font-size: 14px; font-weight: var(--z-heading-weight); color: var(--z-fg); }
.z-pv2__svc i { display: block; font-size: 12px; font-style: normal; color: var(--z-muted-fg); }
.z-pv2__dim { font-size: 13px; color: var(--z-muted-fg); }
.z-pv2__foot {
  display: flex;
  justify-content: space-between;
  padding: 14px 24px;
  border-top: 1px solid var(--z-border);
  color: var(--z-muted-fg);
  font-size: 12px;
}
.z-pv2__btn {
  display: inline-block;
  padding: 5px 12px;
  border-radius: 6px;
  background: var(--z-primary);
  color: var(--z-on-primary);
  font-size: 13px;
  font-style: normal;
}

/* ---------- Раздел «Статус»: доступность, диски, SSL ----------
   Размеры — только через --z-text (иначе не слушаются «Настройки»),
   цвета — только токенами темы. */
.z-infra-caption {
  margin: 18px 0 10px;
  font-size: calc(13px * var(--z-text, 1));
  font-weight: var(--z-heading-weight);
  color: var(--z-heading-fg);
}
.z-infra-caption:first-of-type { margin-top: 12px; }
.z-infra-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
  font-size: calc(12.5px * var(--z-text, 1));
  color: var(--z-text-fg);
}
.z-infra-table td { vertical-align: top; }
.z-infra-status { white-space: nowrap; }
.z-infra-status__text { font-weight: var(--z-text-weight); }
.z-infra-group td {
  background: var(--z-surface-2);
  color: var(--z-heading-fg);
  font-weight: var(--z-heading-weight);
  font-size: calc(12px * var(--z-text, 1));
  letter-spacing: .02em;
}
.z-infra-name { font-weight: var(--z-text-weight); color: var(--z-text-fg); }
.z-infra-host { min-width: 0; }
.z-infra-failure { display: inline-block; overflow-wrap: anywhere; }
.z-infra-addr {
  font-size: calc(12px * var(--z-text, 1));
  color: var(--z-muted-fg);
}
.z-infra-disk { margin-top: 12px; }
.z-infra-disk__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.z-infra-disk__bar {
  height: 6px;
  margin: 6px 0 4px;
  border-radius: 999px;
  background: var(--z-surface-2);
  overflow: hidden;
}
.z-infra-disk__fill {
  display: block;
  height: 100%;
  border-radius: 999px;
}
.z-infra-disk__fill--ok { background: var(--z-success); }
.z-infra-disk__fill--warn { background: var(--z-warning); }
.z-infra-disk__fill--err { background: var(--z-danger); }
.z-infra-smart {
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--z-border);
}
.z-infra-smart__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 6px;
}
.z-infra-smart__title {
  font-size: calc(12px * var(--z-text, 1));
  font-weight: var(--z-heading-weight);
  color: var(--z-heading-fg);
  letter-spacing: .02em;
}
.z-infra-smart__row {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px;
  padding: 3px 0;
}
.z-infra-smart__name {
  font-size: calc(12.5px * var(--z-text, 1));
  font-weight: var(--z-text-weight);
  color: var(--z-text-fg);
  overflow-wrap: anywhere;
}
.z-infra-smart__meta { flex: 1 1 100%; }

/* На узком экране таблица доступности превращается в карточки
   (.z-table--adaptive), но строка-площадка должна остаться заголовком,
   а не отдельной карточкой. */
@media (max-width: 640px) {
  .z-table--adaptive tr.z-infra-group {
    margin: 14px 0 2px;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: transparent;
  }
  .z-table--adaptive tr.z-infra-group td {
    display: block;
    padding: 0 0 6px;
    background: transparent;
    border-bottom: 1px solid var(--z-border);
  }
  .z-table--adaptive tr.z-infra-group td::before { content: none; }
  .z-table--adaptive tr.z-infra-group:first-child { margin-top: 0; }
}

/* ---------- Статус: компактная доступность и сетка SSL ----------
   Вторая строка записи доступности вынесена в столбец «Адрес»: на широком
   экране запись занимает одну строку. Блок SSL уже карточки-на-всю-ширину,
   а домены идут сеткой: 3 колонки на широком экране, 2 на среднем, 1 на узком. */
.z-infra-table td { vertical-align: middle; }
@media (min-width: 900px) {
  .z-infra-table .z-infra-addr { white-space: nowrap; }
}
.z-infra-ssl { max-width: 1100px; }
.z-infra-ssl__caption { margin: 14px 0 6px; }
.z-infra-ssl__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 8px;
}
.z-infra-ssl__item {
  min-width: 0;
  padding: 8px 10px;
  border: 1px solid var(--z-border);
  border-radius: 8px;
  background: var(--z-surface-2);
}
.z-infra-ssl__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}
.z-infra-ssl__domain {
  min-width: 0;
  font-family: var(--z-mono);
  font-weight: var(--z-text-weight);
  color: var(--z-text-fg);
  overflow-wrap: anywhere;
}
@media (max-width: 640px) {
  .z-infra-ssl { max-width: none; }
}




/* ==========================================================================
   НОВОСТИ: список на главной, архив и полная новость
   ========================================================================== */

.z-news-feed { display: flex; flex-direction: column; gap: 10px; }

/* Строка списка — одна ссылка: дата, заголовок с первыми строками и подсказка
   «Открыть». Ведёт в новое окно, поэтому главная остаётся на месте. */
.z-news-item {
  display: grid;
  grid-template-columns: 104px minmax(0, 1fr) auto;
  align-items: start;
  gap: 14px;
  padding: 14px 16px;
  border: 1px solid var(--z-border);
  border-radius: 12px;
  background: var(--z-surface);
  color: var(--z-text-fg);
  text-decoration: none;
}
.z-news-item:hover,
.z-news-item:focus-visible {
  border-color: var(--z-accent);
  background: var(--z-surface-2);
}
.z-news-item__date {
  padding-top: 2px;
  color: var(--z-muted-fg);
  font-size: calc(12px * var(--z-text, 1));
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.z-news-item__main { display: block; min-width: 0; }
.z-news-item__title {
  display: block;
  color: var(--z-heading-fg);
  font-size: calc(15px * var(--z-text, 1));
  font-weight: var(--z-heading-weight);
  line-height: 1.3;
}
.z-news-item__excerpt {
  display: block;
  margin-top: 4px;
  color: var(--z-muted-fg);
  font-size: calc(13px * var(--z-text, 1));
  line-height: 1.45;
}
.z-news-item__open {
  align-self: center;
  color: var(--z-accent);
  font-size: calc(12px * var(--z-text, 1));
  white-space: nowrap;
}
.z-news-count { margin: 0 0 10px; }

@media (max-width: 640px) {
  .z-news-item { grid-template-columns: 1fr; gap: 4px; padding: 12px 14px; }
  .z-news-item__date { padding-top: 0; }
  .z-news-item__open { display: none; }
}

/* Полная новость: узкая колонка для чтения, типографика без литеральных цветов. */
.z-news-article { max-width: 900px; }
.z-news-article__title {
  margin: 0 0 8px;
  color: var(--z-heading-fg);
  font-size: calc(24px * var(--z-text, 1));
  font-weight: var(--z-heading-weight);
  line-height: 1.25;
}
.z-news-article__meta {
  margin: 0 0 18px;
  color: var(--z-muted-fg);
  font-size: calc(13px * var(--z-text, 1));
}
.z-news-article__body {
  color: var(--z-text-fg);
  font-size: calc(15px * var(--z-text, 1));
  line-height: 1.65;
}
.z-news-article__body p { margin: 0 0 12px; }
.z-news-article__body h2,
.z-news-article__body h3,
.z-news-article__body h4 {
  margin: 22px 0 10px;
  color: var(--z-heading-fg);
  font-weight: var(--z-heading-weight);
  line-height: 1.3;
}
.z-news-article__body h2 { font-size: calc(19px * var(--z-text, 1)); }
.z-news-article__body h3 { font-size: calc(17px * var(--z-text, 1)); }
.z-news-article__body h4 { font-size: calc(15px * var(--z-text, 1)); }
.z-news-article__body ul,
.z-news-article__body ol { margin: 0 0 12px; padding-left: 22px; }
.z-news-article__body li { margin: 4px 0; }
.z-news-article__body blockquote {
  margin: 0 0 14px;
  padding: 8px 14px;
  border-left: 3px solid var(--z-accent);
  background: var(--z-surface-2);
  color: var(--z-fg-soft);
}
.z-news-article__body hr {
  margin: 18px 0;
  border: 0;
  border-top: 1px solid var(--z-border);
}
.z-news-article__body a { color: var(--z-accent); }
.z-news-article__body code {
  padding: 1px 5px;
  border: 1px solid var(--z-border);
  border-radius: 6px;
  background: var(--z-surface-2);
  font-family: var(--z-mono);
  font-size: calc(13px * var(--z-text, 1));
}
.z-news-article__body .z-news-pre {
  margin: 0 0 14px;
  padding: 12px 14px;
  overflow-x: auto;
  border: 1px solid var(--z-border);
  border-radius: 8px;
  background: var(--z-surface-2);
}
.z-news-article__body .z-news-pre code { padding: 0; border: 0; background: transparent; }
.z-news-article__body .z-news-table { margin: 0 0 14px; }
.z-news-article__back { margin-top: 22px; }

/* ==========================================================================
   Справочник компаний — поле выбора с поиском
   (agreements/parts/company-picker.php, assets/js/z.js initCompanyPickers)

   Устройство: обёртка .z-cmp содержит настоящий <select> (значение формы и
   запасной вариант без скрипта), поле поиска и список подсказок. Пока скрипт
   не отработал, виден только <select>: класс `is-ready` добавляет его
   инициализатор. Поэтому «спрятать поле, которое ничего не делает» и
   «показать рабочий список» — одно правило, а не два состояния.
   ========================================================================== */

.z-cmp {
  position: relative;
  display: block;
  min-width: 0;
}

.z-cmp__select { width: 100%; }

/* Поле поиска скрыто до инициализации: без скрипта оно не ищет. */
.z-cmp__input {
  display: none;
  width: 100%;
}

.z-cmp__hint { display: none; margin-top: 6px; }

.z-cmp.is-ready .z-cmp__select { display: none; }
.z-cmp.is-ready .z-cmp__input { display: block; }
.z-cmp.is-ready .z-cmp__hint { display: block; }

/* Список подсказок. `display` здесь НЕ задаётся намеренно: видимостью
   управляет атрибут `hidden`, который снимает скрипт. Любое значение
   display перебило бы его, и пустой список висел бы поверх формы. */
.z-cmp__list {
  position: absolute;
  z-index: 60;
  left: 0;
  right: 0;
  top: calc(100% - 1px);
  max-height: 320px;
  overflow-y: auto;
  padding: 4px;
  border: 1px solid var(--z-field-border);
  border-radius: var(--z-radius-sm);
  background: var(--z-surface);
  box-shadow: 0 12px 28px rgba(0, 0, 0, .18);
}

.z-cmp__item {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 1px 8px;
  padding: 7px 9px;
  border-radius: 6px;
  cursor: pointer;
}

.z-cmp__item:hover,
.z-cmp__item.is-active { background: var(--z-row-hover); }

.z-cmp__label {
  font-size: calc(13.5px * var(--z-text, 1));
  font-weight: 500;
  color: var(--z-heading-fg);
  overflow-wrap: anywhere;
}

.z-cmp__badge {
  align-self: start;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--z-accent-soft);
  color: var(--z-accent);
  font-size: calc(11px * var(--z-text, 1));
  font-weight: 600;
  white-space: nowrap;
}

.z-cmp__sub {
  grid-column: 1 / -1;
  font-size: calc(12px * var(--z-text, 1));
  color: var(--z-muted-fg);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.z-cmp__empty {
  padding: 10px 12px;
  font-size: calc(12.5px * var(--z-text, 1));
  color: var(--z-muted-fg);
}

@media (max-width: 640px) {
  /* На телефоне список не должен занимать весь экран: он и так растянут
     по ширине поля, ограничиваем только высоту. */
  .z-cmp__list { max-height: 46vh; }
}
/* ==========================================================================
   НОВОСТИ: картинки, комментарии, действия администратора
   ========================================================================== */

.z-news-figure { margin: 0 0 18px; text-align: center; }
.z-news-figure img,
.z-news-inline-img {
  display: block;
  max-width: 100%;
  height: auto;
  border: 1px solid var(--z-border);
  border-radius: 12px;
  background: var(--z-surface-2);
}
.z-news-figure img { margin: 0 auto; }
.z-news-inline-img { margin: 8px 0; }
.z-news-figure figcaption {
  margin-top: 6px;
  color: var(--z-muted-fg);
  font-size: calc(12px * var(--z-text, 1));
  line-height: 1.4;
}

.z-news-comments { margin-top: 24px; }
.z-news-comments__empty { margin: 0 0 12px; }
.z-news-comment {
  margin-bottom: 10px;
  padding: 14px 16px;
  border: 1px solid var(--z-border);
  border-radius: 12px;
  background: var(--z-surface);
}
.z-news-comment__head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 6px;
}
.z-news-comment__author {
  color: var(--z-heading-fg);
  font-weight: var(--z-heading-weight);
}
.z-news-comment__time {
  color: var(--z-muted-fg);
  font-size: calc(12px * var(--z-text, 1));
  font-variant-numeric: tabular-nums;
}
.z-news-comment__text {
  color: var(--z-text-fg);
  font-size: calc(14px * var(--z-text, 1));
  line-height: 1.55;
  overflow-wrap: anywhere;
}
.z-news-comment__note {
  margin-top: 10px;
  padding: 8px 12px;
  border-left: 3px solid var(--z-accent);
  background: var(--z-surface-2);
  border-radius: 0 8px 8px 0;
}
.z-news-comment__note-head {
  color: var(--z-muted-fg);
  font-size: calc(11px * var(--z-text, 1));
  text-transform: uppercase;
  letter-spacing: .04em;
}
.z-news-comment__note-text {
  margin-top: 4px;
  color: var(--z-text-fg);
  font-size: calc(13px * var(--z-text, 1));
  line-height: 1.5;
  overflow-wrap: anywhere;
}
.z-news-comment__admin { margin-top: 10px; }
.z-news-comment__admin > summary {
  color: var(--z-accent);
  cursor: pointer;
  font-size: calc(13px * var(--z-text, 1));
}
.z-news-comment__admin-body {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--z-border);
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.z-news-comment__form .z-textarea,
.z-news-comment-add .z-textarea { width: 100%; }
.z-news-comment-add {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--z-border);
}
/* ==========================================================================
   КОММЕНТАРИИ К ЛЮБОЙ СТРАНИЦЕ (подвал) и модерация /comments/
   ========================================================================== */

.z-page-comments {
  margin-top: 20px;
  padding-top: 14px;
  border-top: 1px solid var(--z-border);
  text-align: left;
}
.z-page-comments__details > summary {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  cursor: pointer;
  list-style: none;
  color: var(--z-text-fg);
}
.z-page-comments__details > summary::-webkit-details-marker { display: none; }
.z-page-comments__title { font-weight: var(--z-text-weight, 600); }
.z-page-comments__count {
  min-width: 22px;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--z-surface-2);
  color: var(--z-muted-fg);
  font-size: calc(11px * var(--z-text, 1));
  text-align: center;
}
.z-page-comments__hint { color: var(--z-accent); font-size: calc(12px * var(--z-text, 1)); }
.z-page-comments__all { margin-left: auto; font-size: calc(12px * var(--z-text, 1)); }
.z-page-comments__body { margin-top: 12px; display: flex; flex-direction: column; gap: 10px; }
.z-page-comments__empty { margin: 0; }
.z-page-comments__form { padding-top: 10px; border-top: 1px dashed var(--z-border); }
.z-page-comments__form .z-textarea { width: 100%; }
/* Строка состояния отправки комментария (fetch): «Отправляю…», «добавлен»,
   текст ошибки. Раньше сообщение приходило только перезагрузкой страницы. */
.z-page-comments__note { margin: 0; font-size: calc(12px * var(--z-text, 1)); color: var(--z-muted-fg); }
.z-page-comments__note.is-ok { color: var(--z-success); }
.z-page-comments__note.is-error { color: var(--z-danger); }

.z-comment {
  padding: 12px 14px;
  border: 1px solid var(--z-border);
  border-radius: 12px;
  background: var(--z-surface);
}
.z-comment__head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px 10px;
  margin-bottom: 5px;
}
.z-comment__author { color: var(--z-heading-fg); font-weight: var(--z-heading-weight, 600); }
.z-comment__role,
.z-comment__dept {
  color: var(--z-muted-fg);
  font-size: calc(11px * var(--z-text, 1));
}
.z-comment__time {
  color: var(--z-muted-fg);
  font-size: calc(11px * var(--z-text, 1));
  font-variant-numeric: tabular-nums;
}
.z-comment__page { margin: 2px 0 6px; display: flex; flex-direction: column; gap: 1px; }
.z-comment__page-url {
  color: var(--z-muted-fg);
  font-size: calc(11px * var(--z-text, 1));
  overflow-wrap: anywhere;
}
.z-comment__text {
  color: var(--z-text-fg);
  font-size: calc(13px * var(--z-text, 1));
  line-height: 1.55;
  overflow-wrap: anywhere;
}
.z-comment__note {
  margin-top: 8px;
  padding: 7px 10px;
  border-left: 3px solid var(--z-accent);
  background: var(--z-surface-2);
  border-radius: 0 8px 8px 0;
}
.z-comment__note-head {
  color: var(--z-muted-fg);
  font-size: calc(10px * var(--z-text, 1));
  text-transform: uppercase;
  letter-spacing: .04em;
}
.z-comment__note-text {
  margin-top: 3px;
  font-size: calc(12px * var(--z-text, 1));
  line-height: 1.5;
  overflow-wrap: anywhere;
}
.z-comment-actions { margin-top: 8px; }
.z-comment-actions > summary {
  color: var(--z-accent);
  cursor: pointer;
  font-size: calc(12px * var(--z-text, 1));
}
.z-comment-actions__body {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--z-border);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.z-comment-actions__form .z-textarea { width: 100%; }
/* Отметка «Новость о доработке нужна»: чекбокс и кнопка в одну строку,
   подсказка переносится под них. */
.z-comment-news {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 10px;
  margin: 8px 0 0;
}
.z-comment-news .z-check { margin: 0; }
.z-comment-news .z-small { color: var(--z-muted-fg); }
/* Комментарий администратора и две кнопки: «Отправить» и «Отклонить» —
   одна форма, поэтому причина отклонения занимает свою строку. */
.z-comment-news__label { width: 100%; margin: 2px 0 0; }
.z-comment-news__reason { width: 100%; min-height: 46px; }
.z-comment-news__actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}
/* Режим автовыполнения: включение и состояние — отдельной строкой над KPI. */
.z-comments-auto {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 12px;
  padding: 10px 12px;
  border: 1px solid var(--z-border);
  border-radius: 12px;
  background: var(--z-surface);
}
.z-comments-auto .z-small { color: var(--z-muted-fg); }
.z-comment__auto-reason {
  margin: 6px 0 0;
  color: var(--z-danger);
  font-size: calc(12px * var(--z-text, 1));
}
.z-comment__reject-reason {
  margin: 6px 0 0;
  color: var(--z-danger);
  font-size: calc(12px * var(--z-text, 1));
}

.z-comments-admin { display: flex; flex-direction: column; gap: 14px; }
.z-comments-kpi {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 10px;
}
.z-comments-kpi__card {
  padding: 12px 14px;
  border: 1px solid var(--z-border);
  border-radius: 12px;
  background: var(--z-surface);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.z-comments-kpi__card span {
  color: var(--z-muted-fg);
  font-size: calc(11px * var(--z-text, 1));
  text-transform: uppercase;
  letter-spacing: .04em;
}
.z-comments-kpi__card strong { font-size: calc(24px * var(--z-text, 1)); line-height: 1.1; }
.z-comments-tabs { display: flex; flex-wrap: wrap; gap: 6px; }
.z-comments-tab {
  padding: 5px 10px;
  border: 1px solid var(--z-border);
  border-radius: 999px;
  color: var(--z-muted-fg);
  font-size: calc(12px * var(--z-text, 1));
}
.z-comments-tab span { opacity: .7; }
.z-comments-tab.is-active {
  border-color: var(--z-accent);
  background: var(--z-accent-soft);
  color: var(--z-accent);
}
.z-comments-filters {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}
.z-comments-filters .z-input { min-width: 220px; flex: 1 1 260px; }
.z-comments-filters .z-select { width: auto; min-width: 150px; }
.z-comments-list { display: flex; flex-direction: column; gap: 10px; }
.z-comment-wrap .z-comment { background: var(--z-surface); }

/* Стадия реализации комментария: задача HARNESS и полоса готовности.
   Ширина полосы ограничена, чтобы в карточке подвала она не растягивалась
   на всю строку: это индикатор, а не основной элемент. */
.z-impl-chip { white-space: nowrap; }
.z-comment__impl { margin: 6px 0 2px; display: flex; flex-direction: column; gap: 4px; }
.z-impl__bar {
  width: min(320px, 100%);
  height: 6px;
  border-radius: 999px;
  background: var(--z-surface-2);
  overflow: hidden;
}
.z-impl__fill { display: block; height: 100%; border-radius: inherit; background: var(--z-accent); }
.z-comment__impl-note {
  margin: 0;
  color: var(--z-muted-fg);
  font-size: calc(12px * var(--z-text, 1));
}

@media (max-width: 720px) {
  .z-comments-kpi { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .z-comments-filters .z-input { flex: 1 1 100%; }
  .z-page-comments__all { margin-left: 0; }
}

/* --- Распознавание счёта в форме «Платежи» (комментарий #23) ---------------
   Только токены дизайн-системы: красная метка неподписанного счёта берёт
   сигнальный цвет темы, а не литеральный. */
.z-chip--danger { background: var(--z-danger-soft); color: var(--z-danger); }

.z-agr-invoice {
  margin: 0 0 16px;
  padding: 12px 14px;
  border: 1px solid var(--z-border);
  border-radius: var(--z-radius);
  background: var(--z-surface-2);
}
.z-agr-invoice__row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}
.z-agr-invoice__label { font-weight: 600; }
.z-agr-invoice__file { flex: 1 1 240px; min-width: 0; }
.z-agr-invoice__status { margin: 8px 0 0; }
.z-agr-invoice__status--ok { color: var(--z-success); }
.z-agr-invoice__status--danger { color: var(--z-danger); font-weight: 600; }
.z-agr-invoice__notes { margin: 8px 0 0; padding-left: 18px; }
.z-agr-invoice__mark {
  display: block;
  margin-top: 6px;
  font-size: calc(13px * var(--z-text, 1));
  font-weight: 600;
}
.z-agr-invoice__mark--ok { color: var(--z-success); }
.z-agr-invoice__mark--danger { color: var(--z-danger); }

@media (max-width: 720px) {
  .z-agr-invoice__label { flex: 1 1 100%; }
  .z-agr-invoice__row .z-btn { width: 100%; }
}