/* Компоненты админки fbswb. Цвета, отступы и тайминги — только через
 * токены theme.css: собственный оттенок здесь означал бы, что правка
 * темы перестаёт менять этот экран.
 *
 * Экран рассчитан на ПК: оператор смотрит плотную таблицу заданий на
 * широком мониторе. Отсюда высокая плотность и залипающие заголовки —
 * ценность экрана измеряется тем, сколько заданий видно без прокрутки и
 * помнит ли человек, какая колонка перед ним, на трёхсотой строке.
 */

/* ============================================================ базовое */

/* Клавиатурный фокус виден ВСЕГДА и одинаково. Отдельным правилом и в
   самом начале — чтобы никакой компонент ниже не мог его отменить
   незаметно. :focus-visible, а не :focus: кольцо нужно тому, кто пришёл
   с Tab, а не тому, кто просто кликнул мышью по селекту. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* Залипающая шапка + залипающий заголовок таблицы вместе образуют полосу
   высотой --top-h + строка thead. Без этого отступа Tab уводил бы фокус
   ПОД них: элемент получал фокус, браузер прокручивал к нему, и элемент
   оказывался накрыт (WCAG 2.2, 2.4.11 «фокус не перекрыт»). */
a,
button,
input,
select,
summary,
[tabindex] {
  scroll-margin-top: calc(var(--top-h) + 44px);
}

/* Скрыто от глаз, но НЕ от скринридера. Не display:none и не hidden —
   те убирают элемент из дерева доступности целиком, то есть отменяют
   ровно то, ради чего подпись и написана. */
.vh {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Ссылка для клавиатуры: первым Tab на странице — «к таблице», мимо
   переключателя контуров и фильтров. Видимой становится только при
   фокусе, мышью её не встретить. */
.skip {
  position: absolute;
  left: var(--sp-2);
  top: calc(-1 * var(--top-h) - var(--sp-4));
  z-index: calc(var(--z-header) + 1);
  padding: var(--sp-2) var(--sp-3);
  background: var(--bg-elevated);
  color: var(--text);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  text-decoration: none;
  transition: top var(--transition);
}
.skip:focus {
  top: var(--sp-2);
}

/* =============================================================== шапка */

/* Раскладка экрана: шапка, заголовок, фильтры, счётчик и пагинатор
   стоят на месте, прокручивается ТОЛЬКО таблица. Это и есть плотный
   рабочий экран: на трёхсотой строке фильтры и «сколько всего» должны
   быть перед глазами, а не в четырёх экранах выше.
   Побочно это чинит и залипающий заголовок таблицы — почему, написано
   у .table-wrap. */
body {
  display: flex;
  flex-direction: column;
  /* height, а НЕ min-height. С min-height контейнер имеет право вырасти
     выше экрана — тогда сжимать flex-детям нечего, таблица разворачивается
     во всю длину, и пагинатор уходит под нижний край: замер показывал
     body 1197px при экране 963px и .pager с top=1153. Фиксированная
     высота делает свободное место конечным, и цепочка flex начинает
     работать так, как задумано. */
  height: 100dvh;
  overflow: hidden;
}

.top {
  z-index: var(--z-header);
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  height: var(--top-h);
  padding: 0 var(--sp-4);
  background: var(--bg-elevated);
  border-bottom: 1px solid var(--border);
}

.brand {
  font-weight: 650;
  letter-spacing: 0.01em;
}

/* Боевой кабинет виден боковым зрением, а не вычитывается из адресной
   строки. Три носителя разом, потому что цена ошибки — лишняя этикетка
   на живом складе: цветная полоса по всей ширине, красный бренд и слово
   «PROD» текстом. Одного цвета мало — правило «цвет не единственный
   носитель смысла» здесь не формальность, а страховка. */
.top--prod {
  border-bottom: 2px solid var(--err);
  box-shadow: inset 0 3px 0 0 var(--err);
}
.top--prod .brand {
  color: var(--err);
}
.brand-tag {
  margin-left: var(--sp-2);
  padding: 2px var(--sp-2);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--err);
  border: 1px solid var(--err);
  border-radius: var(--radius-sm);
}

/* Переключатель контуров — сегментированный блок, а не россыпь ссылок:
   так видно, что это ОДИН выбор из двух, а активный сегмент читается
   заливкой, а не только цветом текста. */
