@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');

:root {
  --profit: #16a34a;
  --loss: #dc2626;
  --wb-color: #cb11ab;
  --ozon-color: #005bff;
  --sidebar-bg: #14192b;
  --sidebar-bg-2: #1b2340;
  --sidebar-fg: #9aa4c0;
  --sidebar-active: rgba(255,255,255,0.08);
  --accent: #2f7dfb;
  --accent-2: #16a34a;
  --bg: #eef1f6;
  --card-border: #e7eaf1;
  --text-muted: #8b93a7;
  --text-heading: #1a2033;
  --radius: 16px;
  --radius-sm: 10px;
  --shadow: 0 1px 2px rgba(20,25,45,0.04), 0 8px 24px -12px rgba(20,25,45,0.10);
}

* { box-sizing: border-box; }

html, body {
  height: 100%;
  /* Горизонтальный скролл СТРАНИЦЫ (вместе с сайдбаром) не нужен нигде — то, что реально должно
     скроллиться вбок, это широкие таблицы («Прайсы» и т.п.), у них свой overflow-x:auto на
     .table-responsive. Без этой строки страница иногда всё равно ловила горизонтальный скролл
     (и вместе с ним «уезжал» сайдбар) из-за невидимых, но всё ещё display:block элементов —
     например закрытое выпадающее меню «⚡ Действия» специально держится в display:block ради
     плавной CSS-анимации (см. .dropdown-menu ниже) и может вылезать за правый край, даже будучи
     невидимым (opacity:0). Жалоба пользователя — «горизонтальный скролл сдвигает таблицу и
     сайдбар». */
  overflow-x: hidden;
}

body {
  background: var(--bg);
  font-family: 'Inter', -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  color: #2a3149;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, h5, h6 {
  color: var(--text-heading);
  font-weight: 700;
  letter-spacing: -0.01em;
}

/* ===== Shell layout ===== */

.app-shell {
  display: flex;
  min-height: 100vh;
}

.sidebar {
  width: 246px;
  flex-shrink: 0;
  background: linear-gradient(180deg, var(--sidebar-bg) 0%, var(--sidebar-bg-2) 100%);
  color: var(--sidebar-fg);
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
  z-index: 10;
  transition: width .15s ease;
}

.sidebar-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 24px 22px;
  font-weight: 800;
  font-size: 16px;
  color: #fff;
  letter-spacing: -0.01em;
}

.sidebar-brand i {
  font-size: 20px;
  color: var(--accent);
}

/* Бренд-марка — закруглённый бейдж с градиентом OZON→WB (--ozon-color/--wb-color) и линией
   роста внутри (см. web/header.php) — заменяет системную bi-graph-up-arrow на собственный
   логотип; смысл сохранён (аналитика/рост продаж), но узнаваем и в свёрнутом сайдбаре
   (только иконка, см. body.sidebar-collapsed ниже), не только рядом с текстом. */
.sidebar-brand .brand-mark {
  width: 26px;
  height: 26px;
  flex-shrink: 0;
}

