/* workbench.css — общий визуальный слой внутренних инструментов.
 *
 * Собран из трёх разошедшихся копий одного и того же мини-фреймворка:
 *   vendo-spec-converter/src/vendospec/web/static/app.css   (компоненты)
 *   fan-workbench/app/templates_html.py       _CSS          (тёмная тема)
 *   precision-ac-workbench/app/templates_html.py  _CSS      (тёмная тема, таблицы)
 *
 * Словарь токенов взят у них же и не переименован: --bg / --card / --ink /
 * --muted / --line / --accent. Три проекта уже пишут именно так, поэтому
 * подключение к ним не требует правки ни одного стиля — только удаления
 * собственной копии.
 */

/* ── Токены ──────────────────────────────────────────────────────────── */

:root {
  /* Нативные контролы — календарь в input[type=date], выпадающий список
     select, полосы прокрутки — красятся браузером, а не нашим CSS. Без этой
     строки в тёмной теме они остаются светлыми и выбиваются из страницы. */
  color-scheme: light dark;

  --bg: #f6f7f9;
  --card: #ffffff;
  --ink: #1d2430;
  --muted: #626c7c;
  --line: #dfe3e9;

  --accent: #2f5d9e;
  --on-accent: #ffffff;

  /* Смысловые цвета. --tight отдельно от --warn: у подбора это разные вещи —
     «впритык, но проходит» и «предупреждение». Совпадение оттенков случайно,
     разводить их по разным переменным нужно, чтобы можно было развести цвета.
     Значения подобраны под контраст ≥4.5 на своих подложках: исходный
     #b8860b давал на --warn-bg всего 2.97, а метки набраны 12-м кеглем. */
  --ok: #2b752f;
  --tight: #8a6100;
  --warn: #8a6100;
  --bad: #b3261e;
  /* Отдельно от --warn: у позиции с устаревшим результатом (входные данные
     поменялись после последнего расчёта) смысл другой, чем у предупреждения
     — не "проверь", а "пересчитай". Тот же класс проблемы, что развела
     --tight и --warn выше: два похожих по цвету, но разных по действию
     состояния под одной переменной сливаются на письме и в разметке. */
  --stale: #9a3412;

  --ok-bg: #e8f3e9;
  --warn-bg: #fdf4dd;
  --bad-bg: #fbe7e6;
  --grey-bg: #eef0f3;
  --stale-bg: #ffedd5;

  /* Подложка шапки таблицы. Отдельный токен, а не --grey-bg: в тёмной теме
     они расходятся — строка заголовка должна остаться отличимой от строк. */
  --th-bg: #eef1f5;

  --radius: 6px;
  --radius-lg: 10px;
  --shadow: 0 12px 40px rgba(0, 0, 0, .25);

  --space: 14px;
  --space-lg: 20px;

  --font: 15px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  --wrap: 1180px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #14171c;
    --card: #1b1f26;
    --ink: #e6e9ee;
    --muted: #98a1ad;
    --line: #2c323b;

    --accent: #5b8ede;
    /* Тёмный текст на светлой кнопке, а не белый. Акцент в тёмной теме
       обязан быть светлым, иначе ссылки на карточке не читаются, — но белым
       по нему выходит контраст 3.30. Разводится это только сменой цвета
       текста на кнопке, поэтому --accent и --on-accent разные переменные. */
    --on-accent: #0f1319;

    /* Светлые смысловые цвета на тёмном фоне нечитаемы — осветляем и текст,
       и подложки. В исходных копиях это было сделано только для бейджей,
       из-за чего таблицы и плашки в тёмной теме теряли смысл. */
    --ok: #7cc47f;
    --tight: #e0b64a;
    --warn: #e0b64a;
    --bad: #f08b85;
    --stale: #f0975a;

    --ok-bg: #1e3a20;
    --warn-bg: #3b3016;
    --bad-bg: #3d1f1d;
    --grey-bg: #252a32;
    --stale-bg: #3d2411;
    --th-bg: #222731;
  }
}