.contour {
  display: flex;
  gap: 2px;
  padding: 2px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.contour a {
  min-height: 26px;
  display: flex;
  align-items: center;
  padding: 0 var(--sp-3);
  color: var(--text-muted);
  font-size: 13px;
  text-decoration: none;
  border-radius: var(--radius-sm);
  transition:
    background var(--transition),
    color var(--transition);
}
.contour a:hover {
  background: var(--bg-hover);
  color: var(--text);
}
.contour a.is-active {
  background: var(--bg-hover);
  color: var(--text);
  font-weight: 600;
  box-shadow: inset 0 0 0 1px var(--border-strong);
}
/* Боевой сегмент подкрашен и в неактивном состоянии: человек должен
   видеть, КУДА он собирается переключиться, до клика. */
.contour a[data-contour="prod"] {
  color: var(--err);
}

.spacer {
  flex: 1;
}
.user {
  color: var(--text-muted);
  font-size: 13px;
}
.logout {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  min-height: 26px;
  padding: 0 var(--sp-2);
  color: var(--text-muted);
  font-size: 13px;
  text-decoration: none;
  border-radius: var(--radius-sm);
  transition:
    background var(--transition),
    color var(--transition);
}
.logout:hover {
  background: var(--bg-hover);
  color: var(--text);
}

/* ============================================================== страница */

/* min-height:0 обязателен на каждом звене цепочки: flex-элемент по
   умолчанию не сжимается меньше своего содержимого, и без этого
   .table-wrap не получил бы конечной высоты — прокручиваться было бы
   нечему, а таблица просто вытолкнула бы пагинатор за экран. */
main {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: var(--sp-4) var(--sp-4) var(--sp-4);
  /* Предохранительный клапан. В обычной жизни не срабатывает: дети
     сжимаются сами. Включается, только если неподвижная часть экрана
     (шапка, фильтры в две строки, счётчик, пагинатор) уже не влезает в
     низкое окно — тогда лучше прокрутка, чем срезанный и недостижимый
     пагинатор. */
  overflow: auto;
}

/* Неподвижная часть экрана не сжимается: в flex-колонке высота — это
   главная ось, и без flex:none низкое окно начало бы плющить панель
   фильтров и пагинатор вместо того, чтобы отнять высоту у таблицы.
   Отдавать место обязана именно таблица — она единственная прокручиваемая. */
.page-head,
.filters,
.total,
.pager {
  flex: none;
}

.page-head {
  display: flex;
  align-items: baseline;
  gap: var(--sp-3);
  margin: 0 0 var(--sp-3);
}
.page-head h1 {
  margin: 0;
  font-size: 18px;
  font-weight: 650;
  letter-spacing: -0.01em;
}
/* Счётчик — часть заголовка страницы, но живёт в блоке результата: он
   меняется вместе со строками. Оформлен приглушённо, чтобы не спорить с
   заголовком за внимание. */
.total {
  margin: 0;
  color: var(--text-muted);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}

/* =============================================================== фильтры */

.filters {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--sp-3);
  margin: 0 0 var(--sp-3);
  padding: var(--sp-3);
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.filters label {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  color: var(--text-muted);
  font-size: 12px;
}
.filters .field-search {
  flex: 1 1 260px;
  max-width: 380px;
}
.filters .actions {
  display: flex;
  gap: var(--sp-2);
  margin-left: auto;
}

/* Поле с иконкой: иконка декоративная (aria-hidden в разметке) —
   смысл несёт видимая подпись «Поиск», а не лупа. */
.input-icon {
  position: relative;
  display: flex;
}
.input-icon .ico {
  position: absolute;
  left: var(--sp-2);
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-muted);
  pointer-events: none;
}
.input-icon input {
  width: 100%;
  padding-left: 30px;
}

select,
input,
button {
  min-height: 30px;
  background: var(--bg);
  color: var(--text);
  /* --border-strong, а не --border: это границы УПРАВЛЯЮЩИХ элементов,
     для них WCAG 1.4.11 требует 3:1 к фону. Замерено: 3.4 к холсту,
     3.0+ к любой нашей поверхности. */
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  padding: var(--sp-1) var(--sp-2);
  font: inherit;
  font-size: 13px;
  transition:
    border-color var(--transition),
    background var(--transition);
}
select:hover,
input:hover {
  border-color: var(--text-muted);
}
input::placeholder {
  color: var(--text-muted);
  opacity: 1;
}