.sidebar-brand span {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sidebar-toggle {
  background: none;
  border: none;
  color: var(--sidebar-fg);
  cursor: pointer;
  padding: 4px 6px;
  border-radius: 6px;
  line-height: 1;
  flex-shrink: 0;
}

.sidebar-toggle:hover {
  background: rgba(255,255,255,0.08);
  color: #fff;
}

.sidebar-toggle i {
  font-size: 14px;
  transition: transform .15s ease;
  display: block;
}

/* ===== Свёрнутый сайдбар (только иконки) — переключатель в .sidebar-brand, состояние
   персистится в localStorage, класс на body ставится ДО рендера сайдбара инлайн-скриптом
   в header.php, чтобы избежать мигания развёрнутого меню при загрузке. ===== */
body.sidebar-collapsed .sidebar {
  width: 72px;
}

body.sidebar-collapsed .sidebar-brand {
  padding: 24px 12px;
  gap: 0;
}

body.sidebar-collapsed .sidebar-brand span {
  display: none;
}

body.sidebar-collapsed .sidebar-toggle i {
  transform: rotate(180deg);
}

body.sidebar-collapsed .sidebar-account-form,
body.sidebar-collapsed .sidebar .nav-link span {
  display: none;
}

body.sidebar-collapsed .sidebar .nav-link {
  justify-content: center;
  padding: 10px 8px;
}

body.sidebar-collapsed .sidebar .nav-link i {
  width: auto;
}

.sidebar-nav {
  flex: 1;
  padding: 6px 12px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.sidebar-footer {
  border-top: 1px solid rgba(255,255,255,0.06);
  padding: 12px;
  margin-top: auto;
}
.sidebar-version {
  font-size: 10.5px;
  color: rgba(154, 164, 192, 0.55);
  padding: 4px 14px 0;
}
body.sidebar-collapsed .sidebar-version { display: none; }

.sidebar .nav-link {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  color: var(--sidebar-fg);
  text-decoration: none;
  font-size: 14px;
  font-weight: 500;
  border-radius: var(--radius-sm);
  transition: background .15s ease, color .15s ease;
}

.sidebar .nav-link:hover {
  background: rgba(255,255,255,0.06);
  color: #fff;
}

.sidebar .nav-link.active {
  background: var(--sidebar-active);
  color: #fff;
  box-shadow: inset 3px 0 0 var(--accent);
}

.sidebar .nav-link i {
  font-size: 17px;
  width: 20px;
  text-align: center;
  flex-shrink: 0;
}

/* Выход — теперь кнопка внутри POST-формы (был обычный GET-переход, уязвимый к принудительному
   разлогиниванию через <img src="…/logout.php"> с чужой страницы) — сброс браузерных стилей
   кнопки до вида .nav-link. */
.sidebar-logout-btn {
  width: 100%;
  background: none;
  border: 0;
  cursor: pointer;
  font: inherit;
  text-align: left;
}

/* Значок-кнопка ⚙️ в таблицах (users.php/accounts.php) — без окантовки, только hover-подсветка. */
.btn-icon-plain {
  border: 0;
  background: none;
  padding: 2px 6px;
}
.btn-icon-plain:hover {
  background: rgba(0, 0, 0, 0.06);
  border-radius: 6px;
}

.app-main {
  flex: 1;
  min-width: 0;
}

/* Ширину страницы больше ничем не ограничиваем: таблицы здесь широкие (OZON+WB — два десятка
   колонок), и общий лимит только съедал место, не убирая горизонтальный скролл. Раньше лимит
   снимался точечно по списку страниц — новые разделы про него забывали. */
.app-main-inner {
  padding: 24px 24px 48px;
  max-width: none;
  margin: 0;
}

/* ===== Page heading (the <h3> each page starts with) ===== */

.app-main-inner > h3:first-child {
  font-size: 22px;
  font-weight: 800;
  margin-bottom: 24px;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--card-border);
  display: flex;
  align-items: center;
  gap: 10px;
}

.app-main-inner > h3:first-child i {
  color: var(--accent);
  font-size: 20px;
}

/* ===== Cards ===== */

.card-section {
  background: #fff;
  border-radius: var(--radius);
  padding: 20px 22px;
  box-shadow: var(--shadow);
  border: 1px solid var(--card-border);
  margin-bottom: 18px;
}

/* Таблица внутри карточки идёт от края до края: её собственные поля дублировали внешние
   отступы страницы и зря отъедали ширину у столбцов (прямая просьба «уйти от лишних рамок»).
   Только прямой потомок карточки — таблицы внутри модалок и вложенных блоков не трогаем. */
/* На «Прайсах» между карточкой и обёрткой таблицы стоит <form> — учитываем и такой случай. */
.card-section > .table-responsive,
.card-section > form > .table-responsive {
  margin-left: -22px;
  margin-right: -22px;
  width: calc(100% + 44px);
}
.card-section > .table-responsive > table,
.card-section > form > .table-responsive > table { margin-bottom: 0; }
/* Крайние ячейки возвращают себе отступ, иначе текст прилипал бы к рамке карточки. */
.card-section > .table-responsive > table > thead > tr > *:first-child,
.card-section > .table-responsive > table > tbody > tr > *:first-child,
.card-section > form > .table-responsive > table > thead > tr > *:first-child,
.card-section > form > .table-responsive > table > tbody > tr > *:first-child { padding-left: 22px; }
.card-section > .table-responsive > table > thead > tr > *:last-child,
.card-section > .table-responsive > table > tbody > tr > *:last-child,
.card-section > form > .table-responsive > table > thead > tr > *:last-child,
.card-section > form > .table-responsive > table > tbody > tr > *:last-child { padding-right: 22px; }

.card-section h5 {
  font-size: 15px;
  font-weight: 700;
  margin-bottom: 14px;
}

.stat-card {
  background: #fff;
  border-radius: var(--radius);
  padding: 20px 22px;
  box-shadow: var(--shadow);
  border: 1px solid var(--card-border);
  height: 100%;
}

.stat-card .stat-label {
  font-size: 12px;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: .04em;
  font-weight: 600;
  margin-bottom: 8px;
}

.stat-card .stat-value {
  font-size: 25px;
  font-weight: 800;
  color: var(--text-heading);
  letter-spacing: -0.01em;
}

.stat-card .stat-value.positive { color: var(--profit); }
.stat-card .stat-value.negative { color: var(--loss); }

/* Разбивка «Налог + НДС» на составляющие (см. web/orders.php, web/dashboard.php) — под крупной
   суммой, тем же небольшим отступом, что и у пояснения «(%)» у маржи. */
.stat-card .stat-sub { margin-top: 4px; }

/* ===== Badges ===== */

.badge-wb, .badge-ozon {
  display: inline-block;
  padding: .35em .6em;
  border-radius: 6px;
  font-weight: 700;
  font-size: 11px;
  letter-spacing: .02em;
  color: #fff !important;
}

/* Тонировка заголовков таблиц (th) — не badge-пилюли, чтобы не ломать table-cell.
   !important нужен, т.к. .table thead th (ниже) специфичнее простого th.th-ozon
   и иначе перебивает фон обратно в серый. */
th.th-wb, th.th-ozon {
  color: #fff !important;
}
th.th-wb { background: var(--wb-color) !important; }
th.th-ozon { background: var(--ozon-color) !important; }
/* 1С — фирменный жёлтый акцент интерфейса 1С:Предприятие 8.5 (панели/подсветка), не такой
   насыщенный, как у OZON/WB — это справочные данные, не карточка МП. */
th.th-onec { background: #F5E28A !important; color: #4a3c00 !important; }
.badge-wb { background: var(--wb-color); }
.badge-ozon { background: var(--ozon-color); }

/* ===== Tables ===== */

.table {
  margin-bottom: 0;
  font-size: 11.5px;
  width: auto !important;
}

.table thead th {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: .03em;
  color: #4a5170;
  font-weight: 800;
  border-bottom: 2px solid #e2e6f0;
  padding: 6px 10px;
  background: #f4f6fb;
  white-space: nowrap;
}

.table td {
  padding: 3px 10px;
  vertical-align: middle;
  border-bottom: 1px solid #f1f3f8;
  line-height: 1.3;
  white-space: nowrap;
}

.table-sm td, .table-sm th {
  padding: 2px 10px;
}

/* Bootstrap 5.3 красит фон не самого <tr>, а КАЖДОЙ его <td> отдельно через переменную
   --bs-table-bg (см. правило .table > :not(caption) > * > *) — из-за этого чередование
   на одном только <tr> нигде не было видно, кроме .sticky-col (у неё уже был отдельный
   явный фон по ячейке, поэтому только первый столбец и подсвечивался). Красим тоже по
   каждой <td>, чтобы полоса шла на всю строку. */
.table tbody tr:nth-child(even) { background: #eef1f8; }
.table tbody tr:nth-child(even) > td { background: #eef1f8; }
.table-hover tbody tr:hover { background: #e2e9fb; }
.table-hover tbody tr:hover > td { background: #e2e9fb; }

/* Прайсы: жёлтым — товара нет на одном из включённых МП, красным — нет ни на одном */
.table tbody tr.row-missing-some { background: #fff6d8 !important; }
.table tbody tr.row-missing-some > td { background: #fff6d8 !important; }
.table tbody tr.row-missing-all { background: #fde2e0 !important; }
.table tbody tr.row-missing-all > td { background: #fde2e0 !important; }
.table-hover tbody tr.row-missing-some:hover { background: #ffedad !important; }
.table-hover tbody tr.row-missing-some:hover > td { background: #ffedad !important; }
.table-hover tbody tr.row-missing-all:hover { background: #fbc9c5 !important; }
.table-hover tbody tr.row-missing-all:hover > td { background: #fbc9c5 !important; }

/* Строка, которую только что редактировали (модалка «Все свойства»/«Фото») — кратко подсвечена
   после закрытия модалки, чтобы визуально не потерять её в длинной таблице. */
.table tbody tr.row-recently-edited { background: #cfe8ff !important; transition: background-color 1.2s ease; }
.table tbody tr.row-recently-edited > td { background: #cfe8ff !important; transition: background-color 1.2s ease; }
.table-hover tbody tr.row-recently-edited:hover { background: #b8dcff !important; }
.table-hover tbody tr.row-recently-edited:hover > td { background: #b8dcff !important; }

.table-money td, .table-money th { text-align: right; }
.table-money td:first-child, .table-money th:first-child { text-align: left; }

/* Название/наименование товара может быть длинным — ограничиваем 2 строками с многоточием */
.table td.col-wrap, .table th.col-wrap {
  white-space: normal;
  max-width: 420px;
  min-width: 260px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-overflow: ellipsis;
}
.table th.col-wrap { -webkit-line-clamp: unset; display: table-cell; }

/* Короткие идентификаторы (артикул, номер заказа) — не даём им растягиваться шире содержимого */
.table td.col-narrow, .table th.col-narrow {
  max-width: 90px;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* OZON SKU/WB SKU (Номенклатура) — на 2-3 знака шире обычного col-narrow, номера SKU длиннее. */
.table td.col-sku, .table th.col-sku { max-width: 112px; }

/* Артикул (Номенклатура) — шире обычного col-narrow, чтобы влезало 15-18 символов */
.table td.col-article, .table th.col-article {
  max-width: 170px;
  min-width: 150px;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Числовые колонки (цена/маржа/остаток и т.п.) — фиксированная одинаковая ширина под ~8 знаков,
   чтобы не растягивались на всю доступную ширину на широких экранах (см. .col-wrap/.col-article —
   у них наоборот нет width, они и заберут освободившееся место). 100px, не 80px: box-sizing:
   border-box (Bootstrap) — из 100px ширины колонки 20px уходит на padding ячейки (table-sm:
   2px 10px), остаётся 80px контента — ровно под инпут «Реком.» (тоже жёстко 80px), чтобы он
   не переполнял свою колонку. */
.table th.col-num {
  width: 100px;
}

/* Таблицы, которым явно нужна полная ширина карточки (например, сводный отчёт) */
.table.table-full { width: 100% !important; }

/* Редактируемые кликом ячейки «Прайсов» (закупка ¥/₽, «Реком.») — обычный текст, не всегда
   видимое поле ввода (иначе таблица на ~20 колонок становится неприлично широкой). Клик
   подменяет .cell-display на .cell-input (JS в web/prices.php), сама ячейка остаётся узкой
   .col-num. */
.editable-price .cell-display {
  cursor: pointer;
  display: inline-block;
  min-width: 24px;
  border-bottom: 1px dashed transparent;
}
.editable-price .cell-display:hover {
  border-bottom-color: #adb5bd;
}
.editable-price .cell-input {
  width: 100%;
}

/* «Т.цена» (OZON/WB) — клик открывает карточку товара на самой площадке в новой вкладке
   (ссылка есть, только если каталог с МП хоть раз загружали — см. product_id/wbNmIds в
   PriceRowRenderer.php). Выглядит как обычное число (не «ссылка», чтобы не ломать плотную
   таблицу синим текстом), подчёркивание только при наведении — единственная подсказка кликабельности. */
.current-price-link {
  color: inherit;
  text-decoration: none;
}
.current-price-link:hover {
  text-decoration: underline;
}

/* Тулбар «Прайсов» — единый тёмно-серый контур у нейтральных кнопок («Получить цены»,
   «Пересчитать РЦ», «⚡ Действия», раньше вразнобой primary/warning/secondary — по прямому
   запросу пользователя «рамку контур одного цвета темно серый»), и фирменные цвета площадок
   («На OZON»/«На WB» — те же --ozon-color/--wb-color, что уже используются в шапках таблиц/
   вкладках карточки товара, не выдуманы заново). */
.btn-outline-neutral {
  --bs-btn-color: #495057;
  --bs-btn-border-color: #495057;
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: #495057;
  --bs-btn-hover-border-color: #495057;
  --bs-btn-active-color: #fff;
  --bs-btn-active-bg: #495057;
  --bs-btn-active-border-color: #495057;
}
.btn-outline-ozon {
  --bs-btn-color: var(--ozon-color);
  --bs-btn-border-color: var(--ozon-color);
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: var(--ozon-color);
  --bs-btn-hover-border-color: var(--ozon-color);
  --bs-btn-active-color: #fff;
  --bs-btn-active-bg: var(--ozon-color);
  --bs-btn-active-border-color: var(--ozon-color);
}
.btn-outline-wb {
  --bs-btn-color: var(--wb-color);
  --bs-btn-border-color: var(--wb-color);
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: var(--wb-color);
  --bs-btn-hover-border-color: var(--wb-color);
  --bs-btn-active-color: #fff;
  --bs-btn-active-bg: var(--wb-color);
  --bs-btn-active-border-color: var(--wb-color);
}

/* Аккуратные навигационные значки «Выбрать все по отбору»/«Снять выбор» (web/prices.php) —
   вместо длинного текста ссылок, по прямому запросу пользователя. */
.prices-icon-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 7px;
  color: var(--text-muted);
  cursor: pointer;
}
.prices-icon-link:hover {
  background: var(--bg);
  color: var(--accent);
}
.prices-icon-link.text-danger:hover {
  color: var(--loss);
}
/* Индикация «идёт загрузка» на значке «Выбрать все по отбору» — крутит саму SVG-иконку, а не
   подменяет содержимое элемента текстом (тот подход стирал иконку насовсем, см. web/prices.php). */
.prices-icon-link-loading {
  pointer-events: none;
  opacity: 0.55;
}
.prices-icon-link-loading svg {
  animation: prices-icon-spin 0.8s linear infinite;
}
@keyframes prices-icon-spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

/* Цена без акции — мелким текстом под ценой по акции (см. .text-warning на current-price),
   чтобы видеть, во сколько реально обходится акция/бустинг. */
.current-price-note {
  font-size: 10px;
  font-weight: 400;
  color: #8a94a6;
  white-space: nowrap;
}

/* «Реком.» (OZON/WB) — колонка с целевой ценой, которая реально уйдёт на площадку по кнопке
   «На OZON»/«На WB» — не заливка фона (было — не понравилось), а жирный синий текст, чтобы
   выделялась среди остальных расчётных колонок. */
.editable-price[data-col="oz_rec"] .cell-display,
.editable-price[data-col="wb_rec"] .cell-display,
td[data-col="onec_1c_rec"] {
  font-weight: 700;
  color: #0d6efd;
}

/* Табличка-разбор формулы («откуда взялась эта цена») — тултип при наведении на «Реком.»,
   см. PriceRowRenderer.php::price_calc_tooltip_html() + cell-tooltip.js (data-tooltip-html). */
/* Шире обычного .cell-tooltip (360px) — живая жалоба пользователя: с более длинными подписями
   формулы («Наценка... без доп. расхода)», «Доп. расход... добавлен уже в конце)») строка не
   помещалась, текст обрывался за краем тёмного блока. Подписи (.calc-tip-table td:first-child)
   теперь переносятся по словам вместо nowrap — само по себе уже чинит переполнение при любой
   ширине, но и ширины дали с запасом, чтобы переносов было по минимуму.
   Найденный баг: одинарный класс `.cell-tooltip-calc` имеет ту же специфичность, что и базовый
   `.cell-tooltip` (max-width:360px, объявлен НИЖЕ по файлу) — при равной специфичности побеждает
   правило, объявленное последним в файле, поэтому реальный max-width всегда был 360px, а не
   задуманные здесь 460px, несмотря на то что HTML-элемент несёт оба класса. Селектор
   `.cell-tooltip.cell-tooltip-calc` (два класса) специфичнее одного `.cell-tooltip` — теперь
   гарантированно побеждает независимо от порядка правил в файле. */
.cell-tooltip.cell-tooltip-calc { padding: 10px 12px; max-width: 460px; }

/* Значок ✏️ «цена изменена вручную» (price_manual_badge() в PriceRowRenderer.php + оптимистичный
   аналог в web/prices.php) кликабелен — сбрасывает эту одну цену на расчётную. По прямой правке
   пользователя: cursor:help рисовал системный «?» вместо указателя, что не читалось как «можно
   нажать» — заменено на pointer, а сам значок при наведении подменяется на красный ✕ (тот же
   визуальный язык, что «убрать»/«снять» в других местах интерфейса), чтобы действие было понятно
   без объяснений в тултипе. Разметка — два вложенных span (иконка/крестик), не CSS content —
   эмодзи-глиф игнорирует color, менять символ можно только подменой видимого элемента. */
.manual-badge {
  cursor: pointer;
  /* Кликабельная зона больше самого глифа: значок маленький, и попасть по нему мышью было
     трудно — живая жалоба «нажимаю сбросить цену, ничего не происходит». Отрицательные поля
     не дают увеличенной зоне раздвинуть содержимое ячейки. */
  display: inline-block;
  padding: 3px 5px;
  margin: -3px -3px -3px -5px;
  border-radius: 4px;
  line-height: 1;
}
.manual-badge:hover { background: rgba(198, 40, 40, .1); }
.manual-badge .manual-badge-hover {
  display: none;
  color: #c62828;
  font-weight: 700;
}
.manual-badge:hover .manual-badge-icon {
  display: none;
}
.manual-badge:hover .manual-badge-hover {
  display: inline;
}
.calc-tip-table { border-collapse: collapse; font-size: 12px; width: 100%; }
.calc-tip-table td { padding: 2px 10px 2px 0; }
.calc-tip-table td:first-child { white-space: normal; }
.calc-tip-table td:last-child { padding-right: 0; text-align: right; font-weight: 600; white-space: nowrap; }
/* Значения-исключения (не короткое число, а свободный текст — напр. название акции OZON у строки
   «🏷 Акция») — найденный живой баг: обычная nowrap-колонка справа требовала под длинное описание
   акции (напр. «Эластичный бустинг. Без ограничения срока действия») ширину, которую тесная
   таблица тултипа дать не может — auto-layout таблицы в ответ сжимал ЛЕВУЮ колонку (подписи)
   почти до нуля, а с унаследованным от .cell-tooltip word-break:break-word это разваливало текст
   подписей на отдельные буквы в узкий вертикальный столбец на всю высоту тултипа. Этот класс
   отключает nowrap/выравнивание-по-правому-краю специально для такой ячейки — текст переносится
   по словам как обычный абзац, не расширяя минимальную ширину колонки. */
.calc-tip-table td.calc-tip-wrap { text-align: left; font-weight: 400; white-space: normal; }
.calc-tip-table tr.calc-tip-total td { border-top: 1px solid #dee2e6; padding-top: 5px; margin-top: 3px; color: #0d6efd; }
.calc-tip-empty { font-size: 12px; max-width: 260px; white-space: normal; }
.calc-tip-note { font-size: 10.5px; color: #8a94a6; white-space: normal; max-width: 100%; margin-top: 4px; padding-top: 4px; border-top: 1px dashed #dee2e6; }

/* Заблокированные настройки 1С (Склад/Вид цены/Фильтр) — визуально неактивны, пока не нажата
   «✏️ Редактировать» (web/settings.php). Намеренно НЕ через атрибут disabled — disabled-поля не
   попадают в POST вообще, из-за чего сохранение НЕСВЯЗАННОЙ вкладки настроек молча обнулило бы
   эти значения. pointer-events:none просто не даёт кликнуть/выбрать мышью, реальное значение
   при этом всегда уходит на сервер как есть. */
.onec-locked-field {
  pointer-events: none;
  opacity: 0.6;
}

/* Заблокированные «Контрагенты для АУСН» (web/settings.php, вкладка «Налоги») — тот же визуальный
   приём, что у .onec-locked-field выше, но отдельным классом/кнопкой «Редактировать»: это
   независимая пара полей, снятие блокировки на вкладке «1С» не должно её тоже разблокировать. */
.tax-agent-locked-field {
  pointer-events: none;
  opacity: 0.6;
}

/* «Налоговый режим»/«НДС» (web/settings.php, вкладка «Налоги») — заблокированы ВСЕГДА по
   умолчанию (не только «уже настроено», в отличие от .onec-locked-field/.tax-agent-locked-field
   выше — здесь нет осмысленного «ещё не настроено» состояния), одна общая кнопка «Редактировать»
   на обе карточки сразу. */
.tax-regime-locked-field {
  pointer-events: none;
  opacity: 0.6;
}

/* «🧾 Внести» на «Налоговом календаре» (web/summary.php) — обычная bootstrap btn-sm выглядела
   слишком крупной/громоздкой рядом с компактными бейджами соседней колонки «Статус» (живая
   жалоба пользователя, скриншот). Тот же размер шрифта/паддинги, что у .badge. */
.tax-mini-btn {
  font-size: 11px;
  font-weight: 600;
  padding: 3px 9px;
  border-radius: 999px;
  line-height: 1.4;
}

/* Карточки-переключатели способа обмена с 1С (получение/передача) — web/settings.php, вкладка
   «1С». Клик в любом месте карточки выбирает радио (label оборачивает input) — подсвечивается
   рамкой и лёгкой заливкой через :has(), без отдельного JS для визуального состояния. */
.onec-mode-card {
  display: block;
  cursor: pointer;
  border: 2px solid var(--bs-border-color, #dee2e6);
  border-radius: 10px;
  padding: 14px 16px;
  height: 100%;
  transition: border-color .15s ease, background-color .15s ease;
}
.onec-mode-card:hover {
  border-color: #adb5bd;
}
.onec-mode-card:has(input:checked) {
  border-color: #0d6efd;
  background: rgba(13, 110, 253, .06);
}
.onec-mode-card input[type="radio"] {
  float: right;
  margin-top: 2px;
}
.onec-mode-card-icon {
  font-size: 1.4rem;
  line-height: 1;
}
.onec-mode-card-title {
  font-weight: 600;
  margin: 4px 0 2px;
}
.onec-mode-card-desc {
  font-size: .82rem;
  color: var(--bs-secondary-color, #6c757d);
}

/* Расширенные колонки расхода (позаказная таблица) — свёрнуты по умолчанию */
.table td.col-ext, .table th.col-ext { display: none; }
.table.show-ext td.col-ext, .table.show-ext th.col-ext { display: table-cell; }

/* Клик — скопировать текст ячейки в буфер обмена */
.copyable { cursor: pointer; }
.copyable:hover { background: #eaf2ff !important; }
.copy-flash { color: #16a34a; font-weight: 700; margin-left: 4px; }

/* Тултип с полным текстом обрезанной ячейки (см. assets/cell-tooltip.js) */
.cell-tooltip {
  position: fixed;
  z-index: 2000;
  background: #1a2033;
  color: #fff;
  padding: 7px 11px;
  border-radius: 8px;
  font-size: 12.5px;
  line-height: 1.4;
  max-width: 360px;
  box-shadow: 0 6px 20px rgba(0,0,0,0.25);
  pointer-events: none;
  white-space: normal;
  word-break: break-word;
}

/* Тип операции */
.type-sale { color: var(--profit); font-weight: 700; }
.type-return { color: var(--loss); font-weight: 700; }
.type-cancel { color: var(--accent); font-weight: 700; }

/* Перетаскивание столбцов */
.table th[draggable="true"] { cursor: grab; user-select: none; }
.table th.col-drag-over { background: #dbe4ff !important; }
.table th.col-dragging { opacity: .4; }

/* Сортируемые заголовки (Прайсы: клик по «Артикул»/«Название») */
.table th.sortable-col:hover { background: #eef1f8; }
.sort-indicator { font-size: 9px; color: var(--accent); }

/* Номенклатура: привязка к SKU маркетплейса подсвечена его цветом */
#nomenTable td.bg-ozon-tint { background: rgba(0, 91, 255, 0.08) !important; }
#nomenTable td.bg-wb-tint { background: rgba(203, 17, 171, 0.08) !important; }
#nomenTable td.bg-ozon-tint.copyable:hover { background: rgba(0, 91, 255, 0.18) !important; }
#nomenTable td.bg-wb-tint.copyable:hover { background: rgba(203, 17, 171, 0.18) !important; }
/* Остаток по 1С — тот же приём, желтоватый акцент 1С:Предприятие 8.5. */
#nomenTable td.bg-onec-tint { background: rgba(245, 194, 10, 0.15) !important; }
#nomenTable td.bg-onec-tint.copyable:hover { background: rgba(245, 194, 10, 0.3) !important; }

/* Кнопки-иконки в таблицах (например «История») — без контура/рамки, компактные,
   чтобы не раздували высоту строки */
.btn-icon-plain, .btn-icon-plain:focus, .btn-icon-plain:active, .btn-icon-plain:hover {
  outline: none !important;
  box-shadow: none !important;
  border-color: transparent !important;
  background: transparent !important;
  padding: 0 4px !important;
  font-size: 14px;
  line-height: 1.3;
}
.btn-icon-plain:hover { background: #eef1f8 !important; }

.sticky-top.bg-white { background: #f4f6fb !important; }

/* Живая жалоба пользователя: шапка «Прайсов» уезжала вверх при прокрутке — thead.sticky-top
   технически стоял правильно, но .table-responsive без ограничения высоты никогда сам не
   скроллился (росла вся страница целиком), а position:sticky держится только внутри РЕАЛЬНО
   скроллящегося родителя. Ограничиваем высоту обёртки и даём ей свой overflow-y — теперь
   именно она скроллится, и шапка колонок остаётся видна сверху при прокрутке строк вниз. */
#pricesTableWrap {
  max-height: calc(100vh - 230px);
  overflow-y: auto;
}

/* Bootstrap .sticky-top (thead) — z-index 1020, выше стандартного .dropdown-menu (1000), из-за
   чего открытое меню фильтра над таблицей пряталось под шапкой таблицы. */
.dropdown-menu {
  z-index: 1030;
  font-size: 12.5px;
  box-shadow: var(--shadow);
  border-color: var(--card-border);
}
.dropdown-menu .form-check-label { font-size: 12.5px; }
.dropdown-menu .form-check-input { width: 1em; height: 1em; }

/* Плавное появление/исчезание выпадающих меню (Bootstrap по умолчанию просто переключает
   display:none/block без анимации) — по просьбе пользователя, «немного анимации плавной».
   display:block всегда (не none) — иначе opacity/transform не на чем анимировать; сама видимость
   и блокировка кликов, когда меню закрыто, — через visibility/pointer-events, оба тоже участвуют
   в transition (стандартный приём: при появлении visibility переключается мгновенно в начале
   перехода, при исчезновении — только в конце, после того как opacity уже дошёл до 0). */
.dropdown-menu {
  display: block;
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
  transform: translateY(-6px);
  transition: opacity .15s ease, transform .15s ease, visibility .15s ease;
}
.dropdown-menu.show {
  visibility: visible;
  opacity: 1;
  pointer-events: auto;
  transform: translateY(0);
}

/* ===== Меню «Действия» (Номенклатура) — собирает кнопки синхронизации/массовых операций,
   раньше занимавшие отдельный целый блок над таблицей, в одну кнопку. Кнопки внутри намеренно
   нейтральные (не цветные, по просьбе пользователя) — только текст/иконка отличают действия. ===== */
.nomen-actions-menu {
  width: 320px;
  max-width: 92vw;
  padding: 8px 0 12px;
}
.nomen-actions-menu .dropdown-header {
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-muted);
  padding: 6px 16px 4px;
}
.nomen-actions-menu .nomen-action-group {
  padding: 2px 14px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.nomen-actions-menu .nomen-action-group .btn {
  text-align: left;
}
.nomen-actions-menu .dropdown-divider {
  margin: 8px 0;
}
.nomen-actions-menu > .nomen-action-group:last-of-type {
  margin-bottom: 2px;
}

/* ===== Модалки — лёгкость/тень + более плавное появление-исчезание (по просьбе пользователя).
   Bootstrap уже анимирует .modal.fade .modal-dialog (translateY + opacity), здесь только
   усиливаем: мягкая тень вместо плоской заливки, лёгкое масштабирование при появлении вместо
   одного лишь вертикального сдвига — «воздушнее», не просто «выезжает». ===== */
.modal-content {
  border: none;
  border-radius: var(--radius);
  box-shadow: 0 24px 60px -16px rgba(20,25,45,0.35), 0 8px 24px -8px rgba(20,25,45,0.18);
}
.modal.fade .modal-dialog {
  transform: translateY(-16px) scale(.98);
  transition: transform .22s cubic-bezier(.3,1.2,.4,1), opacity .18s ease;
}
.modal.show .modal-dialog {
  transform: translateY(0) scale(1);
}
.modal-backdrop.fade {
  transition: opacity .2s ease;
}

/* «Заморозка» колонок при горизонтальной прокрутке (см. assets/sticky-columns.js —
   он проставляет left инлайном по реальной ширине столбцов). */
.sticky-col { position: sticky; z-index: 2; background: #fff; }
thead .sticky-col { z-index: 4; background: #f4f6fb; }
.table tbody tr:nth-child(even) .sticky-col { background: #eef1f8; }
.sticky-col-last { box-shadow: 2px 0 4px rgba(0,0,0,0.08); }

.text-profit { color: var(--profit) !important; font-weight: 700; }
.text-loss { color: var(--loss) !important; font-weight: 700; }

/* ===== Forms & buttons ===== */

.form-label {
  font-size: 13px;
  font-weight: 600;
  color: #4a5170;
}

.form-control, .form-select {
  border-radius: var(--radius-sm);
  border-color: #dde1ea;
  font-size: 14px;
}

.form-control:focus, .form-select:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(47,125,251,0.15);
}

.btn {
  border-radius: var(--radius-sm);
  font-weight: 600;
  font-size: 14px;
  padding: 9px 18px;
}

/* .btn-sm имеет ту же специфичность (0,1,0), что и .btn выше — без явного переопределения
   здесь бразуер брал padding/font-size именно от .btn (по порядку в файле), и все "маленькие"
   кнопки в тулбарах реально рендерились как обычные, громадные. */
.btn-sm {
  padding: 4px 10px;
  font-size: 12.5px;
}

.btn-success {
  background: var(--accent-2);
  border-color: var(--accent-2);
}
.btn-success:hover { background: #128a3e; border-color: #128a3e; }

.btn-outline-secondary, .btn-outline-primary, .btn-outline-success, .btn-outline-danger {
  border-radius: var(--radius-sm);
}

.alert {
  border-radius: var(--radius-sm);
  border: none;
  font-size: 14px;
}
.alert-success { background: #e7f8ee; color: #146c35; }

/* Модалка «Все свойства» (Номенклатура) — компактные вкладки по МП, обязательные поля
   обведены рамкой вместо бейджа, чтобы взгляд сразу цеплялся за то, что нельзя оставить пустым. */
.nav-tabs-sm .nav-link { padding: 4px 12px; font-size: 12.5px; }
/* Вкладки OZON/WB в карточке товара — фирменные цвета площадок (см. --ozon-color/--wb-color),
   чтобы сразу визуально различать, какая вкладка какой площадки. */
.nav-tabs-sm .nav-link.nav-link-ozon { color: var(--ozon-color); }
.nav-tabs-sm .nav-link.nav-link-ozon.active { color: #fff; background: var(--ozon-color); border-color: var(--ozon-color); }
.nav-tabs-sm .nav-link.nav-link-wb { color: var(--wb-color); }
.nav-tabs-sm .nav-link.nav-link-wb.active { color: #fff; background: var(--wb-color); border-color: var(--wb-color); }
/* Значок «заполнить пачкой» (copy-plus) у заголовка свойства — приглушённый по умолчанию,
   ярче при наведении, чтобы не отвлекать от самого поля. */
.props-bulk-fill-icon { color: rgba(108, 117, 125, 0.6); font-size: 13px; }
.props-bulk-fill-icon:hover { color: var(--bs-primary, #0d6efd); }
.props-field-required .form-control,
.props-field-required .form-select {
  border-color: var(--loss, #c62828);
  box-shadow: 0 0 0 1px rgba(198, 40, 40, 0.15);
}
/* Обязательное поле реально заполнено — красим зелёным вместо красного (переключается JS
   при загрузке значений и при вводе, см. updateRequiredFieldState() в web/nomenclature.php). */
.props-field-required.props-field-filled .form-control,
.props-field-required.props-field-filled .form-select {
  border-color: var(--profit, #16a34a);
  box-shadow: 0 0 0 1px rgba(22, 163, 74, 0.15);
}

/* Поле входит в SPU — «отпечаток» товара, по которому OZON ловит дубликаты (см. applySpuBadges()
   в web/nomenclature.php). Тон OZON-синий, а не красный: это не ошибка поля, а признак «здесь
   товары должны отличаться друг от друга». */
.props-spu-badge {
  display: inline-block;
  margin-left: 5px;
  padding: 0 4px;
  font-size: 9px;
  font-weight: 700;
  line-height: 14px;
  letter-spacing: 0.3px;
  color: #fff;
  background: var(--ozon-color, #005bff);
  border-radius: 3px;
  cursor: help;
  vertical-align: 1px;
}

/* Классификация «уникальности» значения у товаров того же типа — прямо на самом поле в карточке
   (не только в панели переменных шаблона, см. .template-var-item-unique/-common) — по прямому
   запросу пользователя. Полоска слева, не трогает border-color целиком — не спорит с красной/
   зелёной рамкой обязательных полей выше, оба состояния читаются одновременно. */
.props-field-stat-unique > .form-control,
.props-field-stat-unique > .form-select,
.props-field-stat-unique > textarea,
.props-field-stat-unique .dict-autocomplete .form-control {
  border-left: 3px solid #0d6efd;
}
.props-field-stat-unique > label { color: #0d6efd; }
.props-field-stat-common > .form-control,
.props-field-stat-common > .form-select,
.props-field-stat-common > textarea,
.props-field-stat-common .dict-autocomplete .form-control {
  border-left: 3px solid rgba(108, 117, 125, 0.5);
}
.props-field-stat-common > label { color: var(--text-muted, #6c757d); }

/* Подсказка со значением с другого маркетплейса того же товара (см. "заимствовать между МП") —
   отдельный цвет от обычной подсказки «часто встречается», чтобы визуально различать источник. */
.props-suggestion.props-suggestion-cross { color: var(--profit, #16a34a); }

/* Поле, которое OZON перечислил как влияющее на контент-рейтинг и ещё не выполнено (план
   «Рейтинг карточек») — отдельный оранжевый акцент, отличимый от красного «обязательное поле». */
.props-field-rating-issue .form-control,
.props-field-rating-issue .form-select,
.props-field-rating-issue textarea {
  border-color: #fd7e14;
  box-shadow: 0 0 0 1px rgba(253, 126, 20, 0.2);
}
.props-field-rating-issue > label {
  color: #fd7e14;
}

/* Название товара по площадке (Фаза 3) — счётчик символов красным при превышении лимита. */
.mp-name-over-limit { color: var(--loss, #c62828) !important; font-weight: 600; }
.props-mp-name-block textarea.mp-name-over-limit-field {
  border-color: var(--loss, #c62828);
  box-shadow: 0 0 0 1px rgba(198, 40, 40, 0.15);
}

/* Длинные текстовые поля (Аннотация/Описание) — на всю ширину карточки, не в узкой col-md-6,
   т.к. реальный текст обычно 1000-2000 символов и его неудобно редактировать в узкой колонке. */
.props-field-text textarea[data-prop-key] { font-size: 13px; line-height: 1.4; }
/* Рамка+фон на весь блок описания (не только на развёрнутое содержимое) — иначе в свёрнутом виде
   это просто строка текста «Описание / ▸ Показать», которая теряется среди других полей
   (пользователь явно сообщил, что визуально «терял» это место). Тот же серый фон, что у блока
   рейтинга/цены (#f8f9fa) — уже устоявшийся в карточке визуальный язык «это отдельный блок», плюс
   цветная полоска слева, чтобы бросалось в глаза даже свёрнутым, а не просто светлая плашка. */
.props-field-text {
  border: 1px solid rgba(0, 0, 0, 0.12);
  border-left: 4px solid #6f42c1;
  border-radius: var(--radius-sm, 6px);
  padding: 8px 10px;
  background: #f8f9fa;
}
/* Свёрнуто по умолчанию при открытии карточки (см. initDescriptionTemplateFields()) — очень
   длинный блок, не должен занимать место, пока пользователь явно не попросил его показать. */
.props-desc-collapse-toggle { white-space: nowrap; }
.props-text-preview-toggle { cursor: pointer; }
.props-text-preview {
  margin-top: 6px;
  padding: 8px 10px;
  border: 1px dashed rgba(0, 0, 0, 0.2);
  border-radius: 4px;
  background: rgba(0, 0, 0, 0.02);
}
.props-text-preview-body {
  white-space: pre-wrap;
  word-break: break-word;
  font-size: 13px;
  max-height: 320px;
  overflow-y: auto;
}
/* description_shop — рендерится реальным HTML (см. shopRenderRichTextJs()), а не pre-wrap
   текстом, как остальные text-поля — тут разметки нет, переносы уже переданы через <p>/<br>. */
.props-text-preview-rich { white-space: normal; }
.props-text-preview-rich p { margin: 0 0 8px; }
.props-text-preview-rich p:last-child { margin-bottom: 0; }
.props-text-preview-rich ul { margin: 0 0 8px; padding-left: 20px; }

/* Шаблоны описаний (план «Шаблоны описаний») — панель переменных, шаблон и предпросмотр теперь
   идут одной колонкой сверху вниз (предпросмотр снизу, по прямому запросу пользователя), а не
   двумя колонками рядом. */
.template-vars-panel {
  max-height: 220px;
  overflow-y: auto;
  border: 1px solid rgba(0, 0, 0, 0.12);
  border-radius: var(--radius-sm, 4px);
  padding: 4px 6px;
  background: #fff;
}
/* grid, не flex — колонка со значком копирования всегда в одной и той же горизонтальной позиции
   независимо от длины подписи слева (раньше при flex+space-between короткие и длинные подписи
   давали "рваный" правый край и разную высоту строк — по прямому запросу пользователя сделать
   ровным). Подпись может переноситься на несколько строк, не ломая сетку. */
.template-var-item {
  display: grid;
  /* Значок копирования — первая колонка (слева), по прямому запросу пользователя: удобнее
     находить его глазами всегда в одной и той же точке у начала строки, а не в конце после
     подписи переменной длины. */
  grid-template-columns: auto 1fr;
  align-items: start;
  gap: 6px;
  padding: 4px 0;
  font-size: 11px;
  border-bottom: 1px dashed rgba(0, 0, 0, 0.08);
}
.template-var-item:last-child { border-bottom: none; }
/* Подпись и значение — одна строка «Подпись: значение», обычный текстовый перенос (не два
   раздельных блока друг под другом), по прямому запросу пользователя. */
.template-var-text { min-width: 0; overflow-wrap: break-word; }
.template-var-label { font-weight: 600; }
.template-var-value { color: var(--text-muted, #6c757d); }
/* Классификация «уникальности» значения у товаров того же типа (план «Свойства по умолчанию») —
   синий: значение почти всегда разное у каждого товара (штрих-код, совместимые модели и т.п.),
   серый: почти у всех товаров этого типа одно и то же значение (кандидат на автозаполнение при
   создании новой карточки, см. propsFieldStats/props_schema_field_uniqueness()). Полей без
   достаточной статистики (kind:'mixed') это не касается — остаются в обычном виде. */
.template-var-item-unique .template-var-label { color: #0d6efd; }
.template-var-item-common .template-var-label { color: var(--text-muted, #6c757d); font-weight: 500; }
.template-var-copy-btn {
  flex: 0 0 auto;
  border: none;
  background: transparent;
  color: rgba(108, 117, 125, 0.7);
  padding: 2px;
  line-height: 0;
  cursor: pointer;
  align-self: start;
}
.template-var-copy-btn:hover { color: var(--bs-primary, #0d6efd); }

/* Подсветка {переменных} в тексте шаблона — прозрачный textarea поверх подложки с
   раскрашенным HTML (см. renderTemplateHighlight() в web/nomenclature.php). Подложка и textarea
   должны иметь абсолютно одинаковые шрифт/отступы/границу, иначе текст «поплывёт». */
.template-highlight-wrap { position: relative; }
.template-highlight-backdrop {
  position: absolute;
  inset: 0;
  margin: 0;
  padding: 0.25rem 0.5rem;
  font-size: 13px;
  line-height: 1.5;
  white-space: pre-wrap;
  word-wrap: break-word;
  overflow: hidden;
  pointer-events: none;
  color: var(--bs-body-color, #212529);
  border: 1px solid transparent;
  border-radius: var(--radius-sm, 4px);
  background: #fff;
}
.template-highlight-input {
  position: relative;
  background: transparent !important;
  color: transparent !important;
  caret-color: var(--bs-body-color, #212529);
  /* Реальный курсор/позиция ввода принадлежит ЭТОМУ textarea, а не подложке под ним — как
     только текста становится больше видимой высоты, браузер показывает у textarea нативный
     скроллбар и его контентная область СУЖАЕТСЯ на ширину скроллбара, а у подложки (обычный div
     без скроллбара) — нет. Из-за этого перенос строк у них расходится, и клик/печать в
     textarea попадает не туда, где визуально показан текст в подложке (живая жалоба
     пользователя). Скроллбар прячем (скролл по-прежнему работает — колесом/клавиатурой,
     синхронизируется в подложку через 'scroll', см. web/nomenclature.php), чтобы ширина
     контентной области у обоих слоёв всегда совпадала.
  */
  scrollbar-width: none;
  /* НАСТОЯЩАЯ причина «редактирует строкой выше/ниже» (живая жалоба, воспроизведена и
     подтверждена после первой попытки исправить только скроллбар): подложка задаёт
     font-size:13px/line-height:1.5 явно, а у САМОГО textarea никакого явного font-size не
     было — он брал 14px из бутстраповского .form-control-sm по умолчанию. Разная высота
     строки на каждой площадке текста и давала нарастающее расхождение (уже на 6-8 строках
     набегает больше строки разницы). Значения здесь ДОЛЖНЫ быть побайтово идентичны тем,
     что в .template-highlight-backdrop — это не стилевое совпадение, а обязательное условие
     работы всего приёма с подложкой.
  */
  font-size: 13px;
  line-height: 1.5;
}
.template-highlight-input::-webkit-scrollbar { display: none; }
.template-highlight-input::selection { background: rgba(13, 110, 253, 0.35); }
.template-var-token { color: #0d6efd; }
.template-var-token-unknown { color: #dc3545; text-decoration: underline wavy; text-decoration-color: rgba(220, 53, 69, 0.6); }
.template-preview-body {
  white-space: pre-wrap;
  word-break: break-word;
  font-size: 13px;
  min-height: 180px;
  max-height: 320px;
  overflow-y: auto;
  border: 1px dashed rgba(0, 0, 0, 0.2);
  border-radius: var(--radius-sm, 4px);
  padding: 8px 10px;
  background: #fff;
}

/* Автодополнение справочника OZON (Фаза 5) — выпадающий список над/под полем ввода. */
.dict-autocomplete { position: relative; }
.dict-autocomplete-menu {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 2000;
  background: var(--bs-body-bg, #fff);
  border: 1px solid rgba(0, 0, 0, 0.15);
  border-radius: 4px;
  max-height: 220px;
  overflow-y: auto;
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
}
.dict-autocomplete-item { padding: 4px 8px; font-size: 12px; cursor: pointer; }
.dict-autocomplete-item:hover { background: rgba(13, 110, 253, 0.12); }

/* Справочник-коллекция (напр. «Совместимые модели», Фаза 6.2) — выбор чипами через живой поиск
   вместо свободного ввода построчно, чтобы значения гарантированно совпадали со справочником
   OZON (без лишних пробелов/опечаток при пуше). Значок «x» — стилистика lucide.dev. */
.dict-collection-chips { display: flex; flex-wrap: wrap; gap: 3px; margin-top: 3px; }
.dict-collection-chips:empty { margin-top: 0; }
.dict-collection-chip {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 1px 3px 1px 6px;
  border-radius: 10px;
  background: rgba(13, 110, 253, 0.1);
  font-size: 10px;
  line-height: 1.5;
}
.dict-collection-chip-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 13px;
  height: 13px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: rgba(0, 0, 0, 0.5);
  cursor: pointer;
}
.dict-collection-chip-remove svg { width: 9px; height: 9px; }
.dict-collection-chip-remove:hover { background: rgba(198, 40, 40, 0.15); color: var(--loss, #c62828); }

/* Подсказка «часто встречается у похожих товаров» (Фаза 4.1) — не автозаполнение, просто
   предложение под пустым полем с кнопкой «применить». */
.props-suggestion { font-size: 11px; }
.props-suggestion a { cursor: pointer; }

/* Галерея фото (Номенклатура — модалка «Фото на маркетплейсах») — чекбокс выбора и крестик
   удаления лежат бейджами чуть за пределами превью (как в macOS Finder/Photos), перетаскивание
   мышью меняет порядок (первое фото = обложка). */
.media-gallery { padding-top: 10px; }
.media-gallery .media-thumb { position: relative; width: 84px; cursor: grab; }
.media-gallery .media-thumb.dragging { opacity: 0.35; }
.media-gallery .media-thumb.drag-over { outline: 2px dashed var(--accent); outline-offset: 3px; border-radius: var(--radius-sm); }
.media-gallery .media-thumb-img { position: relative; display: block; }
.media-gallery img { width: 84px; height: 84px; object-fit: cover; border-radius: var(--radius-sm); border: 1px solid #e3e7f0; display: block; pointer-events: none; }
.media-gallery .media-thumb .badge { position: absolute; bottom: 3px; left: 3px; font-size: 9px; z-index: 2; }
.media-select-overlay {
  appearance: none; -webkit-appearance: none; margin: 0;
  position: absolute; top: -8px; left: -8px; z-index: 3;
  width: 20px; height: 20px; border-radius: 50%;
  background: rgba(40,40,40,0.55); border: 2px solid #fff;
  box-shadow: 0 1px 3px rgba(0,0,0,0.3); cursor: pointer;
}
.media-select-overlay:checked { background: var(--accent); }
.media-select-overlay:checked::after {
  content: '✓'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  color: #fff; font-size: 12px; line-height: 1; font-weight: 700;
}
.media-delete-btn {
  position: absolute; top: -8px; right: -8px; z-index: 3;
  width: 20px; height: 20px; padding: 0; border-radius: 50%;
  background: var(--loss, #dc2626); border: 2px solid #fff;
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; line-height: 1; color: #fff; font-weight: 700;
  box-shadow: 0 1px 3px rgba(0,0,0,0.3);
  cursor: pointer; transition: background .12s ease;
}
.media-delete-btn:hover { background: #b91c1c; }
.media-draft { border: 1px dashed var(--accent); border-radius: var(--radius-sm); padding: 6px 8px; }
.media-draft img { width: 64px; height: 64px; object-fit: cover; border-radius: var(--radius-sm); vertical-align: middle; margin-right: 8px; }

/* Разделительная полоса со стрелкой между OZON/WB — направление меняется по «Откуда»/«Куда». */
.media-transfer-divider { position: relative; display: flex; align-items: center; justify-content: center; margin: 4px 0 14px; height: 1px; }
.media-transfer-divider::before { content: ''; position: absolute; left: 0; right: 0; top: 0; height: 1px; background: #e3e7f0; }
.media-transfer-arrow {
  position: relative; z-index: 1; background: #fff; border: 1px solid #e3e7f0; border-radius: 20px;
  padding: 1px 16px; font-size: 18px; color: var(--accent); transition: transform .2s ease;
}

/* Селекты «Откуда»/«Куда» подсвечены цветом площадки — сразу видно, что выбрано верно. */
.media-mp-select.select-ozon { border-color: var(--ozon-color); color: var(--ozon-color); font-weight: 600; }
.media-mp-select.select-wb { border-color: var(--wb-color); color: var(--wb-color); font-weight: 600; }
.th-ozon-text { color: var(--ozon-color); }
.th-wb-text { color: var(--wb-color); }

/* Сообщение поверх страницы (не толкает контент вниз под шапкой) — само пропадает
   через несколько секунд, см. JS рядом с #flashMessage. */
.flash-toast {
  position: fixed;
  top: 20px;
  right: 20px;
  z-index: 2000;
  min-width: 280px;
  max-width: 440px;
  box-shadow: 0 10px 30px rgba(0,0,0,0.18);
}
/* JS-версия тоста (showToast() в web/nomenclature.php) — длиннее живёт (8-10 сек, пользователь
   попросил, т.к. короткое сообщение о синке не успевало читаться) и с крестиком закрыть;
   несколько тостов не перекрываются — каждый следующий ниже предыдущего. */
/* pre-line — чтобы переносы строк в тексте ошибки реально переносились, а не слипались в
   сплошную простыню (живая жалоба пользователя: «нужно каждую ошибку писать с новой строки,
   а то там всё сливается»). Именно pre-line, а не pre: лишние пробелы и переносы самой
   вёрстки по-прежнему схлопываются, переносится только то, что мы поставили осознанно. */
.flash-toast-js { padding-right: 34px; white-space: pre-line; }
/* Длинный многострочный текст (список ошибок модерации) не должен вылезать за экран. */
.flash-toast-js { max-height: 60vh; overflow-y: auto; }

/* Те же переносы для сообщений об ошибках внутри модалок: текст приходит из ozon_explain_push_error()
   уже разбитым по строкам (несколько замечаний модерации + рекомендация), а вставляется через
   textContent — без pre-line всё слипалось бы в один абзац. */
#mediaModalError,
#mediaModalWarning,
#pushContentModalError,
#createOzonModalError,
#propsModalError { white-space: pre-line; }
.flash-toast-js .btn-close { position: absolute; top: 12px; right: 12px; }
.alert-danger { background: #fdecec; color: #a31414; }
.alert-info { background: #eaf2ff; color: #1a4fa0; }

.form-text { font-size: 12.5px; color: var(--text-muted); }

/* Легенда «Описание» на «Прайсах» — разбита на разделы, не одним сплошным абзацем */
.prices-legend h6 {
  font-size: 11px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: .03em;
  color: #4a5170;
  margin: 14px 0 6px;
}
.prices-legend h6:first-child { margin-top: 0; }
.prices-legend > div { line-height: 1.5; }

/* ===== Misc ===== */

@media (max-width: 900px) {
  .app-shell { flex-direction: column; }
  .sidebar {
    width: 100%;
    height: auto;
    position: static;
    flex-direction: row;
    overflow-x: auto;
  }
  .sidebar-brand { display: none; }
  .sidebar-toggle { display: none; }
  body.sidebar-collapsed .sidebar { width: 100%; }
  .sidebar-nav { flex-direction: row; padding: 8px; }
  .sidebar .nav-link span { display: none; }
  .sidebar-footer { display: flex; margin: 0; border: none; }
  .app-main-inner { padding: 18px; }
}

/* Терминальная консоль вывода прогресса «🔄 Обновить карточки на МП» (#bulkUpdateProgress) —
   по прямому запросу пользователя: компактный монопространственный лог, как в окне терминала,
   не занимает много места на экране (мелкий шрифт, узкая высота строки). Успешные результаты
   сворачиваются в один счётчик вместо построчного вывода на каждый товар (см.
   addSuccessCount()/addGroupedCounts() в web/nomenclature.php) — построчно показываются только
   реальные ошибки (❌) и частичные проблемы (⚠️), на которые действительно стоит обратить
   внимание. */
.bulk-console {
  background: #0d1117;
  color: #c9d1d9;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px;
  line-height: 1.6;
  padding: 8px 10px;
  border-radius: var(--radius-sm, 4px);
  max-height: 220px;
}
.bulk-console .console-success { color: #3fb950; }
.bulk-console .console-danger { color: #f85149; }
.bulk-console .console-warning { color: #d29922; }
.bulk-console .console-muted { color: #8b949e; }

/* Прогресс-бар «🔄 Обновить карточки на МП», пока идёт «Проверить»/«Начать» (по прямому запросу
   пользователя). Шаг — одна площадка×операция (см. countSteps()/stepProgressBar() в
   web/nomenclature.php), полоска с бегущей штриховкой, пока задача выполняется, заливается
   сплошным зелёным по завершении (красным — если прервалась ошибкой). */
.bulk-progress-wrap { margin-bottom: 10px; }
.bulk-progress {
  height: 8px;
  border-radius: 999px;
  background: #e9ecef;
  overflow: hidden;
}
.bulk-progress-fill {
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--accent, #2f7dfb), #63a4ff);
  transition: width .35s ease;
}
.bulk-progress-fill.is-active {
  background-image: linear-gradient(
    135deg,
    rgba(255, 255, 255, .28) 25%, transparent 25%,
    transparent 50%, rgba(255, 255, 255, .28) 50%,
    rgba(255, 255, 255, .28) 75%, transparent 75%, transparent
  );
  background-size: 28px 28px;
  animation: bulk-progress-stripes 0.9s linear infinite;
}
.bulk-progress-fill.is-done { background: var(--profit, #16a34a); }
.bulk-progress-fill.is-error { background: var(--loss, #dc2626); }
@keyframes bulk-progress-stripes {
  from { background-position: 0 0; }
  to { background-position: 28px 0; }
}
.bulk-progress-label {
  font-size: 11px;
  color: var(--text-muted);
  margin-top: 4px;
  text-align: right;
}

/* Свернуть/развернуть дерево 1С (номенклатурные группы / виды номенклатуры, settings.php) —
   строки используют .d-flex (Bootstrap-утилита с !important), поэтому обычный инлайновый
   element.style.display='none' молча перебивается и визуально ничего не сворачивается —
   нужен свой класс с !important. */
.onec-tree-hidden {
  display: none !important;
}

/* Перетаскивание фото из проводника во вкладку «Медиа» карточки товара. Подсвечиваем весь
   блок площадки (data-mp-section), а не узкую полоску — попасть мышью в большую область
   проще, и сразу видно, на КАКУЮ площадку уйдут файлы (OZON и WB — разные блоки). */
.media-mp-block {
  border: 2px dashed transparent;
  border-radius: 8px;
  padding: 6px;
  transition: border-color .15s, background-color .15s;
}
.media-mp-block.media-dropzone-active {
  border-color: var(--bs-primary, #0d6efd);
  background: rgba(13, 110, 253, .06);
}
.media-dropzone-hint {
  font-size: 11px;
  color: #6c757d;
}

/* ---------------------------------------------------------------------------
   Карточка номенклатуры — сетка полей (редизайн по просьбе пользователя:
   «поля прыгают, выровнять по сетке, три ряда, разбить на блоки»).

   Ключевое: поля раскладывает CSS Grid, а не вложенные Bootstrap-колонки. Раньше
   каждая ГРУППА свойств была отдельной col-md-6 со своим вертикальным стеком —
   из-за разного числа полей в группах соседние колонки не совпадали по строкам,
   а поля разной высоты (чипы справочника, textarea, подсказка «часто
   встречается») дополнительно ломали ряд. Grid держит колонки и ряды сам.
--------------------------------------------------------------------------- */
.modal-props { max-width: min(1320px, 96vw); }

.props-grid {
  display: grid;
  /* Ровно три колонки на широком экране (просьба пользователя), две на планшете,
     одна на телефоне — фиксированное число колонок, а не auto-fill: так ряды
     совпадают между секциями и поля не «плавают» при изменении ширины окна. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px 14px;
  align-items: start;
}
@media (max-width: 1100px) { .props-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 720px)  { .props-grid { grid-template-columns: minmax(0, 1fr); } }
/* Поле на всю ширину сетки — длинные тексты (название, описание). */
.props-grid > .props-grid-wide { grid-column: 1 / -1; }

/* Поле — атом фиксированной структуры: подпись ровно в одну строку, контрол
   одной высоты у всех типов. Именно это не даёт полям «прыгать» по вертикали. */
.props-grid .props-field { display: flex; flex-direction: column; margin-bottom: 0 !important; }
.props-grid .props-field > label {
  font-size: 11px;
  line-height: 16px;
  min-height: 16px;
  margin-bottom: 2px;
  gap: 6px;
}
/* Подпись обрезается многоточием, а не переносится второй строкой — иначе поля
   ввода в соседних колонках начинались бы на разной высоте. Полный текст — в title.
   Первым потомком бывает и <span> (статичные поля), и <div> с пометкой
   «(справочник)» (динамические, см. buildDynamicField()) — правило одно на оба. */
.props-grid .props-field > label { white-space: nowrap; flex-wrap: nowrap; }
.props-grid .props-field > label > *:first-child {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.props-grid .props-field > label > *:last-child { flex: 0 0 auto; }

/* Виджеты-справочники (автодополнение и чипы) — такая же высота старта, как у
   обычного input: они вкладывают поле внутрь своей обёртки. */
.props-grid .props-field > .dict-autocomplete,
.props-grid .props-field > .dict-collection { display: block; }
.props-grid .props-field .form-control,
.props-grid .props-field .form-select { min-height: 31px; }
.props-grid .props-field textarea.form-control { min-height: 31px; }

/* Смысловой блок полей. Заголовок тонкий и негромкий — он структурирует, а не
   спорит за внимание с самими полями. */
.props-section { margin-bottom: 14px; }
.props-section-head {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: #6c757d;
  padding-bottom: 4px;
  margin-bottom: 8px;
  border-bottom: 1px solid #e9ecef;
}
.props-section-head .props-section-count {
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  color: #adb5bd;
}
/* Сворачиваемая секция (дополнительные поля) — заголовок кликабелен целиком. */
a.props-section-head { text-decoration: none; cursor: pointer; }
a.props-section-head:hover { color: #495057; }
a.props-section-head .props-section-caret { transition: transform .15s ease; }
a.props-section-head[aria-expanded="true"] .props-section-caret { transform: rotate(90deg); }

/* Секция общих характеристик типа товара — визуально выделена, это «сердце»
   карточки: заполняется один раз и уходит на обе площадки. */
.props-section-type .props-section-head { color: #0d6efd; border-bottom-color: #cfe2ff; }

/* Быстрый выбор из уже используемых значений (см. syncQuickValues() в web/nomenclature.php) —
   компактные чипы под пустым полем: «выбрал тип — сразу видно, какие бренды реально
   встречаются в каталоге». Не влияют на высоту ряда: сетка выравнивает поля по верху. */
.props-quick-values { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
.props-quick-value {
  border: 1px solid #dee2e6;
  background: #f8f9fa;
  color: #495057;
  border-radius: 10px;
  padding: 1px 8px;
  font-size: 11px;
  line-height: 16px;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.props-quick-value:hover { background: #e7f1ff; border-color: #9ec5fe; color: #0a58ca; }

/* Спрос на «Прайсах» (столбец «Спрос», см. price_demand_cell()): 🔥 — разбирают быстро,
   можно поднять цену; 🐢 — лежит на складе, стоит снизить. Подсветка строки мягкая — таблица
   и так плотная, кричащий фон мешал бы читать остальные колонки. */
.demand-cell { white-space: nowrap; }
.demand-icon { font-size: 12px; }
#pricesTable tbody tr.row-demand-fast td:not(.sticky-col) { background: rgba(22, 163, 74, .07); }
#pricesTable tbody tr.row-demand-slow td:not(.sticky-col) { background: rgba(234, 88, 12, .07); }
#pricesTable tbody tr.row-demand-fast .demand-cell { color: #15803d; font-weight: 600; }
#pricesTable tbody tr.row-demand-slow .demand-cell { color: #c2410c; font-weight: 600; }

/* Товар реально продаётся в убыток (см. get_loss_stats()) — не расчёт, а факт по отчётам.
   Подсветка сильнее, чем у «медленно продаётся»: это прямая потеря денег. */
#pricesTable tbody tr.row-sells-loss td:not(.sticky-col) { background: rgba(198, 40, 40, .09); }

/* Поле поиска на «Прайсах» с крестиком очистки: кнопка появляется только когда есть текст —
   пустое поле не должно нести лишний элемент управления. */
.prices-search-wrap { position: relative; width: 280px; }
.prices-search-wrap input { padding-right: 28px; }
.prices-search-clear {
  position: absolute; top: 50%; right: 4px; transform: translateY(-50%);
  border: 0; background: transparent; color: #98a2b3; line-height: 1;
  padding: 3px; border-radius: 4px; cursor: pointer;
}
.prices-search-clear:hover { color: #475467; background: #eef1f5; }
