/* Оформление панели. Всё через переменные: цвет, отступ и радиус меняются в одном месте.
   Никаких внешних шрифтов и картинок — страница обязана работать на сервере без интернета. */

:root {
  --bg: #f4f5f7;
  --card: #ffffff;
  --fg: #191c20;
  --muted: #6b7280;
  --line: #e2e5ea;
  --line-strong: #c9ced6;
  /* Четыре тона состояния, у каждого три оттенка: заливка, рамка и текст. Значок,
     плашка и строка состояния берут их отсюда и только отсюда — иначе один и тот же
     смысл красился бы на трёх экранах тремя похожими цветами. */
  --accent: #1f5fd0;
  --accent-dark: #17499e;
  --accent-soft: #eaf1fd;
  --accent-line: #c3d8f8;
  --ok: #1a7f37;
  --ok-soft: #e7f5ec;
  --ok-line: #bfe3c9;
  --ok-text: #14522a;
  --warn: #a55a00;
  --warn-soft: #fdf1e2;
  --warn-line: #f0d5ac;
  --warn-text: #7a4200;
  --danger: #c62828;
  --danger-soft: #fdecec;
  --danger-line: #f3c6c6;
  --danger-text: #8f1d1d;

  /* Линии графика: стартовая цена и минимальная. Тоном они не спорят — минимальная
     цена не «тревога», а вторая половина той же истории. */
  --chart-start: var(--accent);
  --chart-min: var(--warn);

  --radius: 10px;
  --radius-sm: 6px;
  --gap: 16px;
  --shadow: 0 1px 2px rgba(16, 24, 40, .06), 0 1px 3px rgba(16, 24, 40, .04);

  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial,
    "Noto Sans", sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
}

* { box-sizing: border-box; }

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: 14px/1.45 var(--font);
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

h1, h2, h3 { margin: 0; font-weight: 650; }

.wrap { width: 100%; max-width: 1180px; margin: 0 auto; padding: 0 16px; }
.page { padding-top: 20px; padding-bottom: 48px; }
.page-title { font-size: 22px; margin-bottom: 14px; }
.subtitle { margin: -8px 0 14px; color: var(--muted); }
.crumbs { margin: 0 0 8px; font-size: 13px; }

/* --- шапка ---------------------------------------------------------------- */

.top {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--card);
  border-bottom: 1px solid var(--line);
}
.top-inner { display: flex; align-items: center; gap: 16px; height: 52px; }
.brand { font-weight: 700; color: var(--fg); white-space: nowrap; }
.brand:hover { text-decoration: none; color: var(--accent); }
.nav { display: flex; gap: 2px; flex: 1; overflow-x: auto; scrollbar-width: none; }
.nav::-webkit-scrollbar { display: none; }
.nav-item {
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  color: var(--muted);
  white-space: nowrap;
}
.nav-item:hover { background: var(--bg); color: var(--fg); text-decoration: none; }
.nav-item.is-active { background: var(--accent-soft); color: var(--accent-dark); font-weight: 600; }
.top-out { margin-left: auto; }

/* --- карточки и плитки ---------------------------------------------------- */

.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 16px;
  margin-bottom: var(--gap);
}
.card-title { font-size: 15px; margin-bottom: 12px; }
.card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}
.card-head .card-title { margin-bottom: 0; }
.card-more { margin: 12px 0 0; }
/* Правый край шапки карточки: подпись и кнопки скачивания стоят одним рядом, а на
   узком экране переносятся вместе, а не разлетаются по углам. */
.head-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--gap);
  margin-bottom: var(--gap);
}
/* Ряд плиток с деньгами шире обычного: сумма в тенге не переносится и не сокращается,
   ей нужно место целиком. Узкая плитка отрезала бы у цены знак ₸. */