button {
  cursor: pointer;
  padding: var(--sp-1) var(--sp-3);
  font-weight: 550;
}
button:hover {
  background: var(--bg-hover);
}
/* Одна главная кнопка на экран; «Сбросить» рядом — заведомо
   второстепенная, поэтому остаётся обычной. */
button.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--bg);
}
button.primary:hover {
  filter: brightness(1.08);
}
button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}
.btn-reset {
  display: inline-flex;
  align-items: center;
  min-height: 30px;
  padding: 0 var(--sp-3);
  color: var(--text-muted);
  font-size: 13px;
  text-decoration: none;
  border: 1px solid transparent;
  border-radius: var(--radius);
  transition:
    color var(--transition),
    border-color var(--transition);
}
.btn-reset:hover {
  color: var(--text);
  border-color: var(--border-strong);
}

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

/* Собственная область прокрутки таблицы — по обеим осям.
 *
 * Горизонтальная нужна затем, что десять колонок не обязаны влезать в
 * узкое окно, но и ломать страницу прокруткой всего документа не должны.
 *
 * Вертикальная — не украшение, а условие работы залипающего заголовка.
 * `overflow-x: auto` сам по себе делает элемент контейнером прокрутки по
 * ОБЕИМ осям (CSS: `visible` рядом с `auto` вычисляется в `auto`), и
 * `position: sticky` внутри начинает липнуть к ЭТОМУ контейнеру, а не к
 * окну. Пока у контейнера не было своей высоты, прокручиваться было
 * нечему — и `top: 48px` просто сдвигал строку заголовков на 48px вниз,
 * поверх первой строки данных. Ровно это и было видно на снимке.
 * Дав контейнеру конечную высоту и обнулив `top`, получаем ту самую
 * залипающую шапку, ради которой всё и затевалось. */
.table-wrap {
  flex: 1;
  min-height: 120px;
  overflow: auto;
  /* Докрутив таблицу до края, не утаскиваем за собой страницу целиком —
     иначе на трекпаде выдача «проскакивает» в прокрутку документа. */
  overscroll-behavior: contain;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-surface);
}

table.tasks {
  border-collapse: separate;
  border-spacing: 0;
  width: 100%;
  font-size: 13px;
}

table.tasks th,
table.tasks td {
  text-align: left;
  padding: var(--sp-2) var(--sp-3);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}

/* Заголовок липнет под шапкой страницы. На трёхсотой строке человек уже
   не помнит, седьмая колонка — это баркод или склад; прокрутка вверх
   ради этого и есть та мелочь, из которой складывается «неудобно». */
table.tasks thead th {
  position: sticky;
  /* 0, а не высота шапки: липнем к верху СВОЕГО контейнера прокрутки
     (.table-wrap), который и так начинается под шапкой страницы. */
  top: 0;
  z-index: var(--z-sticky);
  background: var(--bg-elevated);
  color: var(--text-muted);
  font-weight: 600;
  font-size: 12px;
  padding: 0;
  border-bottom: 1px solid var(--border-strong);
}

/* Сортировка — ссылка во всю ячейку заголовка: цель клика равна видимой
   шапке колонки, а не подписи в ней (WCAG 2.2, 2.5.8 — минимум 24x24). */
table.tasks thead a {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  min-height: 32px;
  padding: var(--sp-1) var(--sp-3);
  color: inherit;
  text-decoration: none;
  transition:
    background var(--transition),
    color var(--transition);
}
table.tasks thead a:hover {
  background: var(--bg-hover);
  color: var(--text);
}
/* Стрелка направления видна всегда у активной колонки и проявляется по
   наведению у остальных — так видно, что колонка вообще сортируемая. */
table.tasks thead .ico-sort {
  opacity: 0;
  color: var(--text-muted);
  transition: opacity var(--transition);
}
table.tasks thead a:hover .ico-sort {
  opacity: 0.7;
}
table.tasks th[aria-sort] a {
  color: var(--text);
}
table.tasks th[aria-sort] .ico-sort {
  opacity: 1;
  color: var(--accent);
}
/* Активная колонка помечена не только стрелкой, но и полосой сверху:
   направление сортировки не должно держаться на одном мелком глифе. */
