/* ui.css — общий интерфейс разделов портала «Полезные разработки».
   Отдаётся из /assets/ (без авторизации, кэш 7 дней) → подключать с ?v=N.

   Правило: раздел подключает этот файл ПЕРВЫМ, свой стиль — после, и в своём
   файле держит только то, чего здесь нет. Кнопки, шапку, таблицы, плашки и
   переключатели не переопределять: разъезжаются именно так.

   Компоненты собраны из «Учёта ГСМ» — там они самые проработанные. Акцент
   переведён на фирменный оранжевый (решение пользователя 2026-08-04).
   Перечень и правила — 01-правила-работы.md, раздел «Единый интерфейс». */

:root {
  color-scheme: light;

  /* Нейтральные */
  --bg: #f5f6f8;
  --card: #ffffff;
  --border: #e3e6eb;
  --border-strong: #b8c0cc;
  --text: #1a1a1a;
  --muted: #6b7280;

  /* Фирменный акцент: логотип, активные вкладки, главные кнопки */
  --primary: #E25A1B;
  --primary-hover: #C24812;
  --primary-soft: #fde8dc;

  /* Состояния — НЕ бренд, менять нельзя: ими кодируются смыслы */
  --green: #16a34a;  --green-bg: #dcfce7;
  --red: #dc2626;    --red-bg: #fee2e2;
  --yellow: #ca8a04; --yellow-bg: #fef3c7;

  --radius: 8px;

  /* Ширина страницы: ДВЕ (решение пользователя 24.09.2026, мокап) — чтобы
     рабочая область не прыгала от раздела к разделу. См. .container */
  --page: 1100px;         /* все разделы — по умолчанию */
  --page-wide: 1400px;    /* широкие таблицы: billing, telemetry */
  --page-narrow: 1100px;  /* устарело: .narrow (было 860 — дашборд, users,
                             backup, spravochniki) теперь = обычной ширине */

  /* Поверхности, которые в тёмной теме меняются. Светлые значения — ровно те,
     что до 24.09.2026 были вписаны в правила числом: для светлой темы замена
     на переменные ничего не меняет. */
  --field: #ffffff;                    /* фон полей и .mp-field (до 25.09.2026 — и кнопок) */
  /* Кнопки — свой токен с 25.09.2026: в тёмной теме они светлее полей (вариант B,
     решение пользователя «слишком тёмный фон… не нажатых кнопок»). Светлые значения
     равны прежним (--field и --border), светлая тема не меняется. В разделах токен
     пишется с запасом `var(--btn-bg, var(--field))` — на старом ui.css из кэша
     кнопка останется прежней, а не прозрачной. */
  --btn-bg: #ffffff;                   /* .btn, .btn-danger, невыбранная .pill-btn */
  --btn-border: #e3e6eb;               /* их рамка */
  --tabs-bg: #eceef2;                  /* подложка вкладок */
  --tab-hover: rgba(255, 255, 255, .55);
  --tab-on: #ffffff;                   /* выбранная вкладка */
  --row-hover: #fafbfc;
  --row-picked: #eaf2fe;
  --row-picked-hover: #e2edfd;
  --primary-dis: #f0b596;              /* отключённая главная кнопка */
  --red-border: #fecaca;               /* «Выйти» */
  --amber-tx: #b45309;                 /* .pill.warn, вкладка «Редактирование» */
  --amber-tx-hover: #92400e;
  --switch-off: #9ca3af;
  --overlay: rgba(15, 23, 42, .35);    /* подложка модалки */
  --toast-bg: #1f2937;
  --page-base: #f5f5f6;                /* цвет под фоном theme.js (серый, без голубизны) */
}

/* ── Тёмная тема (24.09.2026) ──────────────────────────────────────────
   Включается ТОЛЬКО атрибутом <html data-theme="dark">, который ставит
   /assets/theme.js. Сама по себе системная тёмная тема раздел не перекрашивает:
   ui.css на сервере один на всех, а своя вёрстка разделов переводится на
   переменные по очереди. Раздел без theme.js остаётся светлым и целым.
   Палитра — тёмно-синяя, в тон свечению фона theme.js и стеклянной карточке
   страницы входа (вариант «Б», 30.09.2026, мокап утверждён; до того — slate
   #1e293b: серые панели на синем свечении «не бились» с фоном).
   `screen` обязателен: при печати (путевые листы ГСМ, КП) действует светлая
   палитра — светлый текст на белой бумаге не читается. */