/* ── Основа ──────────────────────────────────────────────────────────── */

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: var(--font);
}

.wrap {
  max-width: var(--wrap);
  margin: 0 auto;
  padding: 0 var(--space-lg);
}

h1 { font-size: 22px; margin: 0 0 6px; }
h2 { font-size: 17px; margin: 28px 0 10px; }
.muted { color: var(--muted); font-size: 13px; }

/* ── Шапка ───────────────────────────────────────────────────────────── */

.app-header {
  background: var(--card);
  border-bottom: 1px solid var(--line);
  margin-bottom: 26px;
}
.app-header .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space);
  min-height: 58px;
}

.brand {
  color: var(--ink);
  text-decoration: none;
  font-size: 16px;
  white-space: nowrap;
}
.brand b { color: var(--accent); }

/* Навигация переносится по словам и прокручивается, а не ломает шапку.
   Фиксированная высота 58px и nowrap на всё меню — это то, из-за чего
   в CDW на 1440px логотип уезжал во вторую строку, а меню в третью. */
.app-header nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 6px 20px;
  padding: 8px 0;
}
.app-header nav a {
  color: var(--muted);
  text-decoration: none;
  font-size: 14px;
  white-space: nowrap;
}
.app-header nav a:hover,
.app-header nav a[aria-current] { color: var(--accent); }

@media (max-width: 720px) {
  .app-header .wrap { flex-direction: column; align-items: stretch; }
  .app-header nav { justify-content: flex-start; }
}

/* ── Карточка ────────────────────────────────────────────────────────── */

.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--space-lg);
  margin-bottom: 18px;
  /* Без этого широкая таблица распирает карточку и горизонтально едет вся
     страница вместо того, чтобы прокручиваться внутри своего контейнера. */
  overflow: hidden;
  min-width: 0;
}

/* ── Сетки ───────────────────────────────────────────────────────────── */

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space);
  /* start, а не stretch: иначе пустое поле в ячейке растягивается по высоте
     соседа с длинной подсказкой. Ровно это и происходит в форме CDW. */
  align-items: start;
}
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; align-items: start; }
@media (max-width: 820px) { .grid2 { grid-template-columns: 1fr; } }

/* ── Поля ввода ──────────────────────────────────────────────────────── */

label { display: block; font-size: 13px; color: var(--muted); margin: 8px 0 4px; }

input[type=text], input[type=number], input[type=date], input[type=file],
input:not([type]), textarea, select {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--ink);
  font: inherit;
  font-size: 14px;
}
input:focus-visible, textarea:focus-visible, select:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* Подсказка под полем. Мельче и тусклее самого значения — иначе объяснение
   перевешивает данные, как сейчас на первом экране подбора завес. */
.hint { display: block; font-size: 12px; color: var(--muted); margin-top: 4px; }

/* ── Кнопки ──────────────────────────────────────────────────────────── */

button, .btn-link {
  display: inline-block;
  background: var(--accent);
  color: var(--on-accent);
  border: 0;
  border-radius: var(--radius);
  padding: 10px 20px;
  font: inherit;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
}
button:hover, .btn-link:hover { filter: brightness(1.08); }

/* Вторичное действие. Смысл разделения: на экране должно быть видно одно
   главное действие, остальные — тише по весу, но на месте. */
button.ghost, .btn-link.ghost {
  background: transparent;
  color: var(--muted);
  border: 1px solid var(--line);
  font-weight: 400;
}
button.ghost:hover, .btn-link.ghost:hover { color: var(--accent); filter: none; }

button[disabled] { opacity: .5; cursor: not-allowed; }

/* ── Таблицы ─────────────────────────────────────────────────────────── */