table.tasks th[aria-sort] {
  box-shadow: inset 0 2px 0 0 var(--accent);
}

/* Зебра приглушённая: её задача — вести глаз вдоль строки на десяти
   колонках, а не расчерчивать экран. */
table.tasks tbody tr:nth-child(even) td {
  background: color-mix(in oklab, var(--bg-hover) 55%, transparent);
}
table.tasks tbody tr:hover td {
  background: var(--bg-hover);
}
/* Наведение подтверждается ещё и полосой у левого края: на плотной
   таблице разница фонов в 1.4 заметна не всем и не на всяком мониторе. */
table.tasks tbody tr td:first-child {
  box-shadow: inset 2px 0 0 0 transparent;
  transition: box-shadow var(--transition);
}
table.tasks tbody tr:hover td:first-child {
  box-shadow: inset 2px 0 0 0 var(--accent);
}
table.tasks tbody tr:last-child td {
  border-bottom: none;
}

.mono {
  font-family: var(--font-mono);
  font-size: 12px;
}
/* Цифры фиксированной ширины — иначе колонка ID и колонка цены «дышат»
   при каждой смене страницы, и глазу не за что зацепиться. */
.num,
.mono {
  font-variant-numeric: tabular-nums;
}
/* `table.tasks td.num`, а не просто `.num`: базовое правило выше —
   `table.tasks th, table.tasks td` — весит больше одиночного класса, и
   короткая запись молча проигрывала ему выравнивание. Цена оставалась
   прижатой влево, а колонка чисел, выровненная влево, не читается
   столбиком вовсе. */
table.tasks td.num,
table.tasks th.num {
  text-align: right;
}
/* Прочерк вместо пустоты, но приглушённый: «данных нет» не должно
   выглядеть так же весомо, как данные. */
.nil {
  color: var(--text-muted);
}

/* Артикул и имя склада — единственные колонки со свободным текстом.
   Ограничиваем ширину и обрезаем с многоточием, полное значение отдаём
   в title; nowrap на всей таблице иначе растянул бы её без предела. */
.text-cell {
  max-width: 22ch;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ============================================================== статусы */

/* Точка + текст, а не бейдж с заливкой: русские названия статусов WB
   длинные («Новое сборочное задание»), и заливка под ними превращает
   строку в цветную плашку. Точка ускоряет поиск глазами, текст несёт
   смысл — цвет здесь помощник, а не носитель информации. */
.status {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}
.status::before {
  content: "";
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 0 2px color-mix(in oklab, currentColor 25%, transparent);
}
.status--ok {
  color: var(--ok);
}
.status--warn {
  color: var(--warn);
}
.status--err {
  color: var(--err);
}
.status--info {
  color: var(--info);
}
.status--idle {
  color: var(--text-muted);
}
/* Сам текст статуса остаётся основным цветом: цветной остаётся только
   точка, иначе десять цветных строк подряд снова превращаются в шум. */
.status span {
  color: var(--text);
}
.status--idle span {
  color: var(--text-muted);
}

/* ======================================================= пустое и ошибки */

/* Та же ловушка специфичности, что и у .num: без `table.tasks tbody`
   базовое правило ячейки перебивало и центрирование, и перенос строк —
   сообщение прижималось влево и не переносилось. */
table.tasks tbody tr.empty td {
  padding: var(--sp-8) var(--sp-3);
  text-align: center;
  white-space: normal;
}
.empty-box {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
  color: var(--text-muted);
}
.empty-box .ico {
  color: var(--border-strong);
}
.empty-box strong {
  color: var(--text);
  font-weight: 600;
}
.empty-box .hint {
  font-size: 13px;
  max-width: 46ch;
}
/* Единственная «текстовая» ссылка в интерфейсе. Без своего правила она
   остаётся браузерно-синей — на тёмном фоне это и чужеродно, и плохо
   читается. */
.empty-box a {
  color: var(--accent);
  text-decoration-color: color-mix(in oklab, var(--accent) 45%, transparent);
  text-underline-offset: 2px;
}
.empty-box a:hover {
  text-decoration-color: currentColor;
}

/* =============================================================== пагинация */

.pager {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-top: var(--sp-3);
  color: var(--text-muted);
  font-size: 13px;
}
.pager .range {
  font-variant-numeric: tabular-nums;
}
.pager a {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  min-height: 28px;
  padding: 0 var(--sp-3);
  color: var(--text);
  text-decoration: none;
  background: var(--bg-surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  transition:
    background var(--transition),
    border-color var(--transition);
}
.pager a:hover {
  background: var(--bg-hover);
  border-color: var(--text-muted);
}
.pager .spacer {
  flex: 1;
}

/* ========================================================== загрузка HTMX */

/* Ответ на действие обязан быть виден раньше, чем приедут данные. Полоса
   сверху блока результата + приглушение старой таблицы: человек видит,
   что цифры под курсором уже неактуальны и ответ в пути. */
#results {
  position: relative;
  /* Звено той же цепочки: блок результата тянется на всю оставшуюся
     высоту и отдаёт её .table-wrap, а пагинатор остаётся под ним. */
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
#results::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -2px;
  height: 2px;
  border-radius: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  opacity: 0;
  transition: opacity var(--transition);
}
#results.htmx-request::before {
  opacity: 1;
  animation: load-bar 900ms ease-in-out infinite;
}
#results.htmx-request .table-wrap {
  opacity: 0.45;
  transition: opacity var(--transition);
}
@keyframes load-bar {
  0% {
    transform: scaleX(0);
    transform-origin: left;
  }
  50% {
    transform: scaleX(1);
    transform-origin: left;
  }
  51% {
    transform: scaleX(1);
    transform-origin: right;
  }
  100% {
    transform: scaleX(0);
    transform-origin: right;
  }
}