.tiles-money { grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
.tile {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 14px 16px;
}
/* Селектор с запасом по весу: у числа в плитке всегда есть класс .num, а он тоже
   задаёт кегль — без этого главная цена набиралась бы размером строки таблицы. */
.tile .tile-value { font-size: 24px; font-weight: 650; line-height: 1.2; }
/* Плитка, в которой вместо числа стоят слова: «не указана». Кеглем цены такой текст
   занял бы две строки и перекосил бы весь ряд, а сказать словами всё равно нужно —
   прочерк на месте цены читается как «не загрузилось». Вес селектора выше, чем у
   правила на телефоне: там цифра мельче, а слова остаются теми же. */
.tile .tile-value.tile-word { font-size: 16px; font-weight: 600; }
.tile-label { color: var(--muted); font-size: 13px; margin-top: 2px; }
/* Плитка-ссылка: число ведёт туда, где его можно проверить списком. Цвет наследуется —
   синяя цифра в ряду чёрных читалась бы как другое состояние, а не как ссылка. */
.tile-link { display: block; color: inherit; transition: border-color .12s ease; }
.tile-link:hover { border-color: var(--accent); text-decoration: none; }
.tile-link:hover .tile-label { color: var(--fg); }

/* Карточка с якорем: с обзора на неё приходят по ссылке, а шапка панели прилипшая —
   без отступа заголовок раздела оказался бы ровно под ней. Отступ чуть больше высоты
   шапки (52 px), на телефоне она выше — там своё значение ниже по файлу. */
.card[id] { scroll-margin-top: 68px; }

/* --- факты и служебное ---------------------------------------------------- */

.facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 10px 20px;
  margin: 0;
}
.fact dt { color: var(--muted); font-size: 12px; }
.fact dd { margin: 2px 0 0; }
.fact-wide { grid-column: span 2; }

/* Блок внутри карточки. У наблюдения два отдельных набора настроек и ряд кнопок:
   без разделителя и подписи четыре поля подряд читаются как одна форма. */
.block { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line); }
.block-title { font-size: 13px; margin: 0 0 10px; }
/* Пустое состояние внутри блока не липнет к подсказке над ним: без отступа две строки
   читаются как один абзац, оборванный на середине. */
.block > .empty { margin-top: 10px; }
.badges { display: flex; gap: 6px; flex-wrap: wrap; }

.muted { color: var(--muted); }
.num { font-family: var(--mono); font-variant-numeric: tabular-nums; font-size: 13px; }
.right { text-align: right; }
.break { word-break: break-all; }
.empty { color: var(--muted); margin: 0; }
.hint { color: var(--muted); font-size: 12px; margin: 8px 0 0; }
.is-down { color: var(--danger); }
.is-off { color: var(--muted); }
.arrow { color: var(--muted); }
.ext { white-space: nowrap; }

/* Пара «было → стало» — один блок, а не три отдельных куска: на узкой карточке она
   переносится целиком, и стрелка не остаётся на строке в одиночестве. */
.price-pair {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: flex-end;
  gap: 0 6px;
}
.price-from { white-space: nowrap; }
.price-pair .was { color: var(--muted); text-decoration: line-through; }
.price-pair .now { font-weight: 600; }
/* Красным — только цена: красный здесь означает «подешевело». Сдвинувшийся срок приёма
   заявок или новый статус тревожным цветом красить нельзя, это была бы ложь глазами. */
.pair-price .now { color: var(--danger); white-space: nowrap; }
/* Пара обычного поля — это обычный текст, а не ряд из двух нерушимых кусков. Портал
   пишет статус в две сотни знаков («Не состоялся (на момент начала электронного аукциона
   в аукционном зале…)»), и во флексе такая пара ломалась дважды: «было» с запретом
   переноса уезжало за край карточки (на телефоне утаскивая за собой всю страницу), а
   «стало» сжималось в столбик по три буквы — «Не состоя лся». Текстом всё переносится
   по словам, а короткая пара («Приём заявок → Не состоялся») по-прежнему стоит одной
   строкой. Сумма не рвётся никогда: разряды в ней склеены неразрывным пробелом. */
.pair-plain {
  display: block;
  overflow-wrap: break-word;
}
.pair-plain .price-from { white-space: normal; }

/* --- разворот ------------------------------------------------------------- */