table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
th, td {
  border: 1px solid var(--line);
  padding: 6px 9px;
  text-align: left;
  vertical-align: top;
  /* break-word, а не anywhere: anywhere рвёт и обычные слова —
     «Канальный» превращался в «Каналь/ный». Длинные артикулы вроде
     FP-34WA-Z3-G30/B.ZL break-word переносит так же. */
  overflow-wrap: break-word;
}
th { background: var(--th-bg); font-weight: 600; }
td.num, th.num { text-align: right; white-space: nowrap; }

.scroll { overflow-x: auto; max-width: 100%; }
.scroll table { min-width: 100%; }

/* Обрезка длинного значения с начала, а не с конца. Имена файлов вида
   «Ольга_Коняева_+7 918 054_97_74_Мобильный_2026-07-09.mp3» различаются
   хвостом; обрезка справа делает двенадцать строк неотличимыми. */
.ellipsis-start {
  display: block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  direction: rtl;
  text-align: left;
}

/* ── Метки состояния ─────────────────────────────────────────────────── */

.tag {
  display: inline-block;
  padding: 1px 8px;
  border-radius: 20px;
  font-size: 12px;
  white-space: nowrap;
}
.tag.ok       { background: var(--ok-bg);   color: var(--ok); }
.tag.tight    { background: var(--warn-bg); color: var(--tight); }
.tag.minor    { background: var(--warn-bg); color: var(--warn); }
.tag.hard,
.tag.escalate { background: var(--bad-bg);  color: var(--bad); }
.tag.grey     { background: var(--grey-bg); color: var(--muted); }

tr.row-tight, tr.row-minor { background: var(--warn-bg); }
tr.row-hard, tr.row-escalate { background: var(--bad-bg); }

/* .status-badge — тот же визуальный приём, что у .tag выше (тот же
   padding/radius/размер), но отдельный словарь: не ok/tight/minor/hard/
   escalate/grey из vendo-spec-converter, а neutral/ok/warning/stale/error —
   вокабуляр готовности позиции, который сложился в CDW для заявки со
   списком проёмов (unfilled/ready/needs_review/stale/error) и годится
   любому workbench-приложению с таким же списком: вентиляторам,
   прецизионникам. Отдельный класс, а не синонимы под .tag, потому что тут
   разные предметные области, не разные слова для одного и того же. */
.status-badge {
  display: inline-block;
  padding: 1px 8px;
  border-radius: 20px;
  font-size: 12px;
  white-space: nowrap;
}
.status-badge.neutral { background: var(--grey-bg);  color: var(--muted); }
.status-badge.ok      { background: var(--ok-bg);    color: var(--ok); }
.status-badge.warning { background: var(--warn-bg);  color: var(--warn); }
.status-badge.stale   { background: var(--stale-bg); color: var(--stale); }
.status-badge.error   { background: var(--bad-bg);   color: var(--bad); }

/* ── Плашки ──────────────────────────────────────────────────────────── */

.alert {
  border-left: 3px solid var(--warn);
  background: var(--warn-bg);
  padding: 10px 14px;
  border-radius: 0 var(--radius) var(--radius) 0;
  margin-bottom: 10px;
  font-size: 13.5px;
}
.alert.bad  { border-color: var(--bad); background: var(--bad-bg); }
.alert.good { border-color: var(--ok);  background: var(--ok-bg); }

/* ── Плитки метрик ───────────────────────────────────────────────────── */

.stats { display: flex; gap: 26px; flex-wrap: wrap; margin: 10px 0 0; }
.stat b { display: block; font-size: 24px; line-height: 1.2; font-variant-numeric: tabular-nums; }
.stat span { font-size: 12px; color: var(--muted); }

/* ── Списки ──────────────────────────────────────────────────────────── */

ul.plain { margin: 0; padding-left: 20px; font-size: 13.5px; }
ul.plain li { margin-bottom: 5px; }

ul.rows { padding-left: 0; list-style: none; margin: 0; }
ul.rows li {
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: 8px;
  margin-bottom: 7px;
}
ul.rows.errors li { border-color: var(--bad); }