/* ================================================================== иконки */

.ico {
  flex: none;
  width: 16px;
  height: 16px;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.ico--lg {
  width: 28px;
  height: 28px;
  stroke-width: 1.5;
}

/* ================================================================== вход */

.login {
  max-width: 340px;
  margin: 14vh auto 0;
  padding: var(--sp-6);
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.login h1 {
  margin: 0 0 var(--sp-1);
  font-size: 20px;
  font-weight: 650;
}
.login .hint {
  margin: 0 0 var(--sp-4);
  color: var(--text-muted);
  font-size: 13px;
}
.login label {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  color: var(--text-muted);
  font-size: 12px;
}
.login input {
  min-height: 36px;
  font-size: 14px;
}
.login button {
  width: 100%;
  min-height: 36px;
  margin-top: var(--sp-4);
}
/* Ошибка входа: цвет плюс иконка плюс текст. Красным одним здесь
   обойтись нельзя — это ровно тот случай, ради которого правило
   «не только цветом» и написано. */
.login .error {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  margin: 0 0 var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  color: var(--err);
  font-size: 13px;
  background: color-mix(in oklab, var(--err) 12%, transparent);
  border: 1px solid var(--err);
  border-radius: var(--radius);
}

/* ============================================================ узкий экран */

/* Экран сделан под ПК, но открыть его с планшета в цеху могут. Задача
   этих правил — не «сделать мобильную версию», а не рассыпаться:
   фильтры в колонку, шапка переносится, таблица уезжает в свою
   прокрутку (она уже в .table-wrap). */
@media (max-width: 900px) {
  /* Узкий экран возвращается к обычной прокрутке страницы: запирать
     таблицу в область высотой в пол-экрана там, где и так мало места, —
     это оставить видимыми три строки. */
  body {
    display: block;
    height: auto;
    overflow: visible;
  }
  main {
    display: block;
    overflow: visible;
  }
  #results {
    display: block;
  }
  .table-wrap {
    max-height: none;
  }
  .top {
    height: auto;
    min-height: var(--top-h);
    flex-wrap: wrap;
    padding: var(--sp-2) var(--sp-3);
    gap: var(--sp-2);
  }
  .filters .actions {
    margin-left: 0;
  }
  .filters .field-search {
    max-width: none;
  }
  /* 16px на полях ввода — иначе iOS/Safari при фокусе сам увеличивает
     страницу, и вёрстка «прыгает» под руками. */
  input,
  select {
    font-size: 16px;
  }
}