@media screen {
  :root[data-theme="dark"] {
    color-scheme: dark;
    --bg: #0f1d31;
    --card: #12243b;
    --border: #25405f;
    --border-strong: #34557a;
    --text: #e2e8f0;
    --muted: #9db3cc;

    --primary-hover: #EE6A2C;          /* на тёмном ховер светлее, а не темнее */
    --primary-soft: rgba(226, 90, 27, .18);

    /* Состояния светлее: тёмно-зелёный на графите не читается */
    --green: #4ade80;  --green-bg: rgba(34, 197, 94, .16);
    --red: #f87171;    --red-bg: rgba(248, 113, 113, .15);
    --yellow: #facc15; --yellow-bg: rgba(234, 179, 8, .16);

    --field: #0a1628;
    /* Вариант B (25.09.2026, мокап утверждён; было rgba(10,17,30,.7) и #26344b —
       полоса вкладок и кнопки читались провалами, «слишком тёмно»): полоса светлее
       панели, выбранная вкладка — цвета панели и перетекает в неё; кнопки — светлее
       полей, поля остаются тёмными. С 30.09 — те же ступени в синем тоне. */
    --tabs-bg: #1b3150;
    --tab-hover: rgba(255, 255, 255, .06);
    --tab-on: #12243b;
    --btn-bg: #1b3150;
    --btn-border: #2f4d70;
    --row-hover: rgba(255, 255, 255, .035);
    --row-picked: rgba(59, 130, 246, .16);
    --row-picked-hover: rgba(59, 130, 246, .22);
    --primary-dis: rgba(226, 90, 27, .35);
    --red-border: rgba(248, 113, 113, .4);
    --amber-tx: #fbbf24;
    --amber-tx-hover: #fcd34d;
    --switch-off: #34557a;
    --overlay: rgba(0, 0, 0, .55);
    --toast-bg: #24405f;
    --page-base: #06111d;
  }
  /* Заливки с БЕЛЫМ текстом: светлые «состояния» тёмной темы под белые
     буквы не годятся — здесь остаются насыщенные цвета светлой палитры. */
  :root[data-theme="dark"] .idle-banner { background: #ca8a04; }
  :root[data-theme="dark"] .toast.ok { background: #16a34a; }
  :root[data-theme="dark"] .toast.err { background: #dc2626; }
  :root[data-theme="dark"] .file-row.ok .f-ic { background: #16a34a; }
  :root[data-theme="dark"] .file-row.err .f-ic { background: #dc2626; }
  /* Пункты выпадающего списка. Всплывающий список <select> Windows рисует
     фоном самого поля, а у поля с `background: transparent` («Связь» в
     «Диспетчере») это белый — и светлый текст тёмной темы на нём пропадал
     (скриншот пользователя 24.09.2026). Цвета пунктов задаём явно. */
  :root[data-theme="dark"] select option,
  :root[data-theme="dark"] select optgroup { background-color: var(--field); color: var(--text); }
  /* Яркие картинки и цвета ДАННЫХ на графите режут глаз (решения пользователя
     24.09.2026 по мокапам). Правило для тёмной темы:
     - картинка (обложка, фото, схема) — темнее на 40% и цвет приглушён:
       класс .dim-in-dark (обложки плиток — вариант «Б»; было brightness(.86));
     - цвет-шкала данных (сетка «Связи» Диспетчера, календари, графики) —
       каждый цвет светлой темы подмешан к цвету панели #12243b на ~35%
       (до 30.09.2026 — к #1e293b):
       тускнеют равномерно, порядок оттенков шкалы и различимость состояний
       сохраняются. Значения — у раздела, в его тёмном блоке. */
  :root[data-theme="dark"] .tile-cover,
  :root[data-theme="dark"] .dim-in-dark { filter: brightness(.6) saturate(.75); }
  :root[data-theme="dark"] .tint-blue   { --tile-bg: #0C447C; --tile-fg: #B5D4F4; }
  :root[data-theme="dark"] .tint-amber  { --tile-bg: #633806; --tile-fg: #FAC775; }
  :root[data-theme="dark"] .tint-coral  { --tile-bg: #712B13; --tile-fg: #F5C4B3; }
  :root[data-theme="dark"] .tint-teal   { --tile-bg: #085041; --tile-fg: #9FE1CB; }
  :root[data-theme="dark"] .tint-violet { --tile-bg: #3C3489; --tile-fg: #CECBF6; }
  :root[data-theme="dark"] .tint-gray   { --tile-bg: #444441; --tile-fg: #D3D1C7; }
}

/* ── Фон страницы: «свечение снизу» + растр из ромбиков ────────────────
   Рисует /assets/theme.js на <canvas class="page-bg">, лежащем под всем
   содержимым. Класс has-page-bg на <html> theme.js ставит сам: у раздела
   без theme.js фон прежний, сплошной --bg. Фон body снимается, иначе он
   закрыл бы холст (отрицательный z-index рисуется ПОД фоном блоков).
   ⚠️ Раздел, который красит body у себя, фон перекроет — такой фон в
   разделе надо убрать при подключении theme.js. */
html.has-page-bg { background: var(--page-base); }
html.has-page-bg body { background: transparent; }
.page-bg { position: fixed; inset: 0; z-index: -1; display: block; pointer-events: none; }
@media print {
  .page-bg, .theme-switch { display: none !important; }
  html.has-page-bg { background: #fff; }
}

* { box-sizing: border-box; }

body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  background: var(--bg); color: var(--text);
  margin: 0; padding: 20px; font-size: 14px; line-height: 1.5;
}

/* Две ширины: .container (1100) и .container.wide (1400, широкие таблицы).
   .narrow больше не используется — оставлен равным обычной ширине, чтобы
   забытая где-то ссылка на него не сузила страницу. */
.container { max-width: var(--page); margin: 0 auto; }
.container.narrow { max-width: var(--page-narrow); }
.container.wide { max-width: var(--page-wide); }

h1 { font-size: 22px; margin: 0 0 4px; font-weight: 600; }
.subtitle { color: var(--muted); font-size: 13px; margin: -8px 0 18px; }
.dim { color: var(--muted); }
.spacer { margin-left: auto; }

/* ── Шапка раздела ─────────────────────────────────────────────────
   Одна на все разделы: логотип · «Промышленное тепло» · название раздела ·
   «К инструментам» · «Выйти». Отдельный <h1> с названием раздела не нужен —
   оно здесь. Разметку копировать из 01-правила-работы.md. */
/* Три зоны. Сетка `1fr auto 1fr` держит название раздела по центру ШАПКИ,
   а не по центру свободного места: боковые блоки разной ширины, и при flex
   заголовок уезжал бы влево-вправо от раздела к разделу. */
/* Свой слой над содержимым (v31, 05.10.2026). С v30 `view-transition-name`
   делает шапку отдельным слоем, и `z-index` меню шестерёнки работал только
   внутри неё: плитки дашборда с анимацией появления — тоже слои, они позже
   в разметке и рисовались ПОВЕРХ меню, клик по пункту попадал в плитку.
   50 — выше всплывашек внутри страниц (до 40), ниже окон, баннеров и
   плавающих кнопок (900+). */
.page-header {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 12px;
  background: var(--card); border: 1px solid var(--border);
  border-radius: 10px; padding: 10px 14px; margin-bottom: 16px;
  position: relative; z-index: 50;
}
.page-header .brand { display: flex; align-items: center; gap: 10px; min-width: 0; }
.page-header .logo { width: 34px; height: 34px; border-radius: 9px; flex: none; object-fit: contain; }
.page-header .brand .name { font-size: 14px; font-weight: 700; line-height: 1.2; white-space: nowrap; }
.page-header .page-title {
  font-size: 20px; font-weight: 700; margin: 0; text-align: center;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.page-header .page-actions { display: flex; align-items: center; gap: 8px; justify-content: flex-end; }

@media (max-width: 820px) {
  /* узко — бренд и кнопки в строку, название раздела отдельной строкой под ними */
  .page-header { grid-template-columns: 1fr auto; }
  .page-header .page-title { grid-column: 1 / -1; order: 3; text-align: left; font-size: 18px; }
}
/* Телефон (утверждено по мокапу 24.09.2026): от бренда — только логотип,
   «← К инструментам» — одной стрелкой (подсказка в title остаётся). Иначе
   строка не помещается: название бренда (155 px) заезжало под кнопки ещё до
   переключателя темы, а с ним «Выйти» уходила за край шапки. */
@media (max-width: 520px) {
  .page-header .brand .name { display: none; }
  .page-header .nav-btn[href="/dashboard/"] { font-size: 0; }
  .page-header .nav-btn[href="/dashboard/"]::before { content: "\2190"; font-size: 13px; }
}

.nav-btn {
  font-size: 13px; padding: 6px 12px; border: 1px solid var(--border);
  border-radius: var(--radius); background: transparent; color: var(--text);
  cursor: pointer; text-decoration: none; font-family: inherit;
  font-weight: 500; white-space: nowrap;
}
.nav-btn:hover { background: var(--bg); }
.nav-btn.danger { color: var(--red); border-color: var(--red-border); }
.nav-btn.danger:hover { background: var(--red-bg); }

/* ── Шапка, вариант Б: плавающая ───────────────────────────────────
   Для разделов-приложений, которые занимают весь экран собственной сеткой
   (гант «Графика занятости»): полосу .page-header туда не вставить, не
   перекроив раскладку. Те же .nav-btn, но зафиксированы в правом верхнем
   углу поверх контента, на белой плашке с тенью — чтобы читались на любом
   фоне. Бренд и название раздела здесь не показываем: место занимает
   рабочая область, название есть в <title> и на плитке дашборда.

   Разметка (порядок кнопок — как в шапке разделов: «К инструментам», затем
   «Выйти»; до 24.09.2026 «Выйти» стоял слева). Переключатель темы theme.js
   ставит первым сам:
   <div class="float-nav">
     <a class="nav-btn" href="/dashboard/">← К инструментам</a>
     <button type="button" class="nav-btn danger" id="btn-logout">Выйти</button>
   </div>

   Раздел, который берёт этот вариант, обычно не может подключить ui.css
   целиком (базовые правила body/input перекроят его вёрстку) — тогда
   подключает файл и гасит базу у себя, СВОИМИ явными размерами полей. */
.float-nav {
  position: fixed; top: 12px; right: 12px; z-index: 900;
  display: flex; align-items: center; gap: 8px;
}
/* inline-flex + своя line-height: <button> и <a> с одинаковым padding дают
   разную высоту (31 против 34 px), и в плавающей полосе это видно. */
.float-nav .nav-btn {
  display: inline-flex; align-items: center; line-height: 1.35;
  background: var(--card); box-shadow: 0 2px 8px rgba(0,0,0,.10);
}
.float-nav .nav-btn:hover { background: var(--bg); }

/* ── Кнопки ────────────────────────────────────────────────────────── */
.btn {
  border: 1px solid var(--btn-border); background: var(--btn-bg); color: var(--text);
  border-radius: var(--radius); padding: 8px 16px; font-family: inherit;
  font-size: 13px; font-weight: 600; cursor: pointer;
}
.btn:hover { background: var(--bg); border-color: var(--primary); color: var(--primary); }
.btn-primary { background: var(--primary); color: #fff; border-color: var(--primary); }
.btn-primary:hover { background: var(--primary-hover); color: #fff; }
.btn-primary:disabled { background: var(--primary-dis); border-color: var(--primary-dis); cursor: not-allowed; }
.btn-danger { color: var(--red); border-color: var(--btn-border); background: var(--btn-bg); }
.btn-danger:hover { background: var(--red-bg); border-color: var(--red); color: var(--red); }
.btn-sm { padding: 5px 10px; font-size: 12px; }

/* Отключённая кнопка обязана ВЫГЛЯДЕТЬ отключённой. Без этого правила обычная
   .btn ничем не отличалась от рабочей и вдобавок подсвечивалась оранжевым по
   ховеру: её жмут и не понимают, почему ничего не происходит (замечено в
   «Телеметрии» 2026-08-27). У .btn-primary приглушение своё, оно выше. */
.btn:not(.btn-primary):disabled { color: var(--muted); background: var(--bg);
  border-color: var(--border); cursor: not-allowed; }
.btn:not(.btn-primary):disabled:hover { color: var(--muted); background: var(--bg);
  border-color: var(--border); }
.btn-primary:disabled:hover { background: var(--primary-dis); border-color: var(--primary-dis); }

/* ── Поля ──────────────────────────────────────────────────────────── */
input, select, textarea {
  font-family: inherit; font-size: 14px; padding: 9px 11px;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--field); color: var(--text); width: 100%;
}
input[type="checkbox"], input[type="radio"] { width: auto; }
/* Галочки и радио — фирменным цветом (24.09.2026). До этого каждый раздел
   писал accent-color сам, а где забыл — галочки были системные синие. */
input[type="checkbox"], input[type="radio"] { accent-color: var(--primary); }
/* Фокус поля — ОДНА обводка 2px поверх рамки. Своё :focus у полей раздел не
   пишет: рамка + outline с отступом давали двойную рамку (страница входа,
   исправлено 24.09.2026). */
input:focus, select:focus, textarea:focus {
  outline: 2px solid var(--primary); outline-offset: -1px; border-color: transparent;
}

/* ── Вкладки: подложка с кнопками, выбранная утоплена ──────────────
   Полоса вкладок — карточка с внутренними отступами, каждая вкладка внутри
   неё кнопка. Выбранная белая и вдавлена внутренней тенью, невыбранная
   полностью сливается с подложкой.

   ⚠️ Подложка и панель раздела — ОДИН блок: у подложки скруглены верхние
   углы и снят нижний край, у панели снят верхний и скруглены нижние. Зазора
   между ними быть не должно, иначе кнопки «отрываются» от карточки.

   ⚠️ Подложка — своя переменная `--tabs-bg` (#eceef2), а не `--bg`: тот
   (#f5f6f8) слишком близок к белому, и утопленная вкладка на нём не читалась
   бы. Выбранная вкладка — `--tab-on`: в светлой теме она белая, как панель,
   в тёмной светлее панели, иначе не отличается от подложки.

   `flex-wrap` обязателен: в «Учёте ГСМ» семь вкладок, в 860 px они в одну
   строку не помещаются.

   До 2026-08-27 вкладки были «папками» — у каждой своя рамка, скругление
   сверху и оранжевая полоса СВЕРХУ у активной. Заменено по мокапу. */
.tabs {
  display: flex; flex-wrap: wrap; gap: 6px; align-items: center; padding: 6px;
  position: relative; z-index: 1;
  background: var(--tabs-bg);
  border: 1px solid var(--border); border-bottom: none;
  border-radius: 12px 12px 0 0;
}
.tab {
  padding: 9px 16px; font-family: inherit; font-size: 13.5px; font-weight: 500;
  color: var(--muted); background: none;
  border: none; border-radius: 8px; cursor: pointer;
}
.tab:hover { color: var(--text); background: var(--tab-hover); }
.tab.active {
  background: var(--tab-on); color: var(--primary); border: none;
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, .10), 0 0 0 1px rgba(0, 0, 0, .04);
}
/* Кнопка, прижатая к правому краю полосы (не вкладка) */
.tab-action { margin-left: auto; align-self: center; }

/* Служебная вкладка «Редактирование» — янтарная и прижата вправо: во всех
   разделах она отличается от рабочих. */
.tab[data-tab="redakt"] { margin-left: auto; color: var(--amber-tx); background: none; }
.tab[data-tab="redakt"]:hover { color: var(--amber-tx-hover); background: var(--tab-hover); }
.tab[data-tab="redakt"].active { background: var(--tab-on); color: var(--amber-tx); }

/* Запертая правами вкладка: видна, но не открывается — та же идиома, что у
   плиток дашборда без доступа. */
.tab.locked { opacity: .45; cursor: not-allowed; }
.tab.locked::after { content: " 🔒"; font-size: 11px; }
.tab.locked:hover { color: inherit; background: none; }


/* ── Панели и заголовки секций ─────────────────────────────────────── */
.panel {
  background: var(--card); border: 1px solid var(--border);
  border-radius: 0 10px 10px 10px; padding: 18px 20px;
}
/* ⚠️ Только для разделов С вкладками: панель продолжает подложку без шва.
   Селектор-сестра, а не просто `.panel`, — иначе у разделов без вкладок
   (billing, ops, users, дашборд) пропала бы верхняя рамка. В «Учёте ГСМ»
   панелей несколько (по одной на вкладку), поэтому `~`, а не `+`. */
.tabs ~ .panel { border-top: none; border-radius: 0 0 12px 12px; }
/* То же, когда панели лежат в общей обёртке (так устроен «Прайс-лист ГАЗ»:
   .tabs + .panels > .panel) — сестра `.tabs ~ .panel` туда не достаёт.
   ⚠️ Анимация появления такой панели — только opacity: сдвиг translateY
   на время анимации отрывает панель от подложки вкладок (24.09.2026). */
.tabs ~ .panels > .panel { border-top: none; border-radius: 0 0 12px 12px; }
.section-title {
  font-size: 22px; font-weight: 700; letter-spacing: .02em;
  margin: 0 0 14px; padding-bottom: 12px;
  border-bottom: 2px solid var(--border); color: var(--text);
}
.toolbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 16px; }
.toolbar .spacer { margin-left: auto; }
.toolbar select, .toolbar input { width: auto; }

/* ── Таблицы ───────────────────────────────────────────────────────── */
.tbl { width: 100%; border-collapse: collapse; }
.tbl th, .tbl td { text-align: left; padding: 8px 12px; border-bottom: 1px solid var(--border); vertical-align: top; }
.tbl th { font-size: 11px; color: var(--muted); font-weight: 600; text-transform: uppercase; letter-spacing: .03em; }
.tbl td.num, .tbl th.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.tbl tbody tr:hover td { background: var(--row-hover); }
.tbl-wrap { overflow-x: auto; }

/* ── Плашки состояний ──────────────────────────────────────────────── */
/* Таблетки — один вид на весь портал (09.10.2026, решение пользователя: «все
   таблетки согласованы с единым стилем», «не такие закруглённые»): высота 26,
   скругление как у кнопок и полей (`--radius`), шрифт 12 / 500. Плашка состояния
   (.pill) и переключатель (.pill-btn) отличаются только цветом. Компактные
   метки внутри ячеек (оператор SIM и т.п.) задают себе высоту 16 и скругление 4 сами. */
.pill { display: inline-flex; align-items: center; gap: 5px; height: 26px; padding: 0 10px;
  box-sizing: border-box; border-radius: var(--radius); font-size: 12px; font-weight: 500;
  line-height: 1; white-space: nowrap; vertical-align: middle; }
.pill.ok { background: var(--green-bg); color: var(--green); }
.pill.warn { background: var(--yellow-bg); color: var(--amber-tx); }
.pill.err { background: var(--red-bg); color: var(--red); }
.pill.muted { background: var(--bg); color: var(--muted); }

/* ── Таблетки-переключатели ────────────────────────────────────────────
   Не плашка состояния (.pill выше), а КНОПКА: нажимается независимо от
   соседей. Для наборов, где выбор не «или-или»: юрлица объекта в
   «Справочниках» (ПТ, ИП, обе разом), группы получателя в «Диспетчере».

   Чекбоксы для такого читаются хуже: строка списка превращается в мелкую
   россыпь квадратиков, а нажимать надо ровно по метке. Здесь вся таблетка —
   мишень, и выбранное видно цветом, а не галочкой.

   Приехали из «Справочников» 2026-09-01, когда понадобились вторым разделом.
   Там своя копия пока осталась — снимется при следующей правке раздела. */
.pills { display: inline-flex; gap: 6px; flex-wrap: wrap; }
.pill-btn { font-family: inherit; font-size: 12px; font-weight: 500; height: 26px; padding: 0 10px;
  border: 1px solid var(--btn-border); border-radius: var(--radius); background: var(--btn-bg);
  color: var(--muted); cursor: pointer; white-space: nowrap; }
.pill-btn:hover { border-color: var(--primary); color: var(--primary); }
.pill-btn.on { background: var(--primary); border-color: var(--primary); color: #fff; }
.pill-btn.on:hover { background: var(--primary-hover); color: #fff; }
.pill-btn:disabled { cursor: default; opacity: .6; }
.pill-btn:disabled:hover { border-color: var(--border); color: var(--muted); }
.pill-btn.on:disabled:hover { background: var(--primary); color: #fff; }

.empty-state {
  color: var(--muted); font-size: 14px; padding: 40px 18px; background: var(--bg);
  border: 1px dashed var(--border); border-radius: var(--radius); text-align: center;
}

/* ── Переключатель ─────────────────────────────────────────────────── */
.switch { display: inline-flex; align-items: center; gap: 9px; cursor: pointer; user-select: none; }
.switch input { position: absolute; width: 0; height: 0; opacity: 0; }
.switch .switch-track { position: relative; flex: none; width: 42px; height: 24px; border-radius: 999px;
  background: var(--switch-off); transition: background .15s; box-shadow: inset 0 0 0 1px rgba(0,0,0,.06); }
.switch .switch-thumb { position: absolute; top: 2px; left: 2px; width: 20px; height: 20px; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.35); transition: left .15s; }
.switch input:checked ~ .switch-track { background: var(--green); }
.switch input:checked ~ .switch-track .switch-thumb { left: 20px; }
.switch input:focus-visible ~ .switch-track { outline: 2px solid var(--primary); outline-offset: 2px; }
.switch .switch-label { font-size: 13px; font-weight: 700; color: var(--muted); }
.switch input:checked ~ .switch-label { color: var(--green); }
.switch-sm { gap: 7px; }
.switch-sm .switch-track { width: 32px; height: 19px; }
.switch-sm .switch-thumb { width: 15px; height: 15px; }
.switch-sm input:checked ~ .switch-track .switch-thumb { left: 15px; }
.switch-sm .switch-label { font-size: 11px; font-weight: 600; }

/* ── Модалка ───────────────────────────────────────────────────────── */
.modal-ov { position: fixed; inset: 0; background: var(--overlay); display: flex;
  align-items: center; justify-content: center; z-index: 9999; padding: 20px; overflow: auto; }
.modal { background: var(--card); border-radius: 12px; padding: 22px; width: 420px;
  box-shadow: 0 10px 40px rgba(0,0,0,.2); }
.modal-wide { width: 640px; }
.modal-h { font-size: 17px; font-weight: 600; margin: 0 0 14px; }
.modal-foot { display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; }
/* Заголовок с крестиком «закрыть» (v32, 05.10.2026, решение пользователя).
   Обязателен у окна-МАСТЕРА: в его подвале только переходы «‹ Назад» /
   «Далее ›», «Отмены» там нет — она стояла между ними и путала порядок.
   Закрывают крестик и Esc; кликом мимо окно с полями не закрывается.
   Разметка: <div class="modal-hd"><h2 class="modal-h">…</h2>
   <button type="button" class="modal-x" aria-label="Закрыть"><svg…/></button></div> */
.modal-hd { display: flex; align-items: flex-start; gap: 12px; margin: 0 0 14px; }
.modal-hd .modal-h { flex: 1 1 auto; min-width: 0; margin: 0; }
.modal-x { flex: none; width: 30px; height: 30px; margin: -5px -8px 0 0; padding: 0;
  display: inline-flex; align-items: center; justify-content: center; border: 0;
  border-radius: var(--radius); background: transparent; color: var(--muted); cursor: pointer; }
.modal-x:hover { background: var(--bg); color: var(--text); }
.modal-x:disabled { opacity: .4; cursor: default; background: transparent; }
.modal-x svg { width: 18px; height: 18px; display: block; }
/* ── Анимации портала (01.10.2026, вариант А — демо утверждено) ──────────
   Только экран и только если в системе НЕ включено «уменьшить движение»;
   печать не задета. Длительности 0,15–0,3 с.
   - Переход между страницами — View Transitions браузера (Chrome, Edge,
     Яндекс, Safari 18+; Firefox меняет страницы мгновенно). Новая страница
     проявляется ПОВЕРХ старой, старая стоит: при встречном гашении между
     ними просвечивал бы белый фон. Тип `portal-nav` — чтобы свои переходы
     внутри страницы («График занятости», startViewTransition) общей
     анимацией не задевались. Шапка — своей группой: стоит на месте.
   - Панель вкладки — класс `.tab-pane`, только прозрачность: сдвиг отрывал
     бы её от подложки вкладок (правило 24.09). Срабатывает при любом
     показе — снят `hidden`, класс `active` дал `display`.
   - Окно — подложка проявляется, `.modal` чуть вырастает; так же при любом
     показе, в том числе окна, добавленного в страницу скриптом.
   - Плитки дашборда — по очереди. */
@keyframes ui-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes ui-pop { from { opacity: 0; transform: scale(.96); } to { opacity: 1; transform: none; } }
@keyframes ui-rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes ui-page-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@media screen and (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; types: portal-nav; }
  .page-header { view-transition-name: portal-head; }
  .float-nav { view-transition-name: portal-float; }
  html:active-view-transition-type(portal-nav)::view-transition-old(root) { animation: none; }
  html:active-view-transition-type(portal-nav)::view-transition-new(root) {
    animation: ui-page-in .24s ease-out both; }
  .tab-pane { animation: ui-fade .16s ease-out; }
  .modal-ov { animation: ui-fade .15s ease-out; }
  .modal { animation: ui-pop .18s ease-out; }
  .tile-grid > .tile { animation: ui-rise .3s ease-out both; }
  .tile-grid > .tile:nth-child(2) { animation-delay: 35ms; }
  .tile-grid > .tile:nth-child(3) { animation-delay: 70ms; }
  .tile-grid > .tile:nth-child(4) { animation-delay: 105ms; }
  .tile-grid > .tile:nth-child(5) { animation-delay: 140ms; }
  .tile-grid > .tile:nth-child(6) { animation-delay: 175ms; }
  .tile-grid > .tile:nth-child(7) { animation-delay: 210ms; }
  .tile-grid > .tile:nth-child(8) { animation-delay: 245ms; }
  .tile-grid > .tile:nth-child(9) { animation-delay: 280ms; }
  .tile-grid > .tile:nth-child(10) { animation-delay: 315ms; }
  .tile-grid > .tile:nth-child(11) { animation-delay: 350ms; }
  .tile-grid > .tile:nth-child(12) { animation-delay: 385ms; }
}


/* ── Idle-логаут: 5 минут, баннер за 30 с ──────────────────────────── */
.idle-banner { position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%);
  background: var(--yellow); color: #fff; padding: 12px 20px; border-radius: var(--radius);
  font-size: 14px; font-weight: 500; z-index: 9998; display: none; align-items: center;
  gap: 16px; box-shadow: 0 4px 16px rgba(0,0,0,.25); cursor: pointer; }
.idle-banner.show { display: flex; }
.idle-banner .countdown { font-variant-numeric: tabular-nums; background: rgba(255,255,255,.25);
  padding: 2px 8px; border-radius: 4px; font-weight: 700; }

/* Атрибут hidden проигрывает display из класса — молча ломает скрытие блоков */
[hidden] { display: none !important; }

/* ── Загрузка файлов: зона, список, статусы ────────────────────────────
   Пришло из «Учёта ГСМ», где повторялось трижды (фото чеков, реестры
   операторов, будущие выгрузки). Классы нейтральные: раздел describes что
   принимает, а вид и поведение — отсюда. */
.dropzone { border: 1px dashed var(--border-strong); border-radius: var(--radius);
  background: var(--bg); text-align: center; padding: 28px 12px; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; gap: 6px; }
.dropzone:hover, .dropzone.over { border-color: var(--primary); background: var(--card); }
.dropzone .dz-ic { width: 34px; height: 34px; border-radius: 50%; background: var(--card);
  border: 1px solid var(--border); display: flex; align-items: center; justify-content: center;
  font-size: 20px; color: var(--muted); }
.dropzone .dz-title { font-size: 15px; font-weight: 600; }
.dropzone .dim { font-size: 12px; }

/* Строка файла: значок состояния слева, действия справа. Значок рисуется
   стилями — символ в разметку не кладём, иначе он мелькает при перерисовке. */
.filelist { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
.file-row { display: flex; align-items: center; gap: 10px; padding: 8px 10px; background: var(--bg);
  border: 1px solid var(--border); border-radius: var(--radius); font-size: 13px; }
.file-row .f-ic { flex: none; width: 18px; height: 18px; border-radius: 50%; box-sizing: border-box;
  display: flex; align-items: center; justify-content: center; font-size: 11px; }
.file-row.wait .f-ic { background: var(--border-strong); }
.file-row.run .f-ic { border: 2px solid var(--border); border-top-color: var(--primary);
  animation: f-spin .8s linear infinite; }
.file-row.ok .f-ic { background: var(--green); color: #fff; animation: f-pop .25s ease-out; }
.file-row.ok .f-ic::after { content: "✓"; }
.file-row.err .f-ic { background: var(--red); color: #fff; animation: f-shake .3s ease-in-out; }
.file-row.err .f-ic::after { content: "✕"; }
.file-row .f-kind { flex: none; font-size: 15px; line-height: 1; }
.file-row .f-name { flex: 1 1 40%; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* ⚠️ Заметка справа обязана СЖИМАТЬСЯ и переноситься: при `flex: none` длинный
   текст ошибки модуля («Модель не вернула ни одной строки (часть 1 (пусто)…»)
   уезжал за правый край панели. Ширину держим долей строки, а не пикселями. */
.file-row .f-note { flex: 1 1 auto; min-width: 0; max-width: 45%; text-align: right;
  color: var(--muted); font-size: 12px; overflow-wrap: anywhere; }
.file-row.err .f-note { color: var(--red); }
/* Файл, до которого разбор не дошёл: остановлен пользователем. */
.file-row.off { opacity: .65; }
.file-row.off .f-ic { background: var(--border-strong); }
.file-row select { flex: none; width: auto; min-width: 170px; height: 32px; padding: 0 8px; font-size: 12px; }
@keyframes f-spin { to { transform: rotate(360deg); } }
@keyframes f-pop { from { transform: scale(.6); opacity: 0; } to { transform: none; opacity: 1; } }
@keyframes f-shake { 25% { transform: translateX(-3px); } 75% { transform: translateX(3px); } }

/* Полоса выполнения без процентов: сервер отдаёт результат целиком, доли
   пришлось бы выдумывать. Живость показывает счётчик секунд рядом. */
.progress { display: flex; align-items: center; gap: 10px; margin-top: 10px;
  font-size: 12px; color: var(--muted); }
.progress .bar { flex: 1; height: 4px; border-radius: 2px; background: var(--bg); overflow: hidden; }
.progress .bar i { display: block; width: 30%; height: 100%; border-radius: 2px;
  background: var(--primary); animation: p-slide 1.1s ease-in-out infinite; }
@keyframes p-slide { 0% { margin-left: -30%; } 100% { margin-left: 100%; } }

/* Панель массовых действий над списком: слева что выделено, справа действия. */
.bulkbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 0 0 12px;
  padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg); font-size: 13px; }
.bulkbar .spacer { margin-left: auto; }
/* Колонка выделения в таблице */
.tbl th.pickcol, .tbl td.pickcol { width: 34px; text-align: center; overflow: visible; }
.tbl tbody tr.picked > td { background: var(--row-picked); }
.tbl tbody tr.picked:hover > td { background: var(--row-picked-hover); }

@media (prefers-reduced-motion: reduce) {
  .file-row .f-ic, .progress .bar i { animation: none !important; }
  .progress .bar i { width: 100%; opacity: .5; }
}

/* ── Плитки разделов ───────────────────────────────────────────────────
   Утверждено 2026-08-12 для дашборда: раздел — плитка с цветной шапкой,
   в шапке иконка (инлайн-SVG, без CDN), ниже название и описание.
   minmax(240px) на обычной ширине (1100px, с 24.09.2026) даёт четыре
   колонки — восемь разделов встают двумя ровными рядами (на прежних 860px
   было три колонки и ряды 3+3+2). Цвет шапки кодирует РАЗДЕЛ, а не состояние, поэтому подложки
   вынесены в отдельные классы `.tint-*`, а не взяты из зелёный/красный/
   жёлтый — те по правилу проекта означают успех/ошибку/внимание. */
.tile-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 12px; }
@media (max-width: 520px) { .tile-grid { grid-template-columns: 1fr; } }

.tile {
  display: block; background: var(--card); border: 1px solid var(--border);
  border-radius: 12px; overflow: hidden; text-decoration: none; color: var(--text);
}
.tile:hover { border-color: var(--primary); }
.tile-head {
  height: 56px; display: flex; align-items: center; justify-content: center;
  background: var(--tile-bg, var(--bg)); color: var(--tile-fg, var(--muted));
}
.tile-head svg { width: 26px; height: 26px; }
/* Обложка вместо иконочной шапки. Пропорция задана явно, иначе до загрузки
   картинки плитка «прыгает» по высоте. Подложка `--tile-bg` работает
   плейсхолдером: пока webp едет, на месте картинки не белая дыра. */
.tile-cover {
  display: block; width: 100%; aspect-ratio: 560 / 215;
  object-fit: cover; background: var(--tile-bg, var(--bg));
}
.tile-body { padding: 11px 13px 13px; }
.tile-title { font-size: 14px; font-weight: 600; display: flex; align-items: center; gap: 5px; }
.tile-title svg { width: 13px; height: 13px; flex: none; color: var(--muted); }
.tile-desc { font-size: 12px; color: var(--muted); line-height: 1.4; margin-top: 2px; }
/* Раздел есть, но учётке не выдан: плитку не прячем — видно, что просить */
.tile.locked { opacity: .55; cursor: not-allowed; }
.tile.locked:hover { border-color: var(--border); }

.tint-blue   { --tile-bg: #E6F1FB; --tile-fg: #0C447C; }
.tint-amber  { --tile-bg: #FAEEDA; --tile-fg: #854F0B; }
.tint-coral  { --tile-bg: #FAECE7; --tile-fg: #993C1D; }
.tint-teal   { --tile-bg: #E1F5EE; --tile-fg: #0F6E56; }
.tint-violet { --tile-bg: #EEEDFE; --tile-fg: #3C3489; }
.tint-gray   { --tile-bg: #F1EFE8; --tile-fg: #444441; }

/* ── Кнопка-иконка и выпадающее меню ───────────────────────────────────
   Кнопка ставится в `.page-actions` рядом с «Выйти» (та же высота, что у
   `.nav-btn`). Меню позиционируется от `.menu-wrap`, а не от шапки:
   иначе при узком экране, где шапка перестраивается в две строки, оно
   уезжало бы от своей кнопки. */
/* Размер задаём отступом, а не числом: так кнопка совпадает по высоте с
   соседней `.nav-btn` (те же 6px сверху/снизу), а не расходится на пару
   пикселей при смене шрифта. */
.icon-btn {
  flex: none; padding: 6px; line-height: 0;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: transparent; color: var(--text); cursor: pointer;
}
.icon-btn svg { width: 17px; height: 17px; display: block; }
.icon-btn:hover { background: var(--bg); }
.icon-btn.open { border-color: var(--primary); color: var(--primary); }

.menu-wrap { position: relative; display: flex; }
.menu {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 60; width: 232px;
  background: var(--card); border: 1px solid var(--border); border-radius: 12px;
  padding: 6px; box-shadow: 0 12px 30px rgba(0,0,0,.18);
}
.menu-title { font-size: 11px; color: var(--muted); padding: 5px 8px 6px; }
.menu a {
  display: flex; align-items: center; gap: 10px; padding: 8px;
  border-radius: var(--radius); text-decoration: none; color: var(--text); font-size: 13px;
}
.menu a:hover { background: var(--bg); }
.menu a svg { width: 17px; height: 17px; flex: none; color: var(--muted); }

/* Короткое сообщение по центру: иконка, заголовок, строка текста, кнопка.
   Ширина уже `.modal` (420px) — форма тут не живёт, только сообщение. */
.modal-sm { width: 340px; text-align: center; }
.modal-ico { line-height: 0; margin-bottom: 12px; color: var(--muted); }
.modal-ico svg { width: 34px; height: 34px; }
.modal-ico.warn { color: var(--yellow); }

/* ── Пикер месяца ──────────────────────────────────────────────────────
   Поле + попап с сеткой месяцев (шапка-год со стрелками, клик по году →
   сетка лет). Разметку рисует `PortalUI.mountMonthPicker` из /assets/ui.js.
   Компонент пришёл из «Учёта ГСМ» (`gsm/js/ui.js` + `.mp*` в его styles.css)
   и поднят сюда 2026-08-12 при добавлении выбора месяца в раздел `/bot/`.
   ⚠️ У ГСМ пока своя копия — при правках держать оба места в согласии,
   пока раздел не переведут на общий файл. */
.mp { position: relative; min-width: 150px; }
.mp-field { width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 8px;
  font: inherit; font-size: 14px; padding: 9px 11px; border: 1px solid var(--border); border-radius: 8px;
  background: var(--field); color: var(--text); cursor: pointer; text-align: left; }
.mp-field:hover { border-color: var(--primary); }
.mp-ico { color: var(--muted); font-size: 12px; flex: none; }
.mp-pop { position: absolute; top: calc(100% + 4px); left: 0; z-index: 40; width: 264px; max-width: 92vw;
  background: var(--card); border: 1px solid var(--border); border-radius: 12px;
  box-shadow: 0 12px 30px rgba(15,23,42,.18); padding: 8px; }
.mp-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
.mp-head-btn { font: inherit; font-weight: 700; font-size: 14px; background: none; border: none;
  color: var(--text); cursor: pointer; padding: 5px 8px; border-radius: 7px; }
.mp-head-btn:hover { background: var(--bg); color: var(--primary); }
.mp-nav { display: flex; gap: 2px; }
.mp-nav button { font: inherit; font-size: 17px; line-height: 1; background: none; border: none;
  color: var(--muted); cursor: pointer; padding: 4px 9px; border-radius: 7px; }
.mp-nav button:hover { background: var(--bg); color: var(--primary); }
.mp-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px; }
.mp-cell { font: inherit; font-size: 13px; padding: 12px 0; background: none; border: none;
  border-radius: 999px; color: var(--text); cursor: pointer; }
.mp-cell:hover { background: var(--bg); }
.mp-cell.now:not(.on) { box-shadow: inset 0 0 0 1.5px var(--primary); color: var(--primary); font-weight: 600; }
.mp-cell.on { background: var(--primary); color: #fff; font-weight: 600; }
.mp-cell.on:hover { background: var(--primary-hover); }
/* Месяц, за который нет данных: кликать можно, но видно, что пусто. */
.mp-cell.empty:not(.on) { color: var(--muted); opacity: .55; }

/* ── Карточка с заголовком ─────────────────────────────────────────────
   Самостоятельный блок раздела: заголовок, подпись-пояснение и кнопки
   справа. В отличие от `.panel` не склеена с вкладками — у неё скруглены
   все четыре угла. Пришла из «Системной панели» (2026-08-12). */
.card {
  background: var(--card); border: 1px solid var(--border);
  border-radius: 10px; padding: 15px 18px 18px; margin-bottom: 14px;
}
.card > h2 {
  font-size: 15px; font-weight: 600; margin: 0 0 13px;
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.card > h2 .hint { font-size: 11px; color: var(--muted); font-weight: 400; }
.card > h2 .right { margin-left: auto; display: flex; gap: 6px; }

/* ── Плитки метрик ─────────────────────────────────────────────────────
   Число крупно, подпись мелко, при необходимости — полоса заполнения.
   Цвет значения кодирует состояние, поэтому берётся из состояний. */
.metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; }
.metric { background: var(--bg); border-radius: var(--radius); padding: 11px 13px; }
.m-label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .03em; }
.m-value { font-size: 20px; font-weight: 700; margin-top: 2px; font-variant-numeric: tabular-nums; }
.m-value.green { color: var(--green); }
.m-value.yellow { color: var(--yellow); }
.m-value.red { color: var(--red); }
.m-sub { font-size: 11px; color: var(--muted); margin-top: 3px; line-height: 1.35; }
.meter { height: 4px; margin-top: 7px; border-radius: 2px; background: var(--border); overflow: hidden; }
.meter span { display: block; height: 100%; background: var(--green); }
.meter span.yellow { background: var(--yellow); }
.meter span.red { background: var(--red); }

/* ── Тост и спиннер в кнопке ───────────────────────────────────────────
   Тост сообщает результат действия (рестарт, ошибка запроса) и гаснет сам. */
.toast {
  position: fixed; right: 16px; bottom: 16px; z-index: 10001;
  max-width: 420px; padding: 11px 16px; border-radius: var(--radius);
  background: var(--toast-bg); color: #fff; font-size: 13px; font-weight: 500;
  box-shadow: 0 6px 20px rgba(0,0,0,.25);
  opacity: 0; transform: translateY(8px); pointer-events: none;
  transition: opacity .15s, transform .15s;
}
.toast.show { opacity: 1; transform: none; }
.toast.ok { background: var(--green); }
.toast.err { background: var(--red); }
.spin {
  display: inline-block; width: 11px; height: 11px; margin-right: 6px;
  vertical-align: -1px; border: 2px solid rgba(255,255,255,.45);
  border-top-color: #fff; border-radius: 50%; animation: ui-spin .7s linear infinite;
}
@keyframes ui-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .spin { animation: none; } }

/* ── Календарь дней (PortalUI.mountDayCalendar) ────────────────────────
   Поднят из «Учёта ГСМ» 07.09.2026: там он появился первым, вторым понадобился
   «Диспетчеру» — дни ряда связи. Цвет помеченного дня раздел задаёт сам через
   переменные --c / --cbg / --ctx на классе метки. */
.daycal .dc-head { margin-bottom: 8px; }
.daycal .dc-head .mp { max-width: 190px; }
.dc-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; }
.dc-dow { font-size: 10px; color: var(--muted); text-align: center; padding-bottom: 2px; }
.dc-dow.we { color: var(--red); }
.dc-day { font: inherit; font-size: 12px; padding: 6px 0; text-align: center; background: none;
  border: 1px solid transparent; border-radius: 7px; color: var(--text); cursor: pointer; }
.dc-day:hover { background: var(--bg); }
.dc-day.we { color: var(--red); }
.dc-day.today { box-shadow: inset 0 0 0 1.5px var(--primary); font-weight: 700; }
.dc-day.on { background: var(--cbg); color: var(--ctx); border-color: var(--c); font-weight: 600; }
.daycal.locked .dc-grid { opacity: .75; }
.daycal.locked .dc-day { cursor: default; }
.daycal.locked .dc-day:hover { background: none; }
.dc-day.dis { opacity: .3; cursor: not-allowed; text-decoration: line-through; }
.dc-day.dis:hover { background: none; }
/* Дни соседних месяцев в первой и последней неделе (ui.js v15, 01.10.2026):
   выбираются как свои, но приглушены — главным остаётся месяц в шапке. */
.dc-day.out { opacity: .5; }
.dc-day.out:hover { opacity: .85; }
.dc-day.out.dis { opacity: .2; }

/* ── Поле даты (PortalUI.mountDateField, 28.09.2026) ───────────────────
   Поле ввода (дату можно печатать) + кнопка календаря; попап с календарём
   дней, выбранный день — фирменным цветом. Под полем, пока печатают, —
   «распознано: …» или красное «не похоже на дату». Ширину поля задаёт раздел
   (по содержимому: дата — ~150–170 px), высоту — `.df-box`. */
.df-box { display: flex; align-items: center; height: 36px; padding: 0 3px 0 0;
  border: 1px solid var(--border); border-radius: 8px; background: var(--field); }
/* Фокус — общий для портала: одна обводка 2px поверх рамки (правило 24.09),
   только на всём поле, а не на внутреннем input — иначе рамка двойная. */
.df-box:focus-within { outline: 2px solid var(--primary); outline-offset: -1px; border-color: transparent; }
.df .df-in { flex: 1 1 auto; min-width: 0; width: auto; height: 100%; padding: 0 0 0 10px;
  border: none; background: transparent; font: inherit; font-size: 13px; color: var(--text);
  font-variant-numeric: tabular-nums; }
.df .df-in:focus { outline: none; }
.df .df-in::placeholder { color: var(--muted); }
.df-btn { flex: none; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center;
  border: none; border-radius: 6px; background: var(--btn-bg, var(--field)); color: var(--muted); cursor: pointer; }
.df-btn:hover { color: var(--primary); }
.df-btn:disabled { cursor: not-allowed; opacity: .5; }
.df.bad .df-box { border-color: var(--red); }
.df-hint { position: absolute; left: 2px; top: calc(100% + 2px); z-index: 5; white-space: nowrap;
  font-size: 11px; color: var(--muted); background: var(--card); padding: 0 4px; border-radius: 4px; }
.df-hint b { color: var(--text); font-weight: 600; }
.df-hint.err { color: var(--red); }
.df .df-in:disabled { color: var(--muted); cursor: not-allowed; background: transparent; }
/* Открытый попап переносится в <body> (fixed, от экрана) — слой выше окна
   `.modal-ov` (9999), но ниже тоста (10001). */
.df-pop { width: 272px; padding: 10px; z-index: 10000; }
.df-pop .dc-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.df-pop .dc-day.df-on { --c: var(--primary); --cbg: var(--primary); --ctx: #fff; }
.df-foot { display: flex; justify-content: flex-end; gap: 6px; margin-top: 8px; padding-top: 8px;
  border-top: 1px solid var(--border); }
.df-foot:empty { display: none; }
.mp-field:disabled { cursor: not-allowed; color: var(--muted); background: var(--bg); }
.mp-field:disabled:hover { border-color: var(--border); }

/* ── Таблица со страницами и сортировкой (PortalUI.mountTable) ─────────
   Поднято из «Телеметрии» 2026-08-27: там список понадобился дважды —
   журнал приёма файлов и журнал событий прибора. Скрипт — /assets/ui.js. */
.pt-bar { margin-bottom: 8px; flex-wrap: wrap; gap: 8px; }
.pt-foot { margin-top: 8px; }
.pt-slot { display: inline-flex; align-items: center; gap: 8px; }
.bar-label { color: var(--muted); font-size: 13px; white-space: nowrap; }

/* Группа кнопок-переключателей: 10/20/50, «Все/Новые», «График/Список». */
.seg { display: inline-flex; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
.seg-btn {
  border: 0; border-right: 1px solid var(--border);
  background: var(--card); color: var(--text);
  font: inherit; font-size: 13px; padding: 5px 12px;
  cursor: pointer; white-space: nowrap;
}
.seg-btn:last-child { border-right: 0; }
.seg-btn:hover { background: var(--bg); }
.seg-btn.active { background: var(--primary); color: #fff; }

/* Сортировка по клику на заголовок. */
.tbl th.sortable { cursor: pointer; user-select: none; white-space: nowrap; }
.tbl th.sortable::after { content: '↕'; opacity: .25; margin-left: 5px; }
.tbl th.sortable.asc::after { content: '↑'; opacity: 1; color: var(--primary); }
.tbl th.sortable.desc::after { content: '↓'; opacity: 1; color: var(--primary); }

/* Фиксированная раскладка обязательна, если содержимое ячеек меняется на
   месте: иначе колонка подстраивается под новое значение и вся таблица едет.
   Ширины колонок раздел задаёт у себя. */
.tbl.tbl-fixed { table-layout: fixed; }
/* :not(.pickcol) обязателен — иначе многоточие рисуется и в колонке галочки:
   чекбокс не текст, но браузер всё равно считает содержимое переполненным
   и ставит точку рядом с ним. */
.tbl.tbl-fixed th:not(.pickcol), .tbl.tbl-fixed td:not(.pickcol) {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ── Растягиваемые колонки (18.09.2026) ───────────────────────────────
   Ручка у правой границы заголовка — PortalUI.resizableColumns в ui.js;
   mountTable подключает её сам. Внутри <th>, а не за краем: у фиксированных
   таблиц заголовок обрезает переполнение, и выступающая ручка пропала бы. */
.tbl th { position: relative; }
.col-rs { position: absolute; top: 0; right: 0; width: 8px; height: 100%;
  cursor: col-resize; z-index: 2; touch-action: none; }
.col-rs::after { content: ''; position: absolute; top: 20%; bottom: 20%; right: 3px;
  width: 2px; border-radius: 1px; background: transparent; }
.tbl th:hover .col-rs::after { background: var(--border-strong); }
.col-rs:hover::after, .col-rs.on::after { background: var(--primary); }
body.col-resizing, body.col-resizing * { cursor: col-resize !important; user-select: none !important; }

/* ── Переключатель с бегунком и иконками по краям (24.09.2026) ─────────
   Утверждён для выбора темы, годится для любого «или-или» с двумя иконками:
   иконка · дорожка с бегунком · иконка. Не путать с `.switch` выше — тот
   «вкл/выкл» с подписью, этот выбирает одно из двух.

   Поведение (решения пользователя):
   - клик в ЛЮБОЕ место переключает, в том числе по иконке уже выбранного
     значения — промахнуться нельзя;
   - выбранная иконка горит в полную силу, невыбранная приглушена.

   Разметка (иконки — инлайн-SVG 24×24, stroke="currentColor"):
   <button type="button" class="knob-switch" role="switch" aria-checked="false">
     <span class="ks-ico ks-off">…svg…</span>
     <span class="ks-track"><span class="ks-knob"></span></span>
     <span class="ks-ico ks-on">…svg…</span>
   </button>
   aria-checked="true" — бегунок справа, горит правая иконка. Переключать
   атрибут — дело скрипта раздела (для темы — /assets/theme.js). */
.knob-switch {
  --ks-track: #d8dde6;
  --ks-track-sh: inset 0 2px 4px rgba(15, 23, 42, .22), 0 1px 0 rgba(255, 255, 255, .9);
  --ks-knob: linear-gradient(#ffffff, #eef1f5);
  --ks-knob-sh: 0 2px 5px rgba(15, 23, 42, .28);
  display: inline-flex; align-items: center; gap: 8px; padding: 0;
  border: 0; background: none; color: var(--muted); font: inherit;
  cursor: pointer; user-select: none; -webkit-tap-highlight-color: transparent;
}
:root[data-theme="dark"] .knob-switch {
  --ks-track: #0b1829;                 /* в синем тоне с 30.09.2026 (было #1b2232) */
  --ks-track-sh: inset 0 2px 4px rgba(0, 0, 0, .55), 0 1px 0 rgba(255, 255, 255, .05);
  --ks-knob: linear-gradient(#8fa3bd, #6d819c);
  --ks-knob-sh: 0 2px 4px rgba(0, 0, 0, .5), inset 0 1px 0 rgba(255, 255, 255, .25);
}
.knob-switch:focus-visible { outline: 2px solid var(--primary); outline-offset: 3px; border-radius: 999px; }
.ks-ico { display: flex; line-height: 0; opacity: .5; transition: opacity .2s, color .2s, filter .2s; }
.ks-ico svg { width: 17px; height: 17px; }
.knob-switch:hover .ks-ico { opacity: .7; }
.knob-switch[aria-checked="false"] .ks-off,
.knob-switch[aria-checked="true"] .ks-on { opacity: 1; color: var(--text); }
.ks-track {
  position: relative; flex: none; width: 46px; height: 24px; border-radius: 999px;
  background: var(--ks-track); box-shadow: var(--ks-track-sh);
}
.ks-knob {
  position: absolute; top: 2px; left: 2px; width: 20px; height: 20px; border-radius: 50%;
  background: var(--ks-knob); box-shadow: var(--ks-knob-sh); transition: transform .22s ease;
}
.knob-switch[aria-checked="true"] .ks-knob { transform: translateX(22px); }
@media (prefers-reduced-motion: reduce) { .ks-knob, .ks-ico { transition: none; } }

/* Переключатель темы. Место выбирает theme.js (решение пользователя
   24.09.2026 — «в шапку»):
   - есть шапка раздела → первым в `.page-actions`, перед «К инструментам»
     (на дашборде — перед шестерёнкой): во всех разделах в одном месте;
   - есть плавающая `.float-nav` («График занятости») → первым в ней;
   - нет ни того, ни другого (страница входа) → левый нижний угол на плашке.
   Сначала он жил в углу везде, но на экранах 1280–1366 угол накрывал
   содержимое «Чата», «Бота», ГСМ, «Системной панели», а в «Графике
   занятости» — кнопку правки при любой ширине.
   Солнце слева (светлая), луна справа (тёмная): aria-checked="true" = тёмная.
   Выбранное солнце — фирменное оранжевое, выбранная луна — светлая с отсветом. */
.theme-switch[aria-checked="false"] .ks-off { color: var(--primary); }
.theme-switch[aria-checked="true"] .ks-on { color: #f1f5f9; filter: drop-shadow(0 0 4px rgba(147, 197, 253, .6)); }
.theme-switch.in-header { margin-right: 4px; }
/* В плавающей полосе — на той же плашке, что соседние .nav-btn */
.float-nav .theme-switch {
  padding: 5px 10px; border-radius: 999px;
  background: var(--card); border: 1px solid var(--border);
  box-shadow: 0 2px 8px rgba(0, 0, 0, .10);
}
/* Без шапки — в углу на плашке, чтобы читаться поверх фона с растром */
.theme-switch.is-floating {
  position: fixed; left: 14px; bottom: 14px; z-index: 950;
  padding: 5px 10px; border-radius: 999px;
  background: var(--card); border: 1px solid var(--border);
  box-shadow: 0 2px 10px rgba(0, 0, 0, .18);
}

/* ── Телефон (≤ 520 px): своя раскладка, слой 1 (мокап утверждён 24.09.2026) ──
   Общие правила для всех разделов; на компьютере не меняется ничего.
   Особые экраны со своей многоколоночной сеткой (конфигуратор ИРВИС, смета
   Телеметрии, сетка «Связи», гант «Графика занятости») — слой 2: мобильная
   раскладка правилами раздела, по отдельному мокапу. Только screen: печать
   (путевые листы, КП) не трогаем. */
@media screen and (max-width: 520px) {
  body { padding: 12px; }
  .page-header { margin-bottom: 12px; padding: 8px 10px; }
  .panel, .card { padding: 12px; }

  /* Вкладки — одной строкой, листаются пальцем (вместо двух-трёх рядов).
     Полоса прокрутки скрыта: что дальше есть ещё, видно по обрезанной вкладке. */
  .tabs { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
  .tabs::-webkit-scrollbar { display: none; }
  .tabs > * { flex: none; }
  .tab { white-space: nowrap; }

  /* Таблица, которая не влезает, — отдельная область с горизонтальной
     прокруткой: листается она, а страница и шапка стоят на месте. Таблица,
     которая влезает, выглядит как обычно, без полосы. display:block делает
     сам <table> контейнером прокрутки — обёртки в разделах не нужны.
     Только внутри .container: у разделов-приложений (гант) своя раскладка. */
  .container table { display: block; max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch;
    scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
  /* Тонкая постоянная полоса снизу: без неё таблица, обрезанная ровно по
     границе колонки, выглядит законченной — не понять, что она листается.
     (Android/Chrome рисует её всегда; iOS — при касании.) */
  .container table::-webkit-scrollbar { height: 4px; }
  .container table::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 2px; }
  .container table::-webkit-scrollbar-track { background: transparent; }
  /* Текст в ячейках на телефоне переносится — листается только то, что и с
     переносом не влезает. Иначе таблицы, рассчитанные на компьютер (одна
     строка + многоточие при фиксированной раскладке, .tbl-fixed и свои у
     разделов), в режиме прокрутки теряют фиксированную раскладку и
     разворачиваются во всю длину строки: «Организации» в «Справочниках» —
     1763 px. !important — чтобы перебить nowrap раздела любой специфичности.
     Числа не переносятся и так: в них неразрывные пробелы. */
  .container table th, .container table td { white-space: normal !important; }

  /* Панель инструментов: поля и выпадашки — в столбик на всю ширину,
     кнопки остаются своего размера */
  .toolbar > input, .toolbar > select, .toolbar > .mp { flex: 1 1 100%; width: 100%; }
  .toolbar .spacer { margin-left: 0; }

  /* Метрики — по две в ряд */
  .metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* Окна — на всю ширину экрана */
  .modal-ov { padding: 12px; }
  .modal, .modal-wide, .modal-sm { width: 100%; }
}