/* Строка выбора: галочка, ключ, описание. */
.opt {
  display: flex;
  gap: 9px;
  align-items: flex-start;
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: 8px;
  margin-bottom: 7px;
}
.opt input { width: auto; margin-top: 3px; }
.opt .k { font-weight: 700; min-width: 30px; }
.opt .d { font-size: 13px; }

/* ── Код ─────────────────────────────────────────────────────────────── */

code { background: var(--grey-bg); padding: 2px 6px; border-radius: 5px; font-size: 13px; }
pre {
  background: var(--grey-bg);
  padding: 12px;
  border-radius: 8px;
  overflow-x: auto;
  font-size: 12.5px;
  white-space: pre-wrap;
}

/* ── Экран ожидания ──────────────────────────────────────────────────── */

/* Нужен ровно там, где ответ приходит минутами: чтение картинки моделью
   занимает от 40 до 200 секунд, и без него страница просто стоит — человек
   не понимает, нажалась ли кнопка. Это та динамика, которая экономит время,
   в отличие от переходов и hover-эффектов. */
.busy {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: rgba(29, 36, 48, .55);
  display: flex;
  align-items: center;
  justify-content: center;
}
.busy[hidden] { display: none; }
.busy-card {
  background: var(--card);
  border-radius: var(--radius-lg);
  padding: 26px 32px;
  max-width: 420px;
  text-align: center;
  box-shadow: var(--shadow);
}
.busy-card p { margin: 6px 0 0; }
.busy-what { font-size: 16px; }
.busy-clock { font-variant-numeric: tabular-nums; font-size: 22px; }

.spinner {
  width: 34px; height: 34px;
  margin: 0 auto 14px;
  border: 3px solid var(--grey-bg);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 900ms linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .spinner { animation-duration: 2.4s; } }

/* Полоса прогресса для операций с известной долей выполнения. */
.progress {
  height: 6px;
  background: var(--grey-bg);
  border-radius: 3px;
  overflow: hidden;
}
.progress > i {
  display: block;
  height: 100%;
  background: var(--accent);
  transition: width .3s ease;
}
@media (prefers-reduced-motion: reduce) { .progress > i { transition: none; } }

/* ── Сводка готовности заявки ────────────────────────────────────────── */
/* Из CDW: панель над списком позиций заявки — сколько готово, что мешает
   выпуску, что можно только подтвердить и продолжить. Три состояния, не
   четыре, как в CDW-шном API (`not_ready`/`ready_with_warnings`/`ready`/
   `blocked`) — здесь это уже смысл, а не код статуса: ok/warning/error,
   тот же вокабуляр, что у .status-badge. Приложение само решает, в какой
   из трёх классов лечь его собственный статус. */
.readiness-panel {
  margin-top: 20px;
  padding: 12px 14px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--card);
  font-size: 14px;
}
.readiness-panel.ok      { background: var(--ok-bg); }
.readiness-panel.warning { background: var(--warn-bg); }
.readiness-panel.error   { background: var(--bad-bg); }

.readiness-label {
  margin-top: 4px;
  font-weight: 600;
}

/* Список того, что мешает готовности — либо блокирует выпуск (error),
   либо требует подтверждения, но не блокирует (warning). Не два разных
   списка, один: порядок вперемешку теряет смысл реже, чем два отдельных
   блока теряют то, что они вообще про одно и то же — готовность позиции. */
.readiness-problem-list {
  display: grid;
  gap: 8px;
  margin-top: 12px;
  list-style: none;
  padding: 0;
}

.readiness-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 12px;
  border-radius: var(--radius);
}
.readiness-item.error   { background: var(--bad-bg); }
.readiness-item.warning { background: var(--warn-bg); }

.readiness-item-actions {
  display: flex;
  flex-wrap: wrap;
  flex-shrink: 0;
  gap: 6px;
}