/* Длинное значение и хвост длинного списка прячутся под одним и тем же разворотом.
   `<details>` открывается и закрывается сам, без единой строчки JS, — а значит,
   работает и когда скрипт не загрузился. Значок при этом обязан меняться в обе
   стороны: наполовину сделанный разворот выглядит поломкой. */
.reveal { max-width: 100%; }
.reveal > summary {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  color: var(--accent);
  font-size: 13px;
  list-style: none;
}
/* Свой значок вместо системного треугольника: тот в разных браузерах разный. */
.reveal > summary::-webkit-details-marker { display: none; }
.reveal > summary::after { content: '▾'; color: var(--muted); font-size: 11px; }
.reveal[open] > summary::after { content: '▴'; }
.reveal > summary:hover { text-decoration: underline; }
.reveal > summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
.reveal-body { margin-top: 8px; }

/* Длинное значение целиком: два куска, «было» и «стало», один под другим. Высота
   ограничена — описание объекта занимает пол-экрана, и за ним потерялись бы
   остальные записи. Абзацы остаются абзацами: портал пишет описание в несколько
   строк, и склеить их в одну значит потерять смысл. */
.long-pair { display: grid; gap: 10px; }
.long-label { font-size: 12px; color: var(--muted); }
.long-text {
  margin-top: 3px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--bg);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-height: 260px;
  overflow-y: auto;
  /* Долистав описание до конца, палец не должен утащить за собой всю страницу. */
  overscroll-behavior: contain;
}
.long-was .long-text { color: var(--muted); }

/* --- сообщения ------------------------------------------------------------ */

.flashes { display: grid; gap: 8px; margin-bottom: var(--gap); }
.flash {
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  background: var(--card);
}
.flash-ok { background: var(--ok-soft); border-color: var(--ok-line); color: var(--ok-text); }
.flash-error {
  background: var(--danger-soft);
  border-color: var(--danger-line);
  color: var(--danger-text);
}
.flash-warn { background: var(--warn-soft); border-color: var(--warn-line); color: var(--warn-text); }

.state { margin: 0 0 12px; }
.state-ok { color: var(--ok-text); }
.state-warn { color: var(--warn-text); }
.state-error { color: var(--danger-text); font-weight: 600; }

/* --- значки --------------------------------------------------------------- */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 1px 8px;
  border-radius: 999px;
  font-size: 12px;
  border: 1px solid var(--line-strong);
  background: var(--bg);
  color: var(--muted);
  white-space: nowrap;
}
/* Значки состояния и поводов красятся одними и теми же тонами: смысл один — цвет один.
   Падение цены — то, ради чего всё затевалось, поэтому оно выделено как тревога;
   появление лота и конец приёма заявок зовут посмотреть; изменение цены — спокойный
   синий. Состояния чата (`app/chats.py`) названы состояниями, а не цветами, — тон им
   выбран здесь, рядом с остальными, и меняется тоже здесь. */
.badge-ok,
.badge-new_lot,
.badge-receiving { background: var(--ok-soft); border-color: var(--ok-line); color: var(--ok-text); }
.badge-partial,
.badge-deadline_soon,
.badge-waiting {
  background: var(--warn-soft);
  border-color: var(--warn-line);
  color: var(--warn-text);
}
.badge-error,
.badge-price_drop,
.badge-disabled {
  background: var(--danger-soft);
  border-color: var(--danger-line);
  color: var(--danger-text);
}
.badge-running,
.badge-price_changed {
  background: var(--accent-soft);
  border-color: var(--accent-line);
  color: var(--accent-dark);
}
.badge-off,
.badge-stopped { background: var(--bg); }
/* Отвязанный владельцем чат. Это состояние законченное, но не тревожное: строка и так
   серая, и значок обязан её поддерживать, а не спорить с ней. Тёмная заливка (как у
   «торги завершились») делала его самым заметным пятном таблицы — взгляд падал на
   выключенную строку раньше, чем на работающий чат. От «отписан командой /stop»
   отличается тем, что текст здесь обычной черноты: это не чат ушёл сам, это решение
   владельца. */
.badge-unlinked { background: var(--bg); border-color: var(--line-strong); color: var(--fg); }
/* Название повода длинное — «Скоро закроется приём заявок». В свой столбец журнала оно
   помещается одной строкой, но в узком окне столбец уже: пусть лучше значок переносится,
   чем вылезает на соседний столбец. */
