@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%;
}

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);
}

.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;
}

.app-main-inner {
  padding: 32px 40px 60px;
  max-width: 1360px;
  margin: 0 auto;
}

/* Прайсы/Номенклатура: таблица широкая (много колонок под OZON+WB) — не зажимаем общим лимитом
   ширины страницы, пусть использует всё доступное место рядом с сайдбаром на широких экранах.
   По прямому запросу пользователя — раньше большие пустые отступы по бокам не спасали от
   горизонтального скролла таблицы, только зря съедали ширину. */
body.page-prices .app-main-inner,
body.page-nomenclature .app-main-inner { max-width: none; }

/* ===== 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: 24px 26px;
  box-shadow: var(--shadow);
  border: 1px solid var(--card-border);
  margin-bottom: 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;
}

/* Цена без акции — мелким текстом под ценой по акции (см. .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-calc { padding: 10px 12px; }
.calc-tip-table { border-collapse: collapse; font-size: 12px; }
.calc-tip-table td { padding: 2px 10px 2px 0; white-space: nowrap; }
.calc-tip-table td:last-child { padding-right: 0; text-align: right; font-weight: 600; }
.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: 220px; white-space: normal; }
.calc-tip-note { font-size: 10.5px; color: #8a94a6; white-space: normal; max-width: 240px; 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;
}

/* Расширенные колонки расхода (позаказная таблица) — свёрнуты по умолчанию */
.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);
}

/* Классификация «уникальности» значения у товаров того же типа — прямо на самом поле в карточке
   (не только в панели переменных шаблона, см. .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: 1fr auto;
  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-bottom: 3px; }
.dict-collection-chips:empty { margin-bottom: 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 сек, пользователь
   попросил, т.к. короткое сообщение о синке не успевало читаться) и с крестиком закрыть;
   несколько тостов не перекрываются — каждый следующий ниже предыдущего. */
.flash-toast-js { padding-right: 34px; }
.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;
}