/* Строка ниже проблем — что уже посчитано, при готовых позициях. */
.readiness-summary-line {
  margin-top: 12px;
  color: var(--muted);
  font-size: 13px;
}

/* ── Закреплённая панель действия ────────────────────────────────────── */
/* Из CDW: главное действие формы («Подобрать», «Сохранить») не должно
   уезжать за пределы экрана на длинной форме — приклеена к низу, плывёт
   вместе со статусом операции. В CDW это делалось комбинацией с локальным
   классом `.buttons`, которого в пакете нет — здесь `display:flex` уже
   часть самого `.action-bar`, чтобы компонент был самодостаточным. */
.action-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  position: fixed;
  z-index: 20;
  inset-inline: 20px;
  bottom: 0;
  max-width: var(--wrap);
  margin: 0 auto;
  padding: 12px;
  padding-bottom: calc(12px + env(safe-area-inset-bottom));
  background: var(--card);
  border: 1px solid var(--line);
  border-bottom: 0;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  box-shadow: var(--shadow);
}

.action-bar .status-message {
  flex: 1 1 240px;
  min-width: 0;
  max-height: 64px;
  color: var(--muted);
  text-align: center;
  overflow-y: auto;
  overflow-wrap: anywhere;
}

@media (max-width: 820px) {
  .action-bar {
    inset-inline: 12px;
    gap: 8px;
    padding: 10px;
    padding-bottom: calc(10px + env(safe-area-inset-bottom));
  }

  .action-bar .status-message {
    flex-basis: 100%;
  }
}

/* ── Мастер-деталь рабочий экран ─────────────────────────────────────── */
/* Из CDW (двухпанельный workspace завесы, air_curtains_request_workspace.
   css): слева контекст заявки и список позиций, справа выбранная позиция.
   Структура, не готовые поля — какие секции в сайдбаре и что показывать в
   главной панели, решает приложение. */
.workspace {
  display: grid;
  grid-template-columns: minmax(320px, 360px) minmax(0, 1fr);
  gap: 20px;
  max-width: var(--wrap);
  margin: 0 auto;
}
@media (max-width: 800px) {
  .workspace { grid-template-columns: 1fr; }
}

.workspace-panel {
  min-width: 0;
  padding: var(--space-lg);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--card);
  box-shadow: var(--shadow);
}

.workspace-sidebar {
  display: grid;
  align-content: start;
  gap: 22px;
}

.workspace-section + .workspace-section {
  padding-top: 20px;
  border-top: 1px solid var(--line);
}

.workspace-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.workspace-main {
  min-height: 480px;
}

/* Заглушка главной панели: загрузка, пусто, ошибка — один и тот же приём,
   разный текст и (для ошибки) цвет. */
.workspace-state {
  display: grid;
  min-height: 400px;
  place-content: center;
  padding: 32px;
  color: var(--muted);
  text-align: center;
}
.workspace-state.error { color: var(--bad); }

/* ── Список позиций ──────────────────────────────────────────────────── */
.item-list {
  display: grid;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.item-row {
  padding: 11px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--bg);
}
.item-row.active {
  border-color: var(--accent);
  box-shadow: inset 3px 0 0 var(--accent);
  background: var(--card);
}
.item-select-btn {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  text-align: left;
  font: inherit;
  cursor: pointer;
}
.item-actions {
  display: flex;
  gap: 4px;
  margin-top: 8px;
}

/* ── Диалог несохранённых изменений ──────────────────────────────────── */
/* Родной <dialog>, не самодельный модал поверх .busy: закрытие по Esc и
   клик по backdrop уже работают без единой строчки JS. */
.unsaved-dialog {
  max-width: min(520px, calc(100vw - 32px));
  padding: var(--space-lg);
  border: 0;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
}
.unsaved-dialog::backdrop {
  background: rgba(15, 23, 42, .55);
}
.unsaved-dialog h2 {
  margin-bottom: 18px;
  font-size: 1.1rem;
}
.unsaved-dialog-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