.badge-reason { white-space: normal; }
/* Оставшиеся два повода говорят не о событии с ценой, а о том, чем дело кончилось:
   торги завершились и сводка за обход. Тонов в теме четыре, и все четыре заняты
   событиями, поэтому эти два взяты не тоном, а заливкой: тёмная — итог торгов,
   синяя — сводка про раздел целиком. Заливку не спутать ни между собой, ни с
   бледными значками состояния лота — «снят» и «выключен» остаются светло-серыми. */
.badge-finished {
  background: var(--fg);
  border-color: var(--fg);
  color: var(--card);
}
.badge-digest {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--card);
}

.drop {
  display: inline-block;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--danger-soft);
  color: var(--danger);
  font-size: 12px;
  font-weight: 600;
}

.spinner {
  display: inline-block;
  width: 11px;
  height: 11px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: spin .8s linear infinite;
  vertical-align: -1px;
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .spinner { animation-duration: 3s; } }
.running { display: flex; align-items: center; gap: 8px; color: var(--accent-dark); margin: 0 0 12px; }

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

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 7px 13px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line-strong);
  background: var(--card);
  color: var(--fg);
  font: inherit;
  font-size: 13px;
  line-height: 1.2;
  cursor: pointer;
  white-space: nowrap;
  transition: background .12s ease, border-color .12s ease, color .12s ease;
}
.btn:hover { background: var(--bg); text-decoration: none; }
.btn:active { transform: translateY(1px); }
.btn[disabled], .btn.is-loading { opacity: .55; cursor: not-allowed; pointer-events: none; }
.btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn-primary:hover { background: var(--accent-dark); border-color: var(--accent-dark); }
.btn-quiet { color: var(--muted); }
.btn-quiet:hover { color: var(--fg); }
.btn-danger { color: var(--danger); border-color: #eec4c4; }
.btn-danger:hover { background: var(--danger-soft); }
.btn-wide { width: 100%; }

.field { display: flex; flex-direction: column; gap: 4px; min-width: 160px; }
.field-label { font-size: 12px; color: var(--muted); }
.field-wide { flex: 1 1 260px; }
.field-narrow { flex: 0 0 170px; min-width: 140px; }
/* Пояснение под самим полем: у .hint отступ сверху на целый абзац, внутри поля он
   отрывал бы подпись от строки ввода. */
.field-hint { font-size: 12px; color: var(--muted); }
.input {
  padding: 7px 10px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--card);
  color: var(--fg);
  font: inherit;
  font-size: 13px;
  width: 100%;
}
.input:hover { border-color: var(--muted); }
.input[disabled] { background: var(--bg); color: var(--muted); cursor: not-allowed; }
.input-mini { width: 78px; padding: 4px 6px; }
select.input { appearance: auto; }

.form-row, .filters {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: flex-end;
}
/* Столбец шире 300 px, потому что полей в этой форме три: с прежними 240 px на
   компьютере помещалось четыре столбца, и четвёртый стоял пустым — ряд выглядел
   недоделанным. Чем шире столбец, тем меньше строк занимает подсказка под полем,
   а значит, тем меньше пустоты под соседями. */
.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 16px;
  align-items: start;
}
/* Поля ряда стоят по одной линии, как бы длинна ни была подпись. Каждое поле — это
   три строки общей сетки: подпись, поле ввода, подсказка. Поэтому подпись в две
   строки поднимает всю строку ряда, а не опускает своё поле на 20 px ниже соседних.
   Строк ровно три, потому что столько частей у поля этой формы; появится четвёртая —
   правило нужно поправить вместе с разметкой.
   Старый браузер без subgrid получает прежнюю раскладку: ступенька некрасива,
   пустая страница — хуже. */
@supports (grid-template-rows: subgrid) {
  .form-grid > .field {
    display: grid;
    grid-row: span 3;
    grid-template-rows: subgrid;
    row-gap: 4px;
  }
  /* Подпись прижата к своему полю, как и в остальных формах панели: короткая подпись
     не должна висеть отдельно от поля, к которому относится. */
  .form-grid > .field > .field-label { align-self: end; }
}
.form-actions { grid-column: 1 / -1; margin-top: 12px; }

/* --- галочки выбора ------------------------------------------------------- */

/* Виды торга и стадии выбираются галочками с русскими названиями: кодов портала
   владелец не вводит никогда. Стадий семь, видов пятьдесят шесть — поэтому стадии
   стоят открытым списком, а виды прячутся под разворот. */
.picks { display: grid; gap: 16px; }
.pick { display: grid; gap: 8px; }
.pick-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
.checks {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 6px 16px;
}
/* Поводы уведомлений шире: под подписью стоит пояснение в одну-две строки. */
.checks-wide { grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 10px 16px; }
.check {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: 13px;
  line-height: 1.35;
  cursor: pointer;
}
.check input { margin: 2px 0 0; flex: 0 0 auto; }
.check:hover { color: var(--accent-dark); }
.check-hint { display: block; color: var(--muted); font-size: 12px; margin-top: 2px; }
.check-group + .check-group { margin-top: 12px; }
/* Поле под списком галочек — не впритык: иначе «Напоминать за, часов» читается как
   часть последнего пояснения. */
.checks + .form-row { margin-top: 14px; }
.check-group-title {
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
  margin-bottom: 6px;
}

/* Выбранные виды торга списком — их видно, не открывая разворот. */
/* Скрытое остаётся скрытым: у .chips и подобных свой display, и без !important он
   пересилил бы [hidden]. */
[hidden] { display: none !important; }

.chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 0; }
.chip {
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid var(--line-strong);
  background: var(--bg);
  font-size: 12px;
}
.inline { display: inline-flex; align-items: center; gap: 6px; margin: 0; }
.actions { display: flex; gap: 6px; justify-content: flex-end; flex-wrap: wrap; }

/* Значение, которое копируют: поле во всю ширину и кнопка рядом. Поле именно поле, а
   не текст, — выделить длинную ссылку мышью в ячейке таблицы почти невозможно, а так
   она выделяется одним щелчком даже там, где браузер запретил копирование кнопкой. */
.copy { display: flex; align-items: center; gap: 6px; }
.copy .copy-value { flex: 1 1 auto; min-width: 0; font-family: var(--mono); font-size: 12px; }
.copy .btn { flex: 0 0 auto; white-space: nowrap; }
/* Кнопка на миг подтверждает, что скопировала: без ответа владелец жмёт её второй раз,
   не понимая, сработало ли. */
.copy .btn.is-copied {
  border-color: var(--ok-line);
  background: var(--ok-soft);
  color: var(--ok-text);
}

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

.table-wrap { overflow-x: auto; margin: 0 -4px; padding: 0 4px; }
.table { width: 100%; border-collapse: collapse; }
.table th, .table td {
  padding: 8px 10px;
  border-bottom: 1px solid var(--line);
  text-align: left;
  vertical-align: top;
}
.table th {
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
  border-bottom-color: var(--line-strong);
}
/* Деньги — по правому краю, вместе с заголовком колонки: у общего .right не хватает
   веса против «.table td», и суммы разной длины выстраиваются как попало. */
.table th.right, .table td.right { text-align: right; }
/* Кнопки строки стоят в один ряд: перенос на вторую строку удваивает высоту строки.
   Чтобы ряд из трёх кнопок уместился по ширине карточки, в таблице они компактнее
   обычных — это второстепенные действия, а не главная кнопка страницы. */
.table .actions { flex-wrap: nowrap; }
.table .actions .btn { padding: 5px 9px; font-size: 12px; }
/* В строке таблицы места хватает — там пара цен стоит в одну строку. */
.table .price-pair { flex-wrap: nowrap; }
.table tbody tr:hover { background: #fafbfc; }
.table tbody tr:last-child td { border-bottom: none; }

/* Журнал изменений рисуется двумя таблицами — видимой и той, что под разворотом.
   Ширины столбцов заданы явно, иначе каждая половина считала бы их по своему
   содержимому, и одна половина списка встала бы уступом относительно другой. */
.table-changes { table-layout: fixed; }
.table-changes .col-when { width: 165px; }
.table-changes .col-field { width: 180px; }
.table-changes .col-lot { width: 95px; }

/* Проверки разделов на обзоре: доли, а не содержимое. Значки поводов в последнем
   столбце просят всю ширину, какую им дадут, и без долей они отбирают место у времени
   следующей проверки — ровно у того, ради чего в эту таблицу и смотрят. */
.table-checks { table-layout: fixed; }
.table-checks .col-section { width: 20%; }
.table-checks .col-when { width: 17%; }
.table-checks .col-outcome { width: 18%; }
.table-checks .col-next { width: 20%; }
.table-checks .col-reasons { width: 25%; }

/* Откуда взято «было» у записи о переиздании. Стоит под самой парой значений, потому
   что относится к ней, а не к строке целиком. */
.change-from { font-size: 12px; margin-top: 2px; }

/* Как объект выставляли заново: пара номеров и четыре поля рядом. Столбец номеров
   узкий и не тянется — иначе он забирает место у цен, ради которых таблица и стоит. */
.table-republished .col-pair { width: 165px; }

/* Журнал уведомлений: ширины столбцов заданы долями, а не содержимым. Считая по
   содержимому, браузер отдавал парам цен и кнопке отправки всё, что они просили
   (231 и 239 px), а «Раздел» оставался с 124 px — «Повышение цены и передача»
   ломалось на три строки и вытягивало всю строку таблицы вдвое. Доли держат столбцы
   соразмерными на любой ширине окна, а не только на той, где их однажды посчитали. */
.table-alerts { table-layout: fixed; }
.table-alerts .col-when { width: 10%; }
.table-alerts .col-reason { width: 20%; }
.table-alerts .col-section { width: 15%; }
.table-alerts .col-object { width: 15%; }
.table-alerts .col-detail { width: 15%; }
/* Доставке дано больше остальных: в состоянии «отправка прекращена» тут лежит причина
   отказа целым абзацем, и в узком столбце она растягивает строку на пол-экрана. */
.table-alerts .col-delivery { width: 18%; }
.table-alerts .col-lot { width: 7%; }
/* Пара цен здесь переносится, как на телефоне: в свой столбец «41 961 671 ₸ →
   30 212 403 ₸» одной строкой не влезает, а вылезать на соседний столбец ей нельзя. */
.table-alerts .price-pair { flex-wrap: wrap; }

/* Приглашения: ссылка забирает всё, что осталось от трёх коротких столбцов. Считая по
   содержимому, браузер отдал бы ей ровно длину адреса и ужал бы подпись «для кого». */
.table-invites { table-layout: fixed; }
/* Ссылка лежит в поле ввода, и оно выше строки текста: при общем выравнивании по
   верху подпись, срок и кнопка «Отозвать» вставали заметно выше середины поля —
   строка выглядела съехавшей. Ставим ячейки на одну среднюю линию — так же ровно,
   как в соседней таблице чатов. */
.table-invites td { vertical-align: middle; }
.table-invites .col-for { width: 22%; }
.table-invites .col-until { width: 165px; }
/* Столбец действий узкий: в нём одна кнопка. Остальные столбцы таблицы чатов браузер
   считает по содержимому — имена и подписи бывают длинными. */
.table-invites .col-act,
.table-chats .col-act { width: 120px; }

/* Ячейка с кнопкой на компьютере — обычная ячейка таблицы, а не гибкий блок: гибкий
   не тянется до высоты строки, и разделительная линия под ним обрывалась, не доходя
   до края таблицы. Правило общее для всех таблиц: столбец действий выглядит одинаково
   и у приглашений, и у чатов. На телефоне ячейка снова гибкая — там из неё сделана
   строка карточки (правило в блоке `@media (max-width: 760px)`). */
@media (min-width: 761px) {
  .table td.actions { display: table-cell; text-align: right; }
}

.sort { color: var(--muted); display: inline-flex; gap: 4px; }
.sort:hover { color: var(--fg); text-decoration: none; }
.sort.is-active { color: var(--fg); }
.sort-sign { color: var(--line-strong); }
.sort.is-active .sort-sign { color: var(--accent); }

.pager { display: flex; align-items: center; gap: 12px; margin-top: 12px; }

/* --- лента падений -------------------------------------------------------- */

.feed { list-style: none; margin: 0; padding: 0; }
.feed-item { padding: 10px 0; border-bottom: 1px solid var(--line); }
.feed-item:last-child { border-bottom: none; }
.feed-main { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.feed-title { font-weight: 600; }
.feed-prices { margin-top: 2px; }
.feed-meta { display: flex; gap: 12px; flex-wrap: wrap; font-size: 12px; margin-top: 2px; }

/* --- график --------------------------------------------------------------- */

/* Версий графика две: широкая и узкая. SVG ужимается вместе с подписями, поэтому
   график с компьютера на телефоне читался бы разве что через лупу. Полосы прокрутки
   у карточки нет — что не помещается, того на экране как бы и нет. */
.chart-wrap { margin-bottom: 12px; }
.chart { display: block; width: 100%; height: 220px; }
.chart-narrow { display: none; }
.chart-narrow .chart { height: 200px; }
.chart-line { fill: none; stroke: var(--chart-start); stroke-width: 2; stroke-linejoin: round; }
.chart-area { fill: var(--accent-soft); opacity: .7; }
.chart-dot { fill: var(--card); stroke: var(--chart-start); stroke-width: 2; }
/* Вторая линия — минимальная цена. Цветом и штрихом сразу: одного цвета мало тому,
   кто не различает синий и оранжевый, а подпись рядом объясняет обе. */
.chart-line-min { stroke: var(--chart-min); stroke-dasharray: 6 4; }
.chart-dot-min { stroke: var(--chart-min); }
.chart-grid { stroke: var(--line); stroke-dasharray: 3 3; }
.chart-label { fill: var(--muted); font: 12px var(--mono); }

/* Подпись линий. Стоит под графиком и одна на обе его версии — широкую и узкую:
   на телефоне легенда внутри SVG ужалась бы вместе с ним до нечитаемого. */
.chart-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  margin: 0 0 12px;
  color: var(--muted);
  font-size: 12px;
}
.chart-key { display: inline-flex; align-items: center; gap: 6px; }
.chart-key::before {
  content: '';
  width: 16px;
  height: 0;
  border-top: 2px solid currentColor;
}
.chart-key-start { color: var(--chart-start); }
/* Штриховая — та же линия, что и на графике: подпись обязана выглядеть как то,
   что она подписывает. */
.chart-key-min { color: var(--chart-min); }
.chart-key-min::before { border-top-style: dashed; }

/* --- вход ----------------------------------------------------------------- */

.login { max-width: 380px; margin: 8vh auto 0; }
.login-title { font-size: 22px; text-align: center; }
.login-note { color: var(--muted); text-align: center; margin: 6px 0 18px; }
.login-card { display: grid; gap: 14px; }

/* --- телефон -------------------------------------------------------------- */

@media (max-width: 760px) {
  .page { padding-top: 14px; }
  /* Шапка на телефоне в две строки — около 121 px, и прежний отступ прятал бы под ней
     заголовок раздела, к которому владелец только что пришёл по ссылке с обзора. */
  .card[id] { scroll-margin-top: 132px; }
  .page-title { font-size: 19px; }
  .card { padding: 13px; border-radius: 8px; }
  .brand { font-size: 14px; }

  /* Меню уезжает на вторую строку: на 390 px пять разделов рядом с кнопкой «Выйти»
     не помещаются, а спрятанный раздел — это раздел, которого для владельца нет.
     Боковые отступы те же 16 px, что у остального содержимого: шапка, прижатая
     к краю экрана, выглядит обрезанной. */
  .top-inner { height: auto; gap: 8px 10px; padding: 8px 16px; flex-wrap: wrap; }
  /* Разделы переносятся на вторую строку целиком: прокручиваемое меню на телефоне
     прячет последний пункт, а спрятанный раздел — это раздел, которого нет. */
  .nav { order: 3; flex: 1 0 100%; gap: 4px; flex-wrap: wrap; overflow: visible; }
  .nav-item { padding: 5px 8px; }
  /* Плитка занимает строку целиком. В два столбца три плитки обзора ложатся «2 + 1»,
     и третья висит одна с пустотой справа — ряд выглядит оборванным. Ровный столбик
     читается сверху вниз одинаково при любом числе плиток. */
  .tiles { gap: 10px; grid-template-columns: 1fr; }
  /* Плитка с деньгами не уже 190 px, а значит на 390 px занимает строку целиком:
     в две колонки цена не помещается и теряет знак ₸. Обрезанная цена хуже,
     чем цена во всю ширину. */
  .tiles-money { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
  .tile .tile-value { font-size: 20px; }
  .field, .field-narrow, .field-wide { flex: 1 1 100%; min-width: 0; }
  /* Галочки на телефоне — в один столбец: два узких столбца рвут длинные названия
     видов торга на обрывки. */
  .checks, .checks-wide { grid-template-columns: 1fr; }
  .form-row .btn, .filters .btn { flex: 1 1 auto; }
  /* Кнопки скачивания занимают ширину карточки: две подписи разной длины, прижатые
     к разным краям, читаются как случайно рассыпанные. */
  .head-actions { width: 100%; }
  .head-actions .btn { flex: 1 1 auto; }

  /* Таблица не влезает в 390 px — каждая строка становится карточкой. */
  .table-cards thead { display: none; }
  .table-cards, .table-cards tbody, .table-cards tr, .table-cards td { display: block; width: 100%; }
  .table-cards tr {
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 4px 10px;
    margin-bottom: 10px;
    background: var(--card);
  }
  .table-cards tr:hover { background: var(--card); }
  .table-cards td {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: baseline;
    gap: 4px 14px;
    padding: 5px 0;
    border-bottom: 1px dashed var(--line);
    text-align: left;
  }
  /* Последняя карточка ничем не отличается от остальных: правило «у последней строки
     таблицы нет нижней границы» здесь стирало бы разделители внутри неё. */
  .table-cards tbody tr:last-child td { border-bottom: 1px dashed var(--line); }
  .table-cards tbody tr td:last-child { border-bottom: none; }
  .table-cards td::before {
    content: attr(data-label);
    color: var(--muted);
    font-size: 12px;
    /* Подпись всегда слева, даже в колонке с числами, выровненными по правому краю. */
    text-align: left;
    flex: 0 0 42%;
  }
  .table-cards td.right { text-align: right; }
  /* Кнопки строки встают своей строкой под подписью «Действия»: рядом с подписью
     три кнопки в 390 px не помещаются и ломаются «одна сверху, две снизу». Своей
     строкой они делят ширину карточки поровну и стоят ровным рядом. */
  .table-cards td.actions { flex-wrap: wrap; justify-content: flex-end; }
  .table-cards td.actions::before { flex: 0 0 100%; }
  .table-cards td.actions .inline { flex: 1 1 0; }
  .table-cards td.actions .btn { width: 100%; }
  /* Кнопки наблюдения делят ширину карточки поровну: три подряд в 390 px не влезают,
     и без этого «Удалить» повисало бы одиноким огрызком у правого края. */
  .block.actions .inline { flex: 1 1 40%; }
  .block.actions .btn { width: 100%; }
  .table-cards .price-pair { flex-wrap: wrap; }
  /* Состояние чата стоит у правого края карточки — как и остальные значения. Подпись
     под значком равняется по нему же, иначе она висит сама по себе слева. */
  .table-cards .state-cell { text-align: right; }
  /* Ссылка-приглашение встаёт своей строкой под подписью: в остатке от подписи (58 %
     ширины карточки) от неё видно три знака, и кнопка «Копировать» туда не влезает. */
  .table-cards .copy { flex: 1 1 100%; }
  .table-wrap { overflow-x: visible; }
  .chart-wide { display: none; }
  .chart-narrow { display: block; }
  .facts { grid-template-columns: 1fr 1fr; }
  .fact-wide { grid-column: 1 / -1; }
}
