/* ===========================================================
   XFIX Trade-IN — design tokens (per XFIX Brandbook v1.0)
   =========================================================== */
:root{
  --xfix-green: #39BB3B;
  --xfix-green-dark: #2ea030;
  --xfix-black: #000000;
  --xfix-white: #ffffff;
  --xfix-gray: #d2d2d2;
  /* Текст поверх --xfix-green (активная вкладка сайдбара/бейдж/аватар/активный период/активный
     таб "Оценки стоимости") — раньше хардкодился как #08150a (тёмный, читаемый на ярко-зелёном)
     прямо в каждом правиле. Вынесено в переменную ради интеграции "Алиса Дисконт" (2026-07-29,
     см. html[data-skin="alisa"] ниже) — там акцентный цвет фиолетовый и тёмный текст на нём
     нечитаем, нужен белый. Значение по умолчанию не меняется — ни на что, кроме нового скина,
     это не влияет. */
  --on-accent: #08150a;

  --bg: #f7f7f8;
  --surface: #ffffff;
  --surface-2: #fafafb;
  --border: #e7e7ea;
  --text: #111113;
  --text-muted: #7b7b83;
  /* Сайдбар в светлой теме — тёмный фирменный рельс.
     В тёмной теме (см. [data-theme="dark"] ниже) он, наоборот, становится светлым —
     так рабочая область и навигация визуально контрастируют друг с другом. */
  --sidebar-bg: #0c0c0d;
  --sidebar-border: #1d1d1f;
  /* Разделитель между группами вкладок в навигации — заметно контрастнее --sidebar-border
     (тот используется для тонкой обводки/подчёркиваний, где линия должна быть почти
     незаметной), по фидбэку владельца ("сделай линии более видимыми"). */
  --sidebar-divider: #302f34;
  --sidebar-text: #b9b9c0;
  --sidebar-text-active: #ffffff;
  --sidebar-text-muted: #6f6f78;
  --sidebar-hover-bg: rgba(255,255,255,.06);
  --sidebar-toggle-bg: rgba(255,255,255,.06);
  --sidebar-toggle-hover-bg: rgba(255,255,255,.12);
  --sidebar-toggle-hover-text: #ffffff;

  --danger: #e5484d;
  --radius-lg: 20px;
  --radius-md: 14px;
  --radius-sm: 10px;
  --shadow-sm: 0 1px 2px rgba(0,0,0,.04), 0 1px 1px rgba(0,0,0,.03);
  --shadow-md: 0 8px 24px rgba(0,0,0,.08);
  --shadow-lg: 0 24px 60px rgba(0,0,0,.18);

  /* Токены, которые меняются между светлой/тёмной темой рабочей области —
     см. блок [data-theme="dark"] в самом низу файла. */
  --input-bg: #fdfdfe;
  --input-focus-bg: #ffffff;
  --accent-soft-bg: #eefaef;
  --accent-soft-border: #c9edc9;
  --accent-soft-text: var(--xfix-green-dark);
  --danger-soft-bg: #fff5f5;
  --danger-soft-bg-strong: #fdecec;
  --danger-soft-border: #f3c8c9;
  /* Жёлтый — не из брендбука (там нет предупреждающего цвета), подобран отдельно под
     дашборд-пороги "Заморожено итого" (см. .money-yellow ниже) — тёплый янтарный, читаемый
     на белом фоне карточки, не конфликтует с фирменным зелёным/красным. */
  --warning-text: #b5820a;
  /* ---- Графика дашборда ----
     Владелец, 2026-07-30: "нужны брендированные цвета — синий не по брэнд буку, НО не перегибай
     с зелёным, по необходимости переиграй цветами на всей странице дашборда". Индиго #3f5fc4,
     которым сначала были нарисованы столбики закупки, из брендбука действительно не следует:
     там всего четыре цвета — зелёный #39BB3B, чёрный, белый и серый #D2D2D2.
     Решение — не второй акцент, а иерархия внутри уже имеющихся:
       • зелёный остаётся ТОЛЬКО у денег, которые заработаны (.big-number.accent) и у
         интерактивных элементов. Раньше зелёными были ещё и все полоски-счётчики в четырёх
         нижних карточках — четыре блока сплошного акцента, из-за которых зелёный перестал
         что-либо значить. Это и есть "перегиб с зелёным", который убран;
       • графит --chart-ink — фирменный чёрный, чуть уведённый в зелёный подтон (родство с
         брендом видно, спорить с акцентом не начинает). Им нарисованы столбики закупки: это
         деньги, ушедшие наружу, — не достижение и не убыток, им зелёный не положен;
       • --chart-ink-soft — тот же тон светлее, для второстепенных полосок-счётчиков (штуки, а
         не деньги: они должны читаться, но не кричать).
     Контраст к своей поверхности посчитан, а не подобран на глаз: светлые к #ffffff — 10.9:1
     и 3.4:1, тёмные к #19191c — 10.3:1 и 4.2:1. Тёмная тема — отдельно выбранные шаги, не
     автоматически осветлённые светлые: #334036 на тёмной карточке провалился бы в фон. */
  --chart-ink: #334036;
  --chart-ink-soft: #7d8f81;
  --chart-spend: var(--chart-ink);
  --btn-primary-bg: var(--xfix-black);
  --btn-primary-bg-hover: #222222;
  --btn-primary-text: #ffffff;
  --total-chip-bg: var(--xfix-black);
  --total-chip-border: transparent;
  --field-label-color: #3a3a40;

  --status-draft-bg: #f0f0f2;  --status-draft-text: #5b5b63;
  --status-offer-bg: #eef1ff;  --status-offer-text: #3a4bd6;
  --status-repair-bg: #fff4e0; --status-repair-text: #a9660a;
  --status-stock-bg: #eef2f4;  --status-stock-text: #3a5560;
  --status-incoming_mtime-bg: #fbeef2; --status-incoming_mtime-text: #b23a6b;
  --status-stock_mtime-bg: #f5eefc; --status-stock_mtime-text: #7a3ad6;
  --status-sold-bg: #e9f9ec;   --status-sold-text: #1f8a2e;
  --status-traded_in-bg: #fdf3e3;    --status-traded_in-text: #a3720c;
  --status-disassembled-bg: #fdecec; --status-disassembled-text: #b3312f;
  --status-in_use-bg: #e6f2fb;       --status-in_use-text: #1c66a3;
  /* "Сломано" (владелец, 2026-07-27) — намеренно другой оттенок, чем у "Разобрано" выше (обе
     красноватые, но не одинаковые — это разные статусы, не должны сливаться визуально). */
  --status-broken-bg: #fbe4d5;       --status-broken-text: #a44a12;
}

/* ===================== Тёмная тема (рабочая область) =====================
   Переключается через data-theme="dark" на <html> — см. app.js applyTheme().
   Сайдбар в тёмной теме инвертируется в светлый (--sidebar-* ниже) — контраст
   между тёмной рабочей областью и светлой навигацией сделан осознанно, по
   просьбе владельца. */
[data-theme="dark"]{
  --bg: #0f0f11;
  --surface: #19191c;
  --surface-2: #202024;
  --border: #2c2c31;
  --text: #eeeeef;
  --text-muted: #9a9aa2;
  --field-label-color: #c7c7cf;

  --sidebar-bg: #f5f5f6;
  --sidebar-border: #e2e2e5;
  --sidebar-divider: #d2d2d7;
  --sidebar-text: #55555d;
  --sidebar-text-active: #0c0c0d;
  --sidebar-text-muted: #86868f;
  --sidebar-hover-bg: rgba(0,0,0,.05);
  --sidebar-toggle-bg: rgba(0,0,0,.05);
  --sidebar-toggle-hover-bg: rgba(0,0,0,.09);
  --sidebar-toggle-hover-text: #0c0c0d;

  --input-bg: #202024;
  --input-focus-bg: #26262b;
  --accent-soft-bg: #12291a;
  --accent-soft-border: #1f4a2c;
  --accent-soft-text: #5fd473;
  --danger-soft-bg: #3a1618;
  --danger-soft-bg-strong: #452023;
  --danger-soft-border: #5c2b2f;
  /* Светлее, чем в светлой теме — на тёмном фоне тусклый янтарь плохо читается. */
  --warning-text: #e8b33d;
  --chart-ink: #b9cbbd;
  --chart-ink-soft: #6f8175;
  --chart-spend: var(--chart-ink);
  --btn-primary-bg: var(--xfix-green);
  --btn-primary-bg-hover: var(--xfix-green-dark);
  --btn-primary-text: #08150a;
  --on-accent: #08150a;
  --total-chip-bg: #202024;
  --total-chip-border: #33333a;

  --status-draft-bg: #2a2a2e;  --status-draft-text: #b7b7c0;
  --status-offer-bg: #1e2350;  --status-offer-text: #93a3ff;
  --status-repair-bg: #3d2c0d; --status-repair-text: #e2ac57;
  --status-stock-bg: #1c2c30;  --status-stock-text: #93c1cc;
  --status-incoming_mtime-bg: #3a1e28; --status-incoming_mtime-text: #e08cb1;
  --status-stock_mtime-bg: #2c1e3d; --status-stock_mtime-text: #c39bf0;
  --status-sold-bg: #123320;   --status-sold-text: #66d67a;
  --status-traded_in-bg: #3d2f11;    --status-traded_in-text: #e0ac4d;
  --status-disassembled-bg: #3a1c1c; --status-disassembled-text: #ea7c79;
  --status-in_use-bg: #16283a;       --status-in_use-text: #7cb8ea;
  --status-broken-bg: #3a2414;       --status-broken-text: #e8985a;

  --shadow-sm: 0 1px 2px rgba(0,0,0,.3), 0 1px 1px rgba(0,0,0,.2);
  --shadow-md: 0 8px 24px rgba(0,0,0,.4);
  --shadow-lg: 0 24px 60px rgba(0,0,0,.55);
}
[data-theme="dark"] .field input:focus, [data-theme="dark"] .field select:focus, [data-theme="dark"] .field textarea:focus,
[data-theme="dark"] .add-option-row input:focus{ box-shadow: 0 0 0 3px rgba(57,187,59,.25); }

*{ box-sizing: border-box; }
html,body{ height:100%; }
body{
  margin:0;
  font-family:"Wix Madefor Text", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  background: var(--bg);
  color: var(--text);
  -webkit-font-smoothing: antialiased;
}
h1,h2,h3,h4{ margin:0; font-weight:700; letter-spacing:-0.01em; }
p{ margin:0; }
button{ font-family:inherit; }
input,select,textarea{ font-family:inherit; color:inherit; }

.muted{ color: var(--text-muted); font-weight: 400; }

/* ===================== Layout ===================== */
.app{
  display:grid;
  /* 264 → 244px (2026-08-01, владелец: «давай укоротим ширину панели в пользу правого окна»).
     Ширину панели держит НЕ меню (самый длинный пункт «Поступление м.Тайм» — 149px вместе с
     иконкой и счётчиком), а подпись «STAROVETSKY PRODUCTION» под логотипом: при 11px она занимала
     166px и требовала ровно 260px панели. Уменьшив её до 10px (см. .brand-sub), укладываемся в
     244px — освободившиеся 20px ушли рабочей области. Дальше без правки самой подписи не уйти:
     логотип 44px + отступы съедают ещё ~94px. */
  grid-template-columns: 244px 1fr;
  min-height: 100vh;
}

/* ===================== Sidebar ===================== */
.sidebar{
  background: var(--sidebar-bg);
  color: var(--sidebar-text);
  display:flex;
  flex-direction:column;
  padding: 24px 16px;
  position: sticky;
  top:0;
  height:100vh;
  border-right: 1px solid var(--sidebar-border);
}
.brand{
  display:flex;
  align-items:center;
  gap:10px;
  padding: 8px 4px 28px;
  /* Не даём логотипу сжиматься, когда .nav начинает скроллиться (см. .nav ниже). */
  flex-shrink:0;
}
/* Размер знака — владелец, 2026-07-30: "Все 3 лого сверху слева можешь сделать побольше?"
   36px -> 44px сразу для всех трёх брендов (XFIX / м.Тайм / Алиса), знак у всех квадратный.
   Больше 44px ставить нельзя: сайдбар шириной 264px, минус его padding 16+16 и padding
   .brand 4+4 остаётся 224px, из них знак 44 + gap 10 = 54, подписи достаётся 170px — ровно
   столько занимает "STAROVETSKY PRODUCTION" 11px/uppercase/letter-spacing .06em. При 48px
   подпись уже переносится на вторую строку и ломает блок (по этой же причине заодно ужаты
   gap 12->10 и боковой padding 8->4). */
.brand-mark{
  width:44px; height:44px;
  flex-shrink:0;
  display:none;
}
/* Сайдбар меняет фон по теме (см. --sidebar-bg) — у логотипа поэтому два файла:
   белый бейдж (assets/logo-white.png) для тёмного сайдбара (светлая тема, по умолчанию),
   чёрный бейдж (assets/logo.png) для светлого сайдбара (тёмная тема). Раньше был только
   один вариант, из-за чего на тёмном сайдбаре чёрный квадрат сливался с фоном.
   Оба файла — реальные PNG-ассеты от владельца (не отрисованы вручную в SVG, как раньше):
   квадрат с закруглёнными углами сплошного цвета и X, вырезанный прозрачностью (alpha-cutout),
   поэтому на любом фоне X-вырез сам показывает то, что находится под картинкой — не нужно
   хардкодить контрастный цвет X отдельно на каждый файл.

   К этому добавились знаки м.Тайм и Алисы Дисконта: у каждого бренда та же пара
   "светлый / тёмный" (бренд-буки требуют именно так, а не перекрашивание одного файла).
   Выбор идёт по двум независимым осям:
     ось 1 — активный скин, атрибут data-skin на html (его ставит switchView в app.js);
     ось 2 — цвет сайдбара, то есть тема.
   Сначала показываем ОБА знака активного бренда, потом следующим правилом гасим
   неподходящий по теме. Правила намеренно одинаковой специфичности (0,2,1),
   поэтому порядок решает и !important не нужен — не менять местами. */
html:not([data-skin])        .brand-mark[data-brand="xfix"],
html[data-skin="mtime"]      .brand-mark[data-brand="mtime"],
html[data-skin="alisa"]      .brand-mark[data-brand="alisa"]{ display:block; }
html:not([data-theme="dark"]) .brand-mark[data-side="on-light"],
html[data-theme="dark"]       .brand-mark[data-side="on-dark"]{ display:none; }
.brand-text{ display:flex; flex-direction:column; line-height:1.2; }
.brand-title{ color:var(--sidebar-text-active); font-weight:800; font-size:16px; }
/* letter-spacing .06em -> .03em и nowrap — вынужденно, вместе с укрупнением знака до 44px:
   "STAROVETSKY PRODUCTION" при .06em занимает 172px, а после знака остаётся 169px, и подпись
   переносилась на вторую строку (блок бренда вырастал с 69px до 82px и переставал попадать в
   одну линию с первым пунктом меню). На .03em подпись — 165px, запас ~4px; nowrap держит её в
   одну строку принудительно, на случай другого рендеринга шрифта у владельца. */
/* 11px/.03em → 10px/.02em (2026-08-01): подпись — самый широкий элемент панели, и именно она не
   давала сузить сайдбар. 166px → 148px, панель уместилась в 244px вместо 260px. nowrap оставлен:
   в две строки блок бренда вырастает и перестаёт попадать в одну линию с первым пунктом меню. */
.brand-sub{ font-size:10px; color:var(--sidebar-text-muted); text-transform:uppercase; letter-spacing:.02em; white-space:nowrap; }

/* Прокрутка списка вкладок — владелец, 2026-07-30: "сейчас уввеличилось кол во строчек на панели
   слева и при маштабирование в большую сторону они выходят за экран, думаю при большом маштабе
   должна быть функция скролинга". Раньше .sidebar был просто колонкой высотой 100vh, и когда
   сумма высот .brand + .nav + .sidebar-footer её превышала (много пунктов ИЛИ крупный масштаб
   страницы — это то же самое: в CSS-пикселях 100vh становится меньше), нижние пункты уезжали за
   край без всякой возможности до них добраться: overflow у .sidebar не задан, а прокручивать
   страницу бесполезно — сайдбар sticky.
   Теперь скроллится ИМЕННО список: .nav забирает всё свободное место (flex:1) и прокручивается
   внутри себя. min-height:0 обязателен — без него flex-элемент не может стать ниже своего
   содержимого, и overflow никогда бы не сработал. Логотип сверху и чип пользователя снизу
   остаются на местах (flex-shrink:0 ниже), уезжают только вкладки — так и надо: профиль и выход
   всегда должны быть под рукой. */
.nav{ display:flex; flex-direction:column; gap:10px; margin-top: 4px; flex:1 1 auto; min-height:0; overflow-y:auto; overscroll-behavior:contain;
      /* Тонкая полоса в цвет сайдбара — системная по умолчанию на тёмном фоне выглядит инородно.
         scrollbar-width/-color — Firefox, ::-webkit-scrollbar ниже — Chrome/Safari. */
      scrollbar-width:thin; scrollbar-color: var(--sidebar-divider) transparent; }
.nav::-webkit-scrollbar{ width:6px; }
.nav::-webkit-scrollbar-track{ background:transparent; }
.nav::-webkit-scrollbar-thumb{ background: var(--sidebar-divider); border-radius:3px; }
.nav::-webkit-scrollbar-thumb:hover{ background: var(--sidebar-text-muted); }
/* Группы вкладок (XFIX / м.Тайм / Настройки) — разделены лаконичной горизонтальной
   чертой (.nav-divider), а не подписями секций, чтобы не перегружать сайдбар. */
.nav-group{ display:flex; flex-direction:column; gap:2px; }
.nav-divider{ height:1px; background: var(--sidebar-divider); margin: 2px 12px; flex-shrink:0; }
.nav-item{
  display:flex; align-items:center; gap:10px;
  background:transparent; border:none; cursor:pointer;
  color: var(--sidebar-text);
  padding: 11px 10px;
  border-radius: var(--radius-sm);
  font-size: 13px;
  font-weight: 500;
  text-align:left;
  transition: background .15s ease, color .15s ease;
}
/* .nav-item[hidden] — тот же класс бага, что уже решён у .field/.field-row-2/.field-row-3/
   .field-row-id-model/#section-econ-xfix-compact выше: авторский display:flex побеждает
   UA-правило [hidden]{display:none}. Первый раз, когда nav-item скрывается динамически —
   applyRolePermissionsUI (см. app.js) прячет "Пользователи"/"Настройки" для не-администратора. */
.nav-item[hidden]{ display:none; }
.nav-item:hover{ background: var(--sidebar-hover-bg); color: var(--sidebar-text-active); }
.nav-item.is-active{ background: var(--xfix-green); color:var(--on-accent); font-weight:700; }
.nav-item.is-active .nav-icon path,
.nav-item.is-active .nav-icon circle{ stroke: var(--on-accent); }
.nav-icon{ width:18px; height:18px; flex-shrink:0; fill:none; stroke:currentColor; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round; }
/* Пункты меню длиннее остальных ("Поступление м.Тайм" — самая длинная подпись + счётчик рядом)
   переносились на вторую строку — по прямому фидбэку владельца (скриншот сайдбара, "поработай с
   размером текста чтобы все в одну строчку помещалось и было не мелко и читабельно"). Снижен
   font-size (14.5px → 13px) и чуть подтянуты внутренние отступы/зазор (см. .nav-item выше) — этого
   с запасом хватает под самую длинную подпись, но на случай ещё более длинного текста в будущем
   (или другого рендеринга шрифта на устройстве владельца) подпись ещё и НЕ переносится принудительно:
   min-width:0 разрешает подписи сжаться внутри flex-строки, nowrap+ellipsis обрезают её многоточием
   вместо переноса на вторую строку — так пункт меню гарантированно остаётся в одну строку любой ценой. */
.nav-item span{ white-space:nowrap; overflow:hidden; text-overflow:ellipsis; min-width:0; }

/* Счётчик очереди "Комплектация" (см. updateKomplektaciaBadge в app.js) — заполненный кружок
   с числом, брендовый зелёный (--xfix-green, тот же, что и активная вкладка/кнопки-примари).
   margin-left:auto прижимает его к правому краю пункта меню (тот уже flex-контейнер).
   box-shadow-"ободок" в цвет фона сайдбара — иначе на АКТИВНОЙ вкладке (у неё тот же зелёный
   фон всей кнопки, см. .nav-item.is-active) кружок сливался бы с фоном без видимой границы. */
.nav-badge{
  margin-left:auto; flex-shrink:0;
  min-width:18px; height:18px; padding:0 5px;
  border-radius:9px;
  background: var(--xfix-green); color:var(--on-accent);
  font-size:11px; font-weight:800; line-height:18px; text-align:center;
  box-shadow: 0 0 0 2px var(--sidebar-bg);
}

.sidebar-footer{ margin-top:auto; padding-top: 16px; border-top: 1px solid var(--sidebar-border); display:flex; align-items:center; justify-content:space-between; gap:8px; flex-shrink:0; }
/* Владелец (2026-07-27, "переезжаем"): весь чип теперь кликабелен целиком — открывает модалку
   "Профиль" (#profile-overlay, см. .profile-modal ниже) вместо инлайновой всплывающей палитры
   аватарок и отдельной кнопки "Выйти", раньше живших прямо в сайдбаре. */
.user-chip{ display:flex; align-items:center; gap:10px; padding: 8px; min-width:0; border-radius:8px; cursor:pointer; }
.user-chip:hover{ background: var(--sidebar-hover-bg); }
.user-avatar-wrap{ position:relative; flex-shrink:0; }
.user-avatar{
  width:34px; height:34px; border-radius:50%;
  background: var(--xfix-green); color:var(--on-accent);
  display:flex; align-items:center; justify-content:center;
  font-weight:800; font-size:14px; flex-shrink:0;
}
.user-meta{ display:flex; flex-direction:column; line-height:1.25; min-width:0; }
.user-name{ color:var(--sidebar-text-active); font-size:13.5px; font-weight:600; }
.user-role{ font-size:11.5px; color:var(--sidebar-text-muted); }

.theme-toggle{
  flex-shrink:0;
  width:34px; height:34px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  background: var(--sidebar-toggle-bg);
  border: 1px solid var(--sidebar-border);
  color: var(--sidebar-text);
  cursor:pointer;
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.theme-toggle:hover{ background: var(--sidebar-toggle-hover-bg); color: var(--sidebar-toggle-hover-text); border-color: var(--sidebar-border); }
.theme-toggle:focus-visible{ outline: 2px solid var(--xfix-green); outline-offset:2px; }
.theme-icon{ width:18px; height:18px; fill:none; stroke:currentColor; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round; display:none; }
/* По умолчанию (светлая тема) — показываем луну (клик = включить тёмную) */
.theme-icon-moon{ display:block; }
/* В тёмной теме — показываем солнце (клик = включить светлую) */
[data-theme="dark"] .theme-icon-moon{ display:none; }
[data-theme="dark"] .theme-icon-sun{ display:block; }

/* ===================== Main ===================== */
/* position:relative — только чтобы дать точку отсчёта для #alisa-bg (position:absolute;inset:0,
   см. index.html/ниже в этом файле); на остальной вёрстке `.main` не сказывается никак (сама
   `.main` в обычном потоке не позиционировалась и не позиционируется, других абсолютных потомков
   у неё нет). */
/* z-index:0 (вместе с position:relative) — не просто "точка отсчёта", а полноценный новый
   stacking context для `.main`: без него отрицательный z-index у #alisa-bg (см. ниже — фон
   должен красться ПОД шапку/KPI/поиск/сами вкладки, а не поверх них) утёк бы на уровень выше
   `.main` и мог бы перекрыться с сайдбаром непредсказуемо. С z-index:0 всё, что внутри `.main`,
   гарантированно остаётся в её собственном слое. */
.main{ padding: 28px 36px 48px; min-width:0; position:relative; z-index:0; }

.topbar{
  display:flex; align-items:flex-start; gap:24px;
  margin-bottom: 28px; flex-wrap: wrap;
}
.topbar-heading{ min-width: 200px; margin-right: auto; }
.topbar-heading h1{ font-size: 26px; }
.topbar-heading p{ color: var(--text-muted); margin-top:4px; font-size: 14px; }

.kpis{ display:flex; gap:10px; flex-wrap:wrap; }
.kpi{
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 10px 16px;
  min-width: 118px;
  box-shadow: var(--shadow-sm);
}
.kpi .kpi-label{ font-size: 11.5px; color: var(--text-muted); text-transform:uppercase; letter-spacing:.04em; }
.kpi .kpi-value{ font-size: 19px; font-weight:800; margin-top:2px; }
/* Владелец, 2026-07-30: «и кстати чего у этого блока прозрачный фон?» — про карточку
   «Итоговый заработок» на вкладках «Алиса Дисконт». Причина: --accent-soft-bg в скине
   alisa задан полупрозрачным (rgba(91,54,232,.08) в светлой теме, rgba(138,92,255,.16) в
   тёмной), а у «Алисы» под контентом лежит декоративная картинка-фон — она и просвечивала
   сквозь единственную карточку, которая не использует непрозрачный var(--surface).
   Чиним композицией, а не заменой токена (токен нужен полупрозрачным в других местах):
   непрозрачная подложка var(--surface) + поверх неё тот же тон как background-image.
   Для непрозрачных значений токена (другие скины) это визуальный no-op. */
.kpi.kpi-accent{
  background-color: var(--surface);
  background-image: linear-gradient(var(--accent-soft-bg), var(--accent-soft-bg));
  border-color: var(--accent-soft-border);
}
.kpi.kpi-accent .kpi-value{ color: var(--accent-soft-text); }

/* Buttons */
.btn{
  display:inline-flex; align-items:center; gap:8px;
  border-radius: var(--radius-sm);
  padding: 11px 18px;
  font-size: 14px;
  font-weight: 600;
  border: 1px solid transparent;
  cursor:pointer;
  transition: transform .1s ease, background .15s ease, box-shadow .15s ease;
  white-space: nowrap;
}
.btn svg{ stroke:currentColor; fill:none; stroke-width:2; stroke-linecap:round; }
.btn[hidden]{ display:none; }
.btn:active{ transform: scale(.97); }
.btn-primary{ background: var(--btn-primary-bg); color: var(--btn-primary-text); }
.btn-primary:hover{ background: var(--btn-primary-bg-hover); }
.btn-ghost{ background: var(--surface); border-color: var(--border); color: var(--text); }
.btn-ghost:hover{ background: var(--surface-2); }
.btn-ghost-danger{ background: var(--surface); border-color: var(--danger-soft-border); color: var(--danger); }
.btn-ghost-danger:hover{ background: var(--danger-soft-bg); }
.btn-new-deal{ align-self:flex-start; }

/* ===================== Глобальный поиск ===================== */
/* Живёт вне .view-секций (сразу под .topbar), поэтому виден на любой вкладке —
   ищет по ВСЕМ сделкам сразу. Раньше на каждой вкладке был ещё и локальный текстовый поиск —
   убран по фидбэку владельца (глобального достаточно, дублирование только путало), см.
   "Известные CSS/JS-паттерны" в спеке. Сужение списка на вкладке теперь только через
   select-фильтры в .deals-toolbar (статус/устройство), не текстовый поиск. */
.global-search{ position:relative; margin-bottom:24px; }
.global-search-icon{
  position:absolute; left:14px; top:13px; stroke: var(--text-muted); fill:none; stroke-width:2; stroke-linecap:round; pointer-events:none;
}
/* textarea, а не input — обычный однострочный input у большинства браузеров при вставке
   многострочного текста схлопывает переносы строк в пробелы, а нам нужно сохранить каждую
   строку отдельным поисковым запросом (см. parseSearchTerms в app.js). rows=1 + resize:none +
   ограниченная max-height с overflow держат вид обычной строки поиска, пока текста немного. */
#global-search-input{
  width:100%; resize:none; overflow-y:auto; min-height:44px; max-height:120px;
  padding:11px 40px 11px 42px; border-radius: var(--radius-sm); border:1px solid var(--border);
  background: var(--surface); color:inherit; font-family:inherit; font-size:14px; line-height:1.4;
}
.global-search-clear{
  position:absolute; right:10px; top:10px; width:24px; height:24px; border:none; border-radius:6px;
  background:transparent; color: var(--text-muted); cursor:pointer; font-size:13px; line-height:1;
}
.global-search-clear:hover{ background: var(--surface-2); color: var(--text); }
/* Раньше результаты поиска рисовались отдельным блоком под строкой поиска — сначала
   всплывающей поверх страницы панелью (.gs-row, position:absolute), затем полноценной, но
   всё ещё ВТОРОЙ таблицей на странице. По фидбэку владельца ("не удобно... хотел бы чтобы
   динамически менялось то поле где под периодом") отдельный блок убран целиком — поиск
   подменяет содержимое уже существующей таблицы текущей вкладки, своей разметки/CSS не
   требует, см. renderListView() и обработчики #global-search-input в app.js. */

/* ===================== Deals table ===================== */
/* Локальный текстовый поиск на вкладках убран по фидбэку владельца (глобального поиска
   вверху страницы достаточно) — тулбар теперь из select-фильтров (статус, где применимо,
   + ЗАСЦ/НАСЦ и канал продажи на всех вкладках) и кнопки "Копировать ID". Фильтр по модели
   устройства был и убран по прямому фидбэку владельца ("бесполезный оказался") — см. changelog. */
.deals-toolbar{ display:flex; gap:10px; margin-bottom:16px; }
.deals-toolbar select{
  min-width:220px; padding:11px 34px 11px 14px; border-radius: var(--radius-sm); border:1px solid var(--border); background: var(--surface); color:inherit;
  appearance:none; -webkit-appearance:none; -moz-appearance:none;
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='%237b7b83' d='M7 10l5 5 5-5z'/></svg>");
  background-repeat:no-repeat; background-position: right 12px center; background-size:16px;
  cursor:pointer;
}
/* ИСПРАВЛЕНИЕ (владелец, 2026-07-28: "шапка не следует за прокруткой" — первая версия закреплённой
   шапки, полагавшаяся на скролл всей СТРАНИЦЫ, не работала). Причина: у .table-wrap уже был
   overflow-x:auto (нужен для горизонтального скролла таблицы на узких экранах) — а по правилам CSS
   Overflow пара overflow-x/overflow-y не может быть auto+visible одновременно: если один из них не
   visible, БРАУЗЕР ПРИНУДИТЕЛЬНО делает и другой тоже auto, что бы там ни было явно прописано
   (проверено эмпирически — даже overflow-y:visible в computed style показывал 'auto'). Из-за этого
   .table-wrap ВСЕГДА является скролл-контейнером и по вертикали тоже — а раз он им является, именно
   он, а не окно браузера, становится точкой отсчёта для position:sticky у шапки таблицы (см. ниже).
   Раньше высота .table-wrap была неограничена (росла вместе с содержимым) — внутреннего скролла
   реально не возникало, вместо этого скроллилась вся страница, а sticky-шапка молча "прилипала"
   к контейнеру, который сам никогда не скроллился, то есть визуально никуда не двигалась.
   Раз .table-wrap неизбежно является скролл-контейнером — вместо борьбы с этим он теперь ОСОЗНАННО
   сделан ограниченным по высоте (max-height) со своим собственным внутренним вертикальным скроллом:
   тулбар/фильтры над таблицей остаются на месте (они вне .table-wrap), скроллятся только строки
   внутри самой таблицы, а шапка колонок прилипает к верху ИМЕННО этой прокручиваемой области —
   тот же видимый эффект "шапка не теряется", которого просил владелец, просто скроллится сама
   таблица, а не вся страница целиком.
   max-height:65vh здесь — только ЗАПАСНОЙ вариант на случай, если JS почему-то не отработает:
   реальное значение выставляется в px через syncTableWrapHeight() (app.js, зовётся из render() при
   каждом переключении вкладки/изменении данных + при resize окна) — владелец (тем же вечером,
   2026-07-28): "смотри сколько неиспользуемого пространства снизу когда список позиций большой" —
   фиксированный процент от вьюпорта не учитывал, сколько места РЕАЛЬНО остаётся под тулбаром
   конкретной вкладки на конкретном экране, и обрезал список раньше времени. Инлайн-стиль,
   выставляемый JS, побеждает это CSS-правило в каскаде в обычной работе. */
.table-wrap{ background: var(--surface); border:1px solid var(--border); border-radius: var(--radius-lg); overflow-x:auto; overflow-y:auto; max-height:65vh; }
/* Ширины колонок ниже (см. nth-child) заданы в px, НЕ в % — первая правка (проценты от 1180px)
   оказалась неудачной: колонка "Статус" при коротких статусах ("Ремонт") выглядела с большим
   пустым отступом по бокам, а при длинных ("Поступление м.Тайм", "На согласование") — наоборот,
   переносилась на 2 строки, ID и суммы кое-где вплотную упирались в соседнюю колонку без запаса
   ("Дата продажи еле вмещается"). Владелец (2026-07-27): "проявИ дизайнерский вкус". Каждое
   значение ниже — не на глаз, а измерено фактическим рендером в headless Chromium (реальный шрифт/
   паддинги/border-radius пилюль статуса) под самый длинный реальный текст, который в этой колонке
   вообще может встретиться (все 10 значений STATUSES, ID из 6-7 цифр, суммы до 6 знаков, названия
   каналов/мест ремонта), плюс небольшой запас на разницу рендеринга шрифтов между ОС. min-width —
   просто сумма этих колонок, а не отдельно подобранное число. Итог держит все "тесные" по смыслу
   колонки (статус/ID/суммы/канал/даты) строго в одну строку без переноса — и, в отличие от версии
   с процентами, не "гуляет" при перерасчёте от общей ширины таблицы. Устройство клиента/на замену —
   единственные две колонки, где перенос на 2 строки по-прежнему нормален (это не жалоба владельца,
   там просто длинные названия моделей) — им оставлено больше воздуха, чем остальным. */
/* border-collapse:separate (+ border-spacing:0) вместо collapse — ЭТО и есть настоящая причина,
   почему закреплённая шапка изначально не прилипала (владелец, 2026-07-28, второй заход: "шапка
   не следует за прокруткой" — даже после исправления overflow-x/y у .table-wrap). Известное
   ограничение Chromium: position:sticky на <th>/<td> НЕ работает, пока у таблицы
   border-collapse:collapse — визуально нужный "слитный" вид границ ячеек при separate+spacing:0
   получается тот же самый (0-й border-spacing убирает щели между ячейками), а sticky при этом
   начинает реально работать. */
.deals-table{ width:100%; border-collapse: separate; border-spacing:0; font-size:13px; min-width:1450px; table-layout:fixed; }
.deals-table th{
  text-align:left; padding:10px 12px; background: var(--surface-2);
  font-size:11.5px; text-transform:uppercase; letter-spacing:.03em; color:var(--text-muted);
  border-bottom:1px solid var(--border);
  /* Заголовки разной длины переносятся в разное число строк (1 или 2) — все они лежат в одной
     <tr>, поэтому высота строки уже общая (по самому высокому заголовку), но без vertical-align
     однострочные заголовки центровались по середине этой высоты, а двухстрочные растягивались
     на всю — визуально "гуляли" на разных базовых линиях. bottom выравнивает низ текста
     заголовка одинаково у всех колонок независимо от числа строк. */
  vertical-align:bottom;
  /* "Закрепить шапку таблицы" (владелец, 2026-07-28 — скриншот шапки списка, "чтобы при
     прокрутке она не терялась") — position:sticky вместо простого relative, приклеивает всю
     строку заголовка к верху скролл-контейнера при прокрутке. Скролл-контейнер здесь —
     САМ .table-wrap (max-height + overflow-y:auto, см. комментарий там же — .table-wrap неизбежно
     становится скролл-контейнером из-за overflow-x:auto, поэтому высота сделана ограниченной
     осознанно, а не борется с этим). Заодно position:sticky остаётся той же точкой отсчёта для
     .col-resize-handle (см. ниже и initColumnResize в app.js), у которой position:absolute
     привязан к правому краю ИМЕННО этой ячейки — sticky точно так же устанавливает containing
     block для абсолютно спозиционированных потомков, как и раньше делал relative, так что ручка
     изменения ширины продолжает работать без изменений. background обязателен (уже был выше,
     var(--surface-2)) — иначе строки таблицы просвечивали бы сквозь приклеенную шапку при
     скролле. z-index — выше строк таблицы, ниже .col-resize-handle (6) и тем более модалки (100). */
  position:sticky;
  top:0;
  z-index:5;
}
/* Владелец (2026-07-27: "можешь сделать возможность изменять ширину столбиков на манер гугл
   таблицы?") — перетаскиваемая полоска на правой границе каждого заголовка (кроме ручки
   перетаскивания строк — удалена 2026-08-01). Ширина колонки меняется через inline
   th.style.width (см. initColumnResize в app.js) — table-layout:fixed берёт ширины колонок из
   ПЕРВОЙ строки (thead), поэтому inline-ширина одной этой ячейки управляет всей колонкой, не
   требуя <colgroup>. Сохраняется в localStorage по ключу data-resize-key — таблицы с одинаковой
   структурой колонок (см. атрибут в index.html) делят один и тот же набор сохранённых ширин,
   как и общие CSS-правила nth-child ниже. */
/* Зона захвата целиком внутри своей колонки (2026-08-01). Была `right:-4px; width:8px`, то есть
   половина ручки свешивалась за правый край заголовка — а там её перекрывал следующий заголовок
   (оба sticky, у следующего в DOM приоритет отрисовки), и попасть можно было только в 3-4px
   слева от границы. Отсюда и жалоба «не могу найти ползунок ширины между ID и ID»: он есть, но
   ловится с одного пикселя. Теперь ручка шире и вся лежит внутри колонки, а тонкая линия-подсказка
   (::after) по-прежнему рисуется ровно на границе. */
.col-resize-handle{
  position:absolute; top:0; right:0; bottom:0; width:11px; cursor:col-resize; z-index:6;
  touch-action:none;
}
.col-resize-handle::after{
  content:''; position:absolute; top:0; bottom:0; right:0; width:2px;
  background:transparent;
}
.col-resize-handle:hover::after, .col-resize-handle.is-dragging::after{ background: var(--xfix-green); }
/* Клик по заголовку колонки — сортировка (см. sortDeals/applySort в app.js). Три состояния
   по клику: нет сортировки (порядок как при перетаскивании) → по возрастанию → по убыванию →
   снова нет сортировки. Пока активна сортировка, перетаскивание строк недоступно — оба
   механизма меняют видимый порядок, включать их одновременно означало бы, что перетаскивание
   тут же "отменяется" следующим рендером сортировки, что запутывало бы пользователя. */
/* white-space:normal (не nowrap) — при табличных фиксированных % шириной колонок nowrap
   заставлял длинные подписи ("Заработок на Trade‑in" и т.п.) визуально вылезать поверх
   соседних заголовков вместо переноса на вторую строку. */
/* position:sticky (не relative) — НАЙДЕНО ПРИ ВЕРИФИКАЦИИ закреплённой шапки (владелец,
   2026-07-28, второй заход): это правило специфичнее базового ".deals-table th{position:sticky}"
   выше (класс + атрибут против одного класса) и побеждало его в каскаде — почти ВСЕ подписи
   колонок сортируемые (data-sort-key есть у всех, кроме ручки перетаскивания/ID клиента/ID XFIX),
   поэтому именно они, а не отдельные случайные ячейки, теряли sticky и визуально "уезжали" при
   скролле — шапка выглядела полностью пропавшей, хотя пара узких несортируемых колонок оставались
   на месте. position:sticky точно так же остаётся containing block для .sort-indicator
   (position:absolute, см. ниже), как раньше был relative — сам индикатор не пострадал. */
.deals-table th[data-sort-key]{ cursor:pointer; user-select:none; white-space:normal; line-height:1.3; position:sticky; top:0; padding-right:22px; }
.deals-table th[data-sort-key]:hover{ color: var(--text); }
/* Раньше .sort-indicator был обычным inline-block СРАЗУ ПОСЛЕ текста заголовка — в узких
   колонках (5-6% ширины) это создавало отдельный, невидимый (у него нет содержимого, пока
   сортировка не активна) перенос строки: сам span не помещался на строке с последним словом
   заголовка и переносился на СВОЮ отдельную (пустую) строку. vertical-align:bottom у th
   выравнивал по низу весь блок текста ВКЛЮЧАЯ эту невидимую строку — в узких колонках
   видимый текст оказывался на строку ВЫШЕ, чем в широких колонках (Статус/Устройство
   клиента/на замену), где span умещался на одной строке с текстом и невидимой строки не
   возникало. Итог — заголовки визуально "гуляли" по высоте на разных колонках, хотя
   vertical-align был выставлен одинаково у всех (баг был не в vertical-align, а в том, что
   ДЛЯ РАЗНЫХ колонок число строк текста в потоке отличалось из-за самого индикатора).
   Фикс — полностью убрать индикатор из потока текста через position:absolute, привязав его к
   фиксированному правому нижнему углу ячейки (th получил position:relative + padding-right).
   Теперь индикатор никогда не влияет на перенос строк заголовка ни в одной колонке. */
.sort-indicator{ position:absolute; right:6px; bottom:12px; width:10px; text-align:center; }
.deals-table th.sort-asc .sort-indicator::after{ content:'▲'; font-size:9px; color: var(--text); }
.deals-table th.sort-desc .sort-indicator::after{ content:'▼'; font-size:9px; color: var(--text); }
/* Владелец (2026-07-27, скриншот трёх таблиц): колонка, отсортированная ПО УМОЛЧАНИЮ ("Дата
   Trade-in" на Trade-IN/Складе XFIX, "Дата продажи" на Продажах XFIX/м.Тайм — см.
   LIST_VIEWS.*.defaultSort) визуально читалась как "жирная" на фоне остальных, приглушённых
   (var(--text-muted)) заголовков — раньше активная сортировка перекрашивала ВЕСЬ текст
   заголовка в var(--text) (полноценный, тёмный цвет), из-за контраста с соседями это и
   выглядело как жирный шрифт, хотя font-weight нигде не менялся. Теперь текст заголовка
   остаётся тем же приглушённым цветом независимо от сортировки — какая колонка активна,
   по-прежнему видно по стрелке ▲/▼ (она одна получает акцентный цвет, см. правила выше). */
/* "ID клиента" / "ID XFIX" — больше не сортируемые колонки, клик по заголовку копирует эту
   колонку в буфер обмена (см. copyColumnToClipboard в app.js) вместо сортировки; title
   подсказывает это при наведении. Тот же курсор/ховер, что у сортируемых заголовков, чтобы
   выглядело так же кликабельно, но без .sort-indicator (нечего индицировать) и без
   padding-right под стрелку. */
.deals-table th.th-copy{ cursor:pointer; user-select:none; white-space:normal; line-height:1.3; }
.deals-table th.th-copy:hover{ color: var(--text); }
.deals-table td{ padding:10px 12px; border-bottom:1px solid var(--border); vertical-align: middle; overflow-wrap:break-word; }
/* Явные ширины колонок (table-layout:fixed), в px (не в %) — см. большой комментарий у
   .deals-table выше про то, почему именно px и откуда взяты конкретные числа. С 2026-08-01
   нумерация начинается с колонки «Статус»: колонка-ручка перетаскивания строк убрана. */
.deals-table th:nth-child(1),  .deals-table td:nth-child(1) { width:160px; } /* Статус — хватает под самый длинный реальный ярлык ("Поступление м.Тайм"/"Выведено в работу") в одну строку */
.deals-table th:nth-child(2),  .deals-table td:nth-child(2) { width:75px; } /* ID клиента — 6-7 цифр в одну строку с запасом */
.deals-table th:nth-child(3),  .deals-table td:nth-child(3) { width:75px; } /* ID XFIX */
.deals-table th:nth-child(4),  .deals-table td:nth-child(4) { width:170px; } /* Устройство клиента — единственная колонка (вместе с 6), где перенос на 2 строки у длинных названий моделей нормален и ожидаем */
.deals-table th:nth-child(5),  .deals-table td:nth-child(5) { width:170px; } /* Устройство на замену */
.deals-table th:nth-child(6),  .deals-table td:nth-child(6) { width:105px; } /* ЗАСЦ / НАСЦ — под "Узбекистан"/"Мобисервис" в одну строку */
.deals-table th:nth-child(7),  .deals-table td:nth-child(7) { width:92px; } /* Стоимость для клиента — под суммы вроде "-100 000 ₽" в одну строку */
.deals-table th:nth-child(8),  .deals-table td:nth-child(8) { width:92px; } /* Заработок на Trade-in */
.deals-table th:nth-child(9), .deals-table td:nth-child(9){ width:92px; } /* Заработок с продажи */
.deals-table th:nth-child(10), .deals-table td:nth-child(10){ width:105px; } /* Канал продажи — под "Мобисервис" в одну строку */
.deals-table th:nth-child(11), .deals-table td:nth-child(11){ width:95px; } /* Итоговый заработок */
.deals-table th:nth-child(12), .deals-table td:nth-child(12){ width:95px; } /* Дата Trade-in — под "2026-07-24" в одну строку с запасом (раньше эта колонка реально переполнялась) */
.deals-table th:nth-child(13), .deals-table td:nth-child(13){ width:95px; } /* Дата продажи */
/* ID/суммы/даты/канал — короткие значения фиксированного формата, никогда не должны переноситься
   на 2 строки (это и была жалоба "6-значный ID не помещается в одну строчку") — в отличие от
   "Устройство клиента/на замену" (колонки 4,5, длинные названия моделей, перенос там нормален и
   ожидаем) и "Не хватает" у вкладок "Ошибки" (список через "; ", тоже нормально переносится).
   Общее правило по номеру колонки безопасно для ВСЕХ вариантов таблицы разом (.deals-table-mtime/
   -errors/-errors-mtime) — на этих позициях у каждого варианта тоже лежат короткие значения.
   Номера сдвинуты на один 2026-08-01 вместе с удалением колонки-ручки перетаскивания: не сдвинь
   их, nowrap попал бы на "Устройство клиента", и длинные названия моделей вылезали бы из своей
   ячейки поверх соседней (поймано на проверке). */
.deals-table td:nth-child(2),
.deals-table td:nth-child(3),
.deals-table td:nth-child(6),
.deals-table td:nth-child(7),
.deals-table td:nth-child(8),
.deals-table td:nth-child(9),
.deals-table td:nth-child(10),
.deals-table td:nth-child(11),
.deals-table td:nth-child(12),
.deals-table td:nth-child(13){ white-space:nowrap; }

/* "Ошибки XFIX"/"Ошибки м.Тайм" (.deals-table-errors) — те же 13 колонок, что и у обычных
   списков, ПЛЮС 15-я колонка "Не хватает" (см. dealRowHtml(..., errorsView) в app.js) — общие
   проценты выше (посчитанные под 14 колонок без неё) не оставляют места под неё, поэтому здесь,
   как и у .deals-table-mtime ниже, отдельный, более специфичный блок с урезанными пропорциями
   (Устройство клиента/на замену уменьшены с 18% до 14% — под текст в этих ячейках всё ещё
   достаточно места, зато находится 16% под список пропущенных полей). Сумма — 100%. */
.deals-table-errors{ min-width:1610px; } /* 15 колонок (+"Не хватает") — та же px-логика, что у .deals-table, устройство клиента/на замену чуть уже, чтобы хватило места под 15-ю колонку */
.deals-table-errors th:nth-child(1),  .deals-table-errors td:nth-child(1) { width:160px; } /* Статус */
.deals-table-errors th:nth-child(2),  .deals-table-errors td:nth-child(2) { width:75px; } /* ID клиента */
.deals-table-errors th:nth-child(3),  .deals-table-errors td:nth-child(3) { width:75px; } /* ID XFIX */
.deals-table-errors th:nth-child(4),  .deals-table-errors td:nth-child(4) { width:150px; } /* Устройство клиента */
.deals-table-errors th:nth-child(5),  .deals-table-errors td:nth-child(5) { width:150px; } /* Устройство на замену */
.deals-table-errors th:nth-child(6),  .deals-table-errors td:nth-child(6) { width:105px; } /* ЗАСЦ / НАСЦ */
.deals-table-errors th:nth-child(7),  .deals-table-errors td:nth-child(7) { width:92px; } /* Стоимость для клиента */
.deals-table-errors th:nth-child(8),  .deals-table-errors td:nth-child(8) { width:92px; } /* Заработок на Trade-in */
.deals-table-errors th:nth-child(9), .deals-table-errors td:nth-child(9){ width:92px; } /* Заработок с продажи */
.deals-table-errors th:nth-child(10), .deals-table-errors td:nth-child(10){ width:105px; } /* Канал продажи */
.deals-table-errors th:nth-child(11), .deals-table-errors td:nth-child(11){ width:95px; } /* Итоговый заработок */
.deals-table-errors th:nth-child(12), .deals-table-errors td:nth-child(12){ width:95px; } /* Дата Trade-in */
.deals-table-errors th:nth-child(13), .deals-table-errors td:nth-child(13){ width:95px; } /* Дата продажи */
.deals-table-errors th:nth-child(14), .deals-table-errors td:nth-child(14){ width:200px; } /* Не хватает — список полей через "; ", перенос на 2 строки тут нормален */

/* "Ошибки м.Тайм" (.deals-table-errors-mtime) — та же таблица, что и .deals-table-errors выше
   (errors_xfix), ПЛЮС колонка "Канал" (владелец, 2026-07-21: "отражать его новым столбиком после
   статуса во вкладках мТайм") — errors_xfix её не получает (см. showChannelBadge в
   renderListView, index.html). Доп. класс с бОльшим числом объявлений после базового
   .deals-table-errors — при равной специфичности (1 класс + nth-child) побеждает то, что идёт
   позже в файле, поэтому именно у errors_mtime (единственной таблицы с обоими классами разом)
   применяются эти проценты, а не .deals-table-errors выше; errors_xfix (без доп. класса)
   продолжает жить по ним, как и раньше. Взято по 2% у "Устройство клиента"/"на замену" и 2% у
   "Не хватает" — под новую 6%-колонку, сумма по-прежнему 100%. */
.deals-table-errors-mtime{ min-width:1600px; } /* 16 колонок (+"Канал"+"Не хватает") */
.deals-table-errors-mtime th:nth-child(1),  .deals-table-errors-mtime td:nth-child(1) { width:160px; } /* Статус */
.deals-table-errors-mtime th:nth-child(2),  .deals-table-errors-mtime td:nth-child(2) { width:75px; } /* Канал */
.deals-table-errors-mtime th:nth-child(3),  .deals-table-errors-mtime td:nth-child(3) { width:70px; } /* ID клиента */
.deals-table-errors-mtime th:nth-child(4),  .deals-table-errors-mtime td:nth-child(4) { width:70px; } /* ID XFIX */
.deals-table-errors-mtime th:nth-child(5),  .deals-table-errors-mtime td:nth-child(5) { width:135px; } /* Устройство клиента */
.deals-table-errors-mtime th:nth-child(6),  .deals-table-errors-mtime td:nth-child(6) { width:135px; } /* Устройство на замену */
.deals-table-errors-mtime th:nth-child(7),  .deals-table-errors-mtime td:nth-child(7) { width:100px; } /* ЗАСЦ / НАСЦ */
.deals-table-errors-mtime th:nth-child(8),  .deals-table-errors-mtime td:nth-child(8) { width:88px; } /* Стоимость для клиента */
.deals-table-errors-mtime th:nth-child(9), .deals-table-errors-mtime td:nth-child(9){ width:88px; } /* Заработок на Trade-in */
.deals-table-errors-mtime th:nth-child(10), .deals-table-errors-mtime td:nth-child(10){ width:88px; } /* Заработок с продажи */
.deals-table-errors-mtime th:nth-child(11), .deals-table-errors-mtime td:nth-child(11){ width:100px; } /* Канал продажи */
.deals-table-errors-mtime th:nth-child(12), .deals-table-errors-mtime td:nth-child(12){ width:90px; } /* Итоговый заработок */
.deals-table-errors-mtime th:nth-child(13), .deals-table-errors-mtime td:nth-child(13){ width:90px; } /* Дата Trade-in */
.deals-table-errors-mtime th:nth-child(14), .deals-table-errors-mtime td:nth-child(14){ width:90px; } /* Дата продажи */
.deals-table-errors-mtime th:nth-child(15), .deals-table-errors-mtime td:nth-child(15){ width:190px; } /* Не хватает */

/* Список пропущенных полей ("ID клиента; Дата замены (Trade-in)") — тем же тревожным цветом,
   что и отрицательные суммы (.money-red выше), чтобы сразу бросалось в глаза, что чинить. */
.issues-cell{ color: var(--danger); font-size:12.5px; line-height:1.4; }

/* Владелец (2026-07-27): "крась сумму красным если она отрицательная" — какая именно колонка
   заработка в строке им обёрнута в этот класс, решает LIST_VIEWS.redIfNegativeColumn (см.
   dealRowHtml в app.js), не жирным — просто цветом, независимо от .deals-table th[boldColumn]
   выше. */
.money-negative{ color: var(--danger); }

/* "Склад м.Тайм"/"Продажи м.Тайм" (.deals-table-mtime) — свой набор из 9 колонок (не 13, как
   у остальных таблиц), поэтому проценты выше (посчитанные под старую раскладку) тут не подходят
   вообще: "Устройство" утаскивало себе только 5% (ширину бывшей колонки "ID XFIX" по позиции) —
   отсюда перенос на 2 строки, а "Вид ремонта"/следующая колонка утаскивали по 18% (ширины бывших
   "Устройство клиента"/"на замену") — отсюда пустой разрыв перед "Оценкой выкупа" и остальными
   суммами. Отдельный блок с более специфичным селектором (2 класса) переопределяет проценты
   именно для этой раскладки. Сумма — 100% (без учёта колонки-ручки, как и выше). */
/* +1 колонка "Канал" (см. dealChannelBadgeHtml в app.js, владелец 2026-07-21) сдвинула все
   позиции на одну — проценты пересчитаны заново (взято по 4% у "Устройство"/"Вид ремонта",
   отдано новой колонке), сумма по-прежнему 100%. */
/* Своя min-width, px, не % (см. большой комментарий у .deals-table выше) — 11 колонок у этой
   таблицы совсем другие по смыслу и по содержимому, чем у основной (14 колонок), поэтому и цифры
   свои, а не унаследованные. "Статус" — тот же 160px, что у основной таблицы: именно здесь
   встречается самый длинный ярлык ("Поступление м.Тайм") — вкладка "Поступление м.Тайм" целиком
   состоит из строк с этим статусом, поэтому колонка обязана держать его в одну строку. */
/* 12 колонок с 2026-08-01: после «ID м.Тайм» добавлен «ID Склада» (mtimeStockId) — во всех трёх
   списках м.Тайм сразу, поэтому раскладка снова одна на всех и отдельного блока под «Склад» нет. */
.deals-table-mtime{ min-width:1250px; }
.deals-table-mtime th:nth-child(1),  .deals-table-mtime td:nth-child(1) { width:160px; } /* Статус */
.deals-table-mtime th:nth-child(2),  .deals-table-mtime td:nth-child(2) { width:75px; } /* Канал */
.deals-table-mtime th:nth-child(3),  .deals-table-mtime td:nth-child(3) { width:78px; } /* ID м.Тайм */
.deals-table-mtime th:nth-child(4),  .deals-table-mtime td:nth-child(4) { width:90px; } /* ID Склада */
.deals-table-mtime th:nth-child(5),  .deals-table-mtime td:nth-child(5) { width:180px; } /* Устройство — длинные названия (цвет/объём памяти) по-прежнему в одну строку в большинстве случаев */
.deals-table-mtime th:nth-child(6),  .deals-table-mtime td:nth-child(6) { width:150px; } /* Вид ремонта — под "Материнская плата"/"Микрофон основной" в одну строку */
.deals-table-mtime th:nth-child(7),  .deals-table-mtime td:nth-child(7) { width:92px; } /* Оценка выкупа */
.deals-table-mtime th:nth-child(8),  .deals-table-mtime td:nth-child(8) { width:92px; } /* Цена продажи */
.deals-table-mtime th:nth-child(9), .deals-table-mtime td:nth-child(9){ width:95px; } /* Заработок м.Тайм */
.deals-table-mtime th:nth-child(10), .deals-table-mtime td:nth-child(10){ width:95px; } /* Дата передачи в м.Тайм */
.deals-table-mtime th:nth-child(11), .deals-table-mtime td:nth-child(11){ width:95px; } /* Дата продажи */

.deals-table tr:last-child td{ border-bottom:none; }
.deals-table tr{ cursor:pointer; }
.deals-table tr:hover td{ background: var(--surface-2); }
/* white-space:normal (не nowrap) — у части статусов теперь длинные ярлыки ("Использовано
   для Trade-in"), при фиксированной ширине колонки такой текст должен переноситься на вторую
   строку внутри пилюли, а не вылезать поверх соседней ячейки. */
.status-pill{ display:inline-block; font-size:11px; font-weight:700; padding:4px 10px; border-radius:14px; white-space:normal; line-height:1.3; text-align:center; }
.status-offer{ background: var(--status-offer-bg); color: var(--status-offer-text); }
.status-repair{ background: var(--status-repair-bg); color: var(--status-repair-text); }
.status-stock{ background: var(--status-stock-bg); color: var(--status-stock-text); }
.status-incoming_mtime{ background: var(--status-incoming_mtime-bg); color: var(--status-incoming_mtime-text); }
.status-stock_mtime{ background: var(--status-stock_mtime-bg); color: var(--status-stock_mtime-text); }
.status-sold{ background: var(--status-sold-bg); color: var(--status-sold-text); }
.status-traded_in{ background: var(--status-traded_in-bg); color: var(--status-traded_in-text); }
.status-disassembled{ background: var(--status-disassembled-bg); color: var(--status-disassembled-text); }
.status-in_use{ background: var(--status-in_use-bg); color: var(--status-in_use-text); }
.status-broken{ background: var(--status-broken-bg); color: var(--status-broken-text); }
.status-done{ background:#111; color:#fff; }
.status-draft{ background: var(--status-draft-bg); color: var(--status-draft-text); }
.row-empty td{ text-align:center; padding: 40px; color: var(--text-muted); }

/* Строка-заголовок группы техники (2026-08-01). Липнет к верху при прокрутке под шапкой таблицы,
   чтобы при длинном списке было видно, в какой группе находишься. Не кликабельна и не
   подсвечивается при наведении — это разделитель, а не строка сделки. */
.deals-table tr.row-group td{
  position: sticky; top: 0; z-index: 1;
  background: var(--surface-2);
  padding: 9px 14px;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}
.deals-table tr.row-group:hover td{ background: var(--surface-2); }
.row-group-name{
  font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--text-muted);
}
/* Перетаскивание групп (2026-08-01): ручка слева, подсветка цели и «поднятая» строка. */
.row-group-grip{
  display:inline-block; margin-right:9px; letter-spacing:-2px;
  color: var(--text-muted); opacity:.45; font-size:12px; vertical-align:1px;
  cursor: grab; padding:0 2px;
}
.row-group-grip:active{ cursor: grabbing; }
.deals-table tr.row-group.is-movable:hover .row-group-grip{ opacity:.9; }
.deals-table tr.row-group.is-dragging td{ opacity:.5; }
.deals-table tr.row-group.is-drop-target td{
  box-shadow: inset 0 2px 0 0 var(--accent, #6c5ce7);
}
.row-group-count{
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 20px; height: 18px; padding: 0 6px; margin-left: 9px;
  border-radius: 999px; background: var(--surface); border: 1px solid var(--border-soft);
  font-size: 10.5px; font-weight: 700; color: var(--text-muted);
}

/* Ярлык канала происхождения сделки — "XFIX" (пришла через обычный процесс XFIX, потом
   переехала на склад м.Тайм) или "м.Тайм" (заведена напрямую, минуя XFIX, см. dealChannel в
   app.js). Владелец: "по цветам синий для мтайм и зеленый для xfix". Тот же паттерн пилюли,
   что и .status-pill/.kit-pill выше, отдельный класс — это не статус сделки и не очередь
   склада, а третье, независимое измерение. */
.channel-badge{ display:inline-block; font-size:11px; font-weight:700; padding:4px 10px; border-radius:14px; white-space:nowrap; }
.channel-badge-xfix{ background: #d7f4e3; color: #0f7a3d; }
.channel-badge-mtime{ background: #dbeafe; color: #1d4ed8; }
[data-theme="dark"] .channel-badge-xfix{ background: rgba(34,197,94,.18); color: #4ade80; }
[data-theme="dark"] .channel-badge-mtime{ background: rgba(59,130,246,.2); color: #60a5fa; }
/* В модалке (см. #modal-channel-badge/.modal-title-row в index.html) — та же пилюля; отступ от
   заголовка теперь даёт `gap` родительского .modal-title-row, а не собственный margin. */
#modal-channel-badge{ vertical-align: middle; }

/* Очередь "Комплектация" (см. KITTING_STATUS_LABELS в app.js) — те же пилюли, что и .status-pill
   в списках, отдельный класс во избежание пересечения с обычными статусами сделки (разные
   пространства значений — kittingStatus не входит в STATUSES). Цвета сознательно переиспользуют
   уже существующие переменные состояний: "Разукомплектовать" красится как "Ремонт" (устройство
   только что туда ушло), "Укомплектовать" — как "Склад XFIX" (пункт назначения), "Укомплектовано"
   — как "Продано" (готово, действие не требуется) — не заводим новых цветов ради ещё одного
   набора переменных light/dark. */
.kit-pill{ display:inline-block; font-size:11px; font-weight:700; padding:4px 10px; border-radius:14px; white-space:normal; line-height:1.3; text-align:center; }
.kit-need_dekit{ background: var(--status-repair-bg); color: var(--status-repair-text); }
.kit-need_kit{ background: var(--status-stock-bg); color: var(--status-stock-text); }
.kit-done{ background: var(--status-sold-bg); color: var(--status-sold-text); }

/* Перетаскивание строк списка (смена порядка отображения, не этапа сделки) */
/* Стили колонки-ручки перетаскивания строк удалены 2026-08-01 вместе с самой колонкой —
   порядок в списках задают группа и дата, вручную переставляются только группы. */
.deals-table tr.is-dragging{ opacity:.4; }
.deals-table tr.drag-over-top td{ box-shadow: inset 0 2px 0 var(--xfix-green); }
.deals-table tr.drag-over-bottom td{ box-shadow: inset 0 -2px 0 var(--xfix-green); }

/* ===================== Dashboard ===================== */
/* #dash-grid — вертикальный стек рядов: полоса периода, два ряда по 5 карточек-цифр,
   затем ряды по 2 «рамки» (карточки с гистограммами). Каждый ряд — свой мини-grid
   (.dash-row-5 / .dash-row-2), а не один общий grid с span'ами — 5 не делится ровно
   на "2 колонки на рамку", поэтому проще держать ряды отдельными сетками. */
.dash-grid{ display:flex; flex-direction:column; gap:16px; }
.dash-row-5{ display:grid; grid-template-columns: repeat(5, 1fr); gap:16px; }
.dash-row-2{ display:grid; grid-template-columns: repeat(2, 1fr); gap:16px; }
/* "Заморожено в ремонте/на складе" (см. app.js renderDashboard) специально помечены тем же
   классом .dash-row-5, что и ряд с "рамками" заработка ниже — по фидбэку владельца карточки
   должны быть ТАКОЙ ЖЕ ширины, как рамки, а не растянуты на половину ряда (как было бы с
   .dash-row-2). При 5-колоночной сетке и всего 2 карточках grid сам оставляет 3 колонки
   пустыми — ширина карточки автоматически совпадает с шириной рамок без лишнего кода/класса. */
/* Разбор / Выведено в работу — счётчики и суммы, специально внизу дашборда (см. .dash-grid
   ниже — ряды идут в порядке разметки сверху вниз): по фидбэку владельца это менее нужная
   информация, чем продажи/заработок выше, поэтому не должна отвлекать в верхних рядах. */
.dash-row-4{ display:grid; grid-template-columns: repeat(4, 1fr); gap:16px; }
.dash-card{
  background: var(--surface); border:1px solid var(--border); border-radius: var(--radius-lg);
  padding: 20px; box-shadow: var(--shadow-sm);
}
.dash-card h3{ font-size:13px; color:var(--text-muted); text-transform:uppercase; letter-spacing:.04em; font-weight:700; }
/* Шапка карточки-разбивки: заголовок слева, крестик "скрыть блок" справа (владелец,
   2026-07-30: "сделай возможным полностью скрывать 4 нижних блока по отдельности").
   Крестик почти невидим, пока курсор не над карточкой, — иначе четыре крестика подряд
   перетягивали бы на себя внимание сильнее, чем сами цифры. */
.dash-card-head{ display:flex; align-items:flex-start; justify-content:space-between; gap:10px; }
.dash-card-hide{ flex:none; background:none; border:none; padding:0 0 0 6px; cursor:pointer;
  font-size:14px; line-height:1; color:var(--text-muted); opacity:0; transition:opacity .12s, color .12s; }
.dash-card:hover .dash-card-hide,
.dash-card-hide:focus-visible{ opacity:.65; }
.dash-card-hide:hover{ opacity:1; color:var(--danger); }
/* Строка возврата скрытых блоков — появляется только когда что-то скрыто. */
.dash-hidden-bar{ display:flex; align-items:center; flex-wrap:wrap; gap:8px; }
.dash-hidden-bar-label{ font-size:12px; font-weight:700; text-transform:uppercase;
  letter-spacing:.04em; color:var(--text-muted); }
.dash-hidden-chip{ display:inline-flex; align-items:center; gap:7px; padding:6px 12px;
  border:1px dashed var(--border); border-radius:999px; background:var(--surface);
  font-family:inherit; font-size:12.5px; font-weight:600; color:var(--text-muted); cursor:pointer; }
.dash-hidden-chip:hover{ color:var(--text); border-color:var(--accent-soft-border);
  background:var(--accent-soft-bg); }
.dash-hidden-chip-all{ border-style:solid; }
.dash-card .big-number{ font-size:30px; font-weight:800; margin-top:8px; }
.dash-card .big-number.accent{ color: var(--accent-soft-text); }
/* "Заработок на Trade‑in"/"Заработок на продажах" раньше были выделены рамкой/фоном
   (.dash-card-highlight, фирменный зелёный) — по фидбэку владельца рамка убрана, карточки
   оформлены как обычные (как "Итоговый заработок"), класс .dash-card-highlight больше нигде
   не используется. Взамен — раскраска цифры по знаку/порогу (.money-yellow/.money-red ниже),
   применяется и к "Заработок на Trade‑in"/"Заработок на продажах" (отрицательное — красным),
   и к "Заморожено итого" (3 порога по сумме) — см. renderDashboard() в app.js. */
.dash-card .big-number.money-yellow{ color: var(--warning-text); }
.dash-card .big-number.money-red{ color: var(--danger); }
.bar-row{ display:flex; align-items:center; gap:10px; margin-top:12px; font-size:13px; }
/* Раньше .bar-label была фиксированной ширины (120px) — длинные значения (в первую очередь
   названия клиентов, см. "Клиенты" в renderDashboard()) переносились на вторую строку и ломали
   выравнивание. По прямому запросу владельца ("можешь поработать с пропорциями зеленых полосок
   чтобы имена клиентов в одну строчку помещались?") ширина метки теперь по содержимому
   (flex:0 1 auto + white-space:nowrap — никогда не переносится на новую строку), а полоска
   (.bar-track) занимает всё оставшееся место и уступает метке ровно столько, сколько ей нужно;
   min-width на треке — чтобы у совсем длинных значений полоска не схлопывалась в ноль совсем. */
.bar-label{ flex:0 1 auto; white-space:nowrap; color:var(--text-muted); }
.bar-track{ flex:1 1 40px; min-width:40px; height:8px; background: var(--surface-2); border-radius:6px; overflow:hidden; }
/* Полоски-счётчики (штуки по местам ремонта / клиентам / каналам / итогам сделки) — графит, а
   не фирменный зелёный (владелец, 2026-07-30: "не перегибай с зелёным"). Четыре карточки
   сплошных зелёных полосок делали акцент фоном; теперь зелёный на дашборде остался только у
   заработанных денег, и снова читается как сигнал. См. --chart-ink-soft в :root. */
.bar-fill{ height:100%; background: var(--chart-ink-soft); border-radius:6px; }
.bar-value{ width:60px; text-align:right; font-weight:700; flex-shrink:0; }
/* "Показать ещё"/"Скрыть" у длинных списков-счётчиков (см. barRowsCollapsible в
   renderDashboard()) — по прямому запросу владельца показывать по умолчанию 8 строк, остальное
   сворачивать. Оформлена как обычная текстовая ссылка (без рамки/фона), фирменный зелёный текст,
   тот же приём, что и .big-number.accent — чтобы явно читалась как интерактивный элемент, а не
   ещё одна строка списка. */
.bar-toggle{ display:block; margin-top:12px; background:none; border:none; padding:0; color:var(--accent-soft-text); font-size:13px; font-weight:600; cursor:pointer; }
.bar-toggle:hover{ text-decoration:underline; }
/* Разделитель между двумя фиксированными группами строк в карточке "Итоги сделки" (см.
   renderDashboard() в app.js, barGroupDivider) — та же чёрточка-приём, что .nav-divider в левом
   сайдбаре (по прямому запросу владельца), адаптированная под светлую карточку дашборда вместо
   тёмного сайдбара (--border вместо --sidebar-divider). */
.bar-group-divider{ height:1px; background: var(--border); margin:14px 0; }

/* График "Продано и на складе по месяцам" на Dashboard м.Тайм (см. renderDashboardMtime в
   app.js) — простые вертикальные столбики без сторонней библиотеки (весь проект — plain HTML/
   CSS/JS, без сборки), тем же принципом, что и .bar-fill (ширина/высота в % от максимума среди
   всех значений на графике). Занимает всю ширину карточки (карточка — прямой ребёнок .dash-grid,
   flex-колонки, поэтому сама растягивается на 100% без .dash-row-* обёртки). */
.mtime-chart-legend{ display:flex; gap:18px; font-size:12px; color:var(--text-muted); margin:6px 0 4px; }
.mtime-chart-legend-item{ display:flex; align-items:center; gap:6px; }
.mtime-chart-swatch{ width:10px; height:10px; border-radius:2px; display:inline-block; }
.mtime-chart-swatch-sold{ background: var(--xfix-green); }
.mtime-chart-swatch-stock{ background: var(--chart-ink-soft); }
.mtime-chart{ display:flex; align-items:flex-end; gap:6px; height:220px; padding-top:10px; }
.mtime-chart-month{ flex:1 1 0; min-width:0; display:flex; flex-direction:column; align-items:center; height:100%; }
.mtime-chart-bars{ flex:1 1 auto; width:100%; display:flex; align-items:flex-end; justify-content:center; gap:3px; }
.mtime-chart-bar{ width:40%; max-width:22px; border-radius:3px 3px 0 0; min-height:2px; }
.mtime-chart-bar-sold{ background: var(--xfix-green); }
.mtime-chart-bar-stock{ background: var(--chart-ink-soft); opacity:.7; }
.mtime-chart-month-label{ font-size:10.5px; color:var(--text-muted); margin-top:6px; white-space:nowrap; }

/* ---- График "Закупка новой техники по месяцам" (Dashboard XFIX, см. renderBuySpendXfix) ----
   Отдельный блок, а не переиспользование .mtime-chart выше: там два столбика штук на месяц без
   подписей значений, здесь — один денежный столбик с числом над ним, пунктирная линия среднего
   и столбик-прогноз. Общего кода почти не было бы, а имя .mtime-* на дашборде XFIX сбивало бы
   с толку.
   Одна серия — легенды нет намеренно (её роль выполняет заголовок карточки); прогнозный столбик
   отличается не цветом, а заливкой в полоску и пунктирной обводкой, поэтому и в чёрно-белой
   печати, и при дальтонизме он остаётся отличимым от фактических месяцев. */
.spend-chart-note{ font-size:12.5px; color:var(--text-muted); line-height:1.45; margin:2px 0 12px; }
/* margin-top появился, когда сверху убрали серый пояснительный абзац (владелец, 2026-07-30) —
   иначе цифры "Итого/В среднем/Прогноз" прилипали прямо к заголовку карточки. */
.spend-chart-totals{ display:flex; flex-wrap:wrap; gap:26px; margin:14px 0 6px; }
.spend-chart-total h4{ font-size:11.5px; font-weight:700; letter-spacing:.03em; text-transform:uppercase;
  color:var(--text-muted); margin:0 0 2px; }
.spend-chart-total-value{ font-size:20px; font-weight:800; }
/* Прогноз — оценка, а не факт: и цифра приглушена, и столбик на графике нарисован полоской с
   пунктирным контуром. Одно и то же различие продублировано цветом и фактурой намеренно —
   в чёрно-белой печати и при дальтонизме фактура остаётся. */
.spend-chart-total-forecast .spend-chart-total-value{ color:var(--text-muted); }
/* Высоты столбиков считаются не от 100%, а от 86% высоты поля (см. BAR_AREA в app.js) —
   оставшиеся 14% занимает число над самым высоким столбиком, иначе оно вылезало бы за карточку. */
.spend-chart-plot{ position:relative; display:flex; align-items:flex-end; gap:8px; height:230px; }
.spend-chart-col{ flex:1 1 0; min-width:0; display:flex; flex-direction:column; justify-content:flex-end;
  height:100%; }
.spend-chart-val{ font-size:11px; font-weight:700; text-align:center; margin-bottom:5px; white-space:nowrap; }
.spend-chart-bar{ background:var(--chart-spend); border-radius:4px 4px 0 0; min-height:2px; }
/* Прогноз: тот же цвет, но заливка в полоску + пунктирный контур — «это не факт, а оценка».
   Полоска нарисована градиентом, а не картинкой, чтобы работала в обеих темах от одного токена. */
.spend-chart-col-forecast .spend-chart-bar{
  background:repeating-linear-gradient(135deg,
    color-mix(in srgb, var(--chart-spend) 34%, transparent) 0 5px,
    transparent 5px 10px);
  border:1.5px dashed var(--chart-spend); border-bottom:none; border-radius:4px 4px 0 0;
}
.spend-chart-col-forecast .spend-chart-val{ color:var(--text-muted); }
/* Линия среднего — поверх столбиков. Подпись у ЛЕВОГО края: справа линия упирается в столбик
   прогноза, высота которого равна среднему по построению, и подпись ложилась ровно на его число
   (проверено на скриншоте — читались оба сразу). Непрозрачный фон подписи обязателен: слева она
   всё равно может попасть на столбик, если ранний месяц окажется выше среднего. */
.spend-chart-avg{ position:absolute; left:0; right:0; border-top:1.5px dashed var(--text-muted);
  opacity:.75; pointer-events:none; }
.spend-chart-avg span{ position:absolute; left:0; bottom:3px; font-size:10.5px; font-weight:700;
  color:var(--text-muted); background:var(--surface); padding:0 4px; white-space:nowrap; }
.spend-chart-labels{ display:flex; gap:8px; margin-top:7px; }
.spend-chart-label{ flex:1 1 0; min-width:0; text-align:center; font-size:10.5px; color:var(--text-muted);
  white-space:nowrap; }
/* Текущий месяц ещё не закончился — его столбик и подпись приглушены, и в среднее он не входит
   (см. renderBuySpendXfix): иначе неполный месяц занижал бы и среднее, и прогноз. */
.spend-chart-col-partial .spend-chart-bar{ opacity:.5; }
.spend-chart-label-partial{ font-style:italic; }
@media (max-width:1100px){
  .spend-chart-val{ font-size:10px; }
  .spend-chart-label{ font-size:9.5px; }
}

/* Блок "Активность за период" вверху дашборда — кнопка "Весь период" + диапазон дат "с"/"до" */

/* margin-bottom:16px — изначально было 0, из-за чего первый ряд карточек (или тулбар/таблица на
   вкладках-списках) визуально "лежал" вплотную под кнопкой/датами. У второго периода
   (`.dash-period-bar-secondary` ниже) это же самое поправили раньше отдельным отступом ("че оно
   лежит на нижнем блоке?"), после чего по прямому запросу владельца ("тут сделай точно также")
   такой же отступ снизу добавлен и главному #period-bar — раньше НЕ трогали специально (был
   отдельный явный запрос "период не ехал ... он идеально стоит"), но теперь стало ясно, что
   0 снизу и там смотрелось так же тесно, просто это заметили не сразу. */
.dash-period-bar{
  display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:10px;
  background:none; border:none; box-shadow:none; padding:0 2px; margin-bottom:16px;
}
/* Второй "Период" — только для карточек-разбивок дашборда (ЗАСЦ/НАСЦ, По статусу, По каналу
   продажи, Куда ушло устройство, Разобрано, Выведено в работу), см. #period-bar-2-xfix/-mtime
   в index.html. Тот же отступ 16px сверху и снизу, что и у главного #period-bar выше (у него
   16px снизу через margin-bottom в базовом классе, здесь дополнительно нужен ещё и margin-top —
   этот блок не идёт сразу после `.global-search`, как главный, а стоит между двумя рядами
   карточек дашборда). */
/* justify-content:flex-end — подписи "Штуки" больше нет (владелец, 2026-07-30: "убери слова
   деньги и штуки"), и без неё space-between в .dash-period-bar прижимал бы единственного
   оставшегося ребёнка влево. Второй период выравниваем ВПРАВО, чтобы он стоял ровно под
   верхним, который на дашборде уехал в правый край .view-topbar. */
.dash-period-bar-secondary{ margin:16px 0; justify-content:flex-end; }
.dash-period-buttons{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; }

/* Одна строка сверху вкладки: слева таб-бар "Продажи | Дашборд" и фильтры, справа общий период
   (владелец, 2026-07-30: "в одну строчку на место убранного период … должно быть в одну
   строчку право и лево"). Сам #period-bar сюда ПЕРЕНОСИТ switchView — см. [data-period-slot]
   в index.html. Внутренние margin-bottom дочерних блоков гасим: отступ снизу теперь один,
   у самой строки. */
.view-topbar{ display:flex; align-items:center; flex-wrap:wrap; gap:10px 12px; margin-bottom:16px; }
.view-topbar > .price-guide-tabs,
.view-topbar > .deals-toolbar,
.view-topbar > .dash-period-bar{ margin-bottom:0; }
.view-topbar > .dash-period-bar{ margin-left:auto; padding-right:0; }
/* Селекты фильтров в этой строке уже, чем обычные 220px, — иначе таб-бар + три фильтра +
   период не помещаются в одну строку на ~1500px. Замер на 1500x950 ("Продажи XFIX", самая
   плотная из четырёх вкладок): доступно 1164px, занято 197 (табы) + 12 + 470 (фильтры) + 12 +
   448 (период) = 1139. При min-width 158px не помещалось — строка ломалась пополам. Текст
   плейсхолдеров владелец специально сократил ("Все места ремонта" → "Места ремонта") как раз
   под такую ширину. Ниже ~1400px строка переносится, и это нормально: перенос флексом
   аккуратный, период уходит на вторую строку целиком. */
.view-topbar .deals-toolbar select{ min-width:150px; }
.period-btn, .period-btn-2{
  padding:7px 14px; border-radius: var(--radius-sm); border:1px solid var(--border);
  background: var(--surface); color: var(--text-muted); font-size:13px; font-weight:600;
  cursor:pointer; transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.period-btn:hover, .period-btn-2:hover{ background: var(--surface-2); color: var(--text); }
.period-btn.is-active, .period-btn-2.is-active{ background: var(--xfix-green); border-color: var(--xfix-green); color:var(--on-accent); }
.dash-period-range{ display:flex; align-items:center; gap:12px; flex-wrap:wrap; }
.dash-period-range label{ display:flex; align-items:center; gap:6px; font-size:13px; font-weight:600; color: var(--text-muted); }
.dash-date-input{
  padding:6px 10px; border-radius: var(--radius-sm); border:1px solid var(--border);
  background: var(--input-bg); color: var(--text); font-size:13px;
}

/* ===================== Settings ===================== */
.settings-card{
  background: var(--surface); border:1px solid var(--border); border-radius: var(--radius-lg);
  padding:22px; margin-bottom:16px; max-width: 640px;
}
.settings-card h2{ font-size:16px; margin-bottom:8px; }
.settings-card p{ margin-bottom:14px; font-size:13.5px; line-height:1.5; }
.settings-row{ display:flex; align-items:center; justify-content:space-between; gap:16px; padding:10px 0; border-top:1px solid var(--border); }
.settings-row:first-of-type{ border-top:none; }

/* Редактор групп техники (2026-08-01): номер приоритета, название, ключевые слова и кнопки
   порядка/удаления в одну строку. Поле ключевых слов резиновое — их обычно больше, чем помещается
   в фиксированную ширину .settings-row input. */
.device-groups{ display:grid; gap:8px; margin-top:6px; }
.device-group-row{ display:flex; align-items:center; gap:8px; }
.device-group-order{
  flex:none; width:22px; height:22px; border-radius:50%;
  display:inline-flex; align-items:center; justify-content:center;
  background: var(--surface-2); border:1px solid var(--border-soft);
  font-size:11px; font-weight:700; color: var(--text-muted);
}
.device-group-row input{
  padding:8px 12px; border-radius:8px; border:1px solid var(--border);
  background: var(--input-bg); color: var(--text); font-size:13px; min-width:0;
}
.device-group-row .js-dg-name{ width:190px; flex:none; font-weight:600; }
.device-group-row .js-dg-kw{ flex:1; }
.device-group-row .btn{ padding:7px 11px; font-size:13px; flex:none; }
.device-group-row .btn[disabled]{ opacity:.35; cursor:default; }
.settings-actions{ display:flex; gap:8px; margin-top:12px; flex-wrap:wrap; }
.settings-row label{ font-size:13.5px; font-weight:600; }
.settings-row input{ padding:8px 12px; border-radius:8px; border:1px solid var(--border); width:200px; text-align:right; background: var(--input-bg); color: var(--text); }
.settings-row input:disabled{ background: var(--surface-2); color: var(--text-muted); opacity:1; }
.settings-card-danger{ border-color: var(--danger-soft-border); }

/* ===================== Оценка стоимости ===================== */
/* Категория-переключатель — та же визуальная логика, что у .period-btn (пилюли, активная —
   залита зелёным), но отдельный класс: смысл разный (категория устройства, а не диапазон дат),
   и незачем тянуть за собой .period-btn-специфичные соседские правила. */
/* Владелец (2026-07-27): раньше это была статичная серая заглушка ("Справочник цен —
   редактируется прямо здесь..."), теперь показывает дату последнего "Обновления цен" активной
   вкладки (см. updatePriceGuideNote в app.js) — тот же размер/начертание, что и раньше
   (небольшая подпись под вкладками), но цвет управляется явно: чёрный (обычный текст) по
   умолчанию, красный — если этой дате месяц и больше. */
/* Владелец, 2026-07-30, заход первый: "Текст об изменение цены сейчас не плохо читабельнй".
   Тогда строчка стала контрастной плашкой с рамкой 1px и красным вариантом для просроченных дат.
   Владелец, 2026-07-30, заход второй: "Когда я сказал о том что информация о последней смене
   цвета не читабельна ты сделал только хуже когда обвел в рамку. Давай текст всегда будет чёрный
   и сделаем это сообщение на манер как сейчас в калькуляторе в конце окно сообщающие что на
   колонку не задана цена". То есть за образец берётся .calc-hint (см. ниже в блоке "Алисы"):
   мягкая подложка var(--surface-2), ПУНКТИРНАЯ рамка вместо сплошной, радиус 10, 12px.
   Заход третий (владелец, 2026-07-30): "Меня прям бесит красный пунктир. Оставляй везде в
   рамке, измени обрамление на обычное как у всех, а текст внутри рамки пусть будет как ранее
   оговаривали стандартный или красный если с момента корректировки прошёл месяц". То есть
   ровно наоборот к заходу второму: рамка теперь ОБЫЧНАЯ — сплошная 1px var(--border), как у
   любого другого блока приложения, и она НИКОГДА не красная; сигнал "цены давно не обновляли"
   ушёл обратно в цвет текста (var(--danger) при просрочке ≥ PRICE_GUIDE_STALE_DAYS).
   Прежний довод "красный мелкий текст поверх фона Алисы нечитаем" снят непрозрачной подложкой
   var(--surface-2) ниже — она появилась как раз в заходе втором и осталась.
   Подложка у обоих вариантов одна и та же — var(--surface-2), и она обязана быть НЕПРОЗРАЧНОЙ.
   Первая попытка красила просроченный вариант в var(--danger-soft-bg), а это rgba с альфой ~0.09:
   на вкладках "Алисы" сквозь него просвечивал декоративный фон с дугами и точками, и строчка
   снова становилась нечитаемой — ровно та беда, из-за которой всё и затевалось.
   inline-flex, а не block, чтобы плашка была по ширине текста, а не во всю колонку. */
.price-guide-note{
  display:inline-flex; align-items:center; gap:8px;
  margin: 0 0 16px; padding: 12px 14px;
  font-size:12px; font-weight:500; line-height:1.4;
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 10px;
}
/* hidden на inline-flex сам по себе не сработал бы — display перебивает атрибут. */
.price-guide-note[hidden]{ display:none; }
/* Просрочено (≥ месяца с последней правки цен): краснеет ТОЛЬКО текст, рамка остаётся общей —
   см. комментарий к заходу третьему выше. */
.price-guide-note-stale{
  color: var(--danger);
}
.price-guide-tabs{ display:flex; gap:8px; flex-wrap:wrap; margin-bottom:16px; }
.price-guide-tab{
  padding:8px 16px; border-radius: var(--radius-sm); border:1px solid var(--border);
  background: var(--surface); color: var(--text-muted); font-size:13.5px; font-weight:600;
  cursor:pointer; transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.price-guide-tab:hover{ background: var(--surface-2); color: var(--text); }
.price-guide-tab.is-active{ background: var(--xfix-green); border-color: var(--xfix-green); color:var(--on-accent); }
/* Крестик удаления — ТОЛЬКО у пользовательских вкладок (data-custom, см. renderPriceGuideTabs в
   app.js), встроенные 6 вкладок в разметке его вообще не содержат. */
.price-guide-tab-delete{
  margin-left:8px; opacity:.55; font-size:11px;
}
.price-guide-tab-delete:hover{ opacity:1; }
/* "+ Добавить вкладку" (владелец, 2026-07-27: "добавь возможность создавать не только новые
   модели но и вкладки") — визуально более лёгкая, пунктирная пилюля, чтобы явно читаться как
   действие "создать", а не ещё одна категория. */
.price-guide-tab-add{
  padding:8px 14px; border-radius: var(--radius-sm); border:1px dashed var(--border);
  background:transparent; color: var(--text-muted); font-size:15px; font-weight:700;
  cursor:pointer; line-height:1;
}
.price-guide-tab-add:hover{ background: var(--surface-2); color: var(--text); border-style:solid; }

/* Своя, более простая таблица — НЕ .deals-table: у той table-layout:fixed с процентными
   ширинами, жёстко подобранными под конкретные 13-14 колонок списка сделок (см. комментарий
   там же) — здесь колонок меньше и они разные у каждой категории, авто-раскладка подходит
   лучше. Общий .table-wrap (фон/рамка/скругление) переиспользуется как есть.
   По правке владельца ("название столбцов красиво в 2 строчки и соответственно сместим все
   столбцы влево но пропорционально... то что справа освободится место пусть будет, может в
   перспективе дополним чем то") — таблица больше НЕ растягивается на всю ширину контейнера
   (убран width:100%): table-layout:fixed + явная ширина у каждой колонки даёт табличке
   компактную "естественную" ширину — сумма ширин колонок меньше ширины контейнера, поэтому
   справа образуется свободное место (зарезервировано на будущее), а не пустая резина между
   немногочисленными столбцами. Заголовки переносятся на 2 строки при такой ширине сами —
   white-space:normal вместо nowrap, vertical-align:bottom выравнивает разное число строк по
   низу (тот же приём, что у .deals-table th). */
/* Панель вокруг таблицы цен обжимается по её ширине, а не по ширине экрана. Сама таблица давно
   width:auto (колонки фиксированной ширины, см. ниже), но общий .table-wrap — блок во всю
   доступную ширину, и до 2026-07-30 это не бросалось в глаза: у всех вкладок было по шесть-восемь
   колонок. Теперь во вкладке можно оставить две (Модель + РРЦ), и рамка на весь экран вокруг
   таблицы на треть экрана выглядит как незагрузившаяся страница. max-width:100% сохраняет
   горизонтальный скролл из .table-wrap для широких вкладок (iPhone, MacBook). */
.price-guide-table-wrap{ width: fit-content; max-width:100%; }
.price-guide-table{ width:auto; table-layout:fixed; border-collapse: collapse; font-size:13.5px; }
.price-guide-table th{
  text-align:left; padding:10px 12px; background: var(--surface-2);
  font-size:11.5px; text-transform:uppercase; letter-spacing:.03em; color:var(--text-muted);
  border-bottom:1px solid var(--border); white-space:normal; line-height:1.3; vertical-align:bottom;
  /* position:relative — точка отсчёта для .col-resize-handle (владелец, 2026-07-27: "а почему во
     вкладке оценки стоимости такого нет?" — тот же перетаскиваемый компонент, что и у таблиц
     сделок, см. .col-resize-handle выше и attachColumnResizeHandles в app.js). Эта таблица не
     использовала общий механизм раньше просто потому, что она отдельный класс/компонент
     (.price-guide-table, не .deals-table) с thead, который вдобавок перестраивается заново при
     каждом renderPriceGuide (переключение категории/добавление вкладки) — поэтому хэндлы
     навешиваются в конце renderPriceGuide, а не один раз при загрузке страницы. */
  position:relative;
}
/* Каждая \n-часть заголовка (см. renderPriceGuide) — свой блок с white-space:nowrap, поэтому
   ровно 2 строки гарантированы структурно (сам браузер не может перенести часть внутри себя),
   а не шириной колонки — ширина шрифта у владельца может отличаться от песочницы, где эта правка
   проверялась ("Wix Madefor Text" не грузится в Playwright-песочнице, пиксельно не сверить 1:1). */
.price-guide-table th .price-guide-th-line{ display:block; white-space:nowrap; }
.price-guide-table td{ padding:6px 10px; border-bottom:1px solid var(--border); vertical-align: middle; }
.price-guide-table input{
  width:100%; padding:7px 9px; border-radius:8px; border:1px solid transparent;
  background: transparent; color: var(--text); font-size:13.5px; font-family: inherit;
  transition: background .15s ease, border-color .15s ease;
}
/* Первая колонка — всегда "Модель" на любой категории (см. PRICE_GUIDE_CATEGORIES) — у iPhone
   содержит модель+память, объединённые в одну строку (по более ранней правке владельца), значит
   текст заметно длиннее, чем в остальных колонках, поэтому её явная ширина больше. Остальные
   колонки (кроме крайней справа — там кнопка удаления) — одна и та же ширина независимо от того,
   сколько их у конкретной категории: с table-layout:fixed это и даёт "пропорционально" из правки
   владельца — все узкие колонки одинаковой ширины, а не резиновые под контейнер. */
/* 260px, не 220px — самые длинные строки после объединения модели с памятью у iPhone это варианты
   вида "iPhone 17 Pro Max (esim) 256 ГБ" (с "(esim)"/"(sim)" в скобках), они не помещались и
   обрезались при более узкой колонке (видно на скриншоте до отправки). */
.price-guide-table th:first-child, .price-guide-table td:first-child{ width:260px; }
/* MacBook: строка "Модель" включает полную комплектацию (RAM/SSD/чип) — заметно длиннее 260px
   (владелец, 2026-07-21: "в макбуках ты не полностью перенес название там в таблице прям
   комплектация была подписана" — данные не терялись, просто обрезались визуально). Класс
   переключается в renderPriceGuide() через cfg.wideModel — см. PRICE_GUIDE_CATEGORIES. */
.price-guide-table--wide-model th:first-child, .price-guide-table--wide-model td:first-child{ width:420px; }
.price-guide-table th:not(:first-child):not(.price-guide-th-actions),
.price-guide-table td:not(:first-child):not(.price-guide-td-actions){ width:180px; }
.price-guide-table input:hover{ background: var(--surface-2); }
.price-guide-table input:focus{ background: var(--input-bg); border-color: var(--border); outline:none; }
.price-guide-th-actions, .price-guide-td-actions{ width:40px; text-align:center; }
.price-guide-row-delete{
  background:none; border:none; cursor:pointer; color: var(--text-muted); font-size:13px;
  padding:6px; border-radius:6px; transition: background .15s ease, color .15s ease;
}
.price-guide-row-delete:hover{ background: var(--danger-soft-bg); color: var(--danger); }
.price-guide-empty{ padding:24px 16px; text-align:center; color: var(--text-muted); }

/* Импорт сделок — предпросмотр перед подтверждением */
#import-preview{ margin-top:14px; padding-top:14px; border-top:1px solid var(--border); }
#import-preview-summary{ margin-bottom:10px; }
#import-preview-warnings{ font-size:13px; color: var(--text-muted); margin-bottom:10px; }
#import-preview-warnings ul{ margin:6px 0 0 18px; padding:0; }
.import-preview-actions{ display:flex; gap:10px; }

/* ===================== Modal ===================== */
/* Выпадающий список текущего этапа — можно менять статус вручную в любую сторону
   (например, вернуть сделку со склада в ремонт). Цвет фона берётся из тех же
   .status-draft/.status-offer/... классов, что и у .status-pill в списках. */
.status-badge-select{
  padding: 10px 12px; border-radius: var(--radius-sm); border:1px solid var(--border);
  font-size:13.5px; font-weight:700; line-height:1.3; cursor:pointer;
  background-color: var(--surface-2);
}
.modal-overlay{
  position:fixed; inset:0; background: rgba(10,10,12,.55);
  display:flex; align-items:center; justify-content:center; z-index:100;
  padding: 10px;
  backdrop-filter: blur(2px);
}
.modal-overlay[hidden]{ display:none; }
.modal{
  background: var(--surface);
  width:min(1180px, 100%);
  max-height: 98vh;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  display:flex; flex-direction:column;
  overflow:hidden;
}
.modal-header{ display:flex; align-items:center; justify-content:space-between; padding: 8px 18px; border-bottom:1px solid var(--border); }
.modal-header h2{ font-size:16px; }
/* Заголовок + ярлык канала сгруппированы вместе (см. #modal-channel-badge/index.html) — бейдж
   вплотную к тексту заголовка, а не отдельным элементом, растянутым к крестику через
   justify-content:space-between родителя. */
.modal-title-row{ display:flex; align-items:center; gap:10px; }
.modal-close{ border:none; background: var(--surface-2); width:26px; height:26px; border-radius:50%; cursor:pointer; font-size:13px; color:var(--text-muted); }
.modal-close:hover{ background: var(--border); color:var(--text); }
.modal-body{ padding: 6px 18px; overflow-y:auto; }
.modal-footer{ display:flex; align-items:center; justify-content:space-between; padding: 6px 18px; border-top:1px solid var(--border); background: var(--surface-2); }
.modal-footer-right{ display:flex; gap:10px; margin-left:auto; }

/* ---------------- Онлайн-режим: логин / журнал / пользователи ----------------
   Владелец, 2026-07-21: "как сделать онлайн" + "нужны пользователи разные чтобы
   ограничить доступ и в целом права дать или видеть кто что делал". */
.login-modal{ max-width:380px; margin: 10% auto 0; }
.login-form{ display:flex; flex-direction:column; gap:12px; padding: 4px 0 14px; }
.login-form .btn{ margin-top:4px; }
.login-error{ color: var(--danger); font-size:13px; margin:0; }

/* Модалка "Профиль" (владелец, 2026-07-27: "переезжаем" — аватар/имя/пароль/выход одним местом,
   вместо попапа-палитры и отдельной кнопки "Выйти" прямо в сайдбаре). Компактная, по образцу
   .login-modal, с тремя блоками друг под другом: аватар / смена имени / смена пароля, и кнопкой
   выхода в подвале модалки (.modal-footer уже стилизован выше для карточки сделки). */
.profile-modal{ max-width:420px; margin: 6% auto 0; }
.profile-section-label{ font-size:11.5px; font-weight:600; color:var(--field-label-color); display:block; margin-bottom:6px; }
.avatar-picker-grid{
  display:grid; grid-template-columns: repeat(8, 1fr); gap:5px; margin-bottom:16px;
}
.avatar-option{
  width:32px; height:32px; border-radius:8px; border:1px solid transparent;
  background:transparent; font-size:17px; cursor:pointer;
  display:flex; align-items:center; justify-content:center;
}
.avatar-option:hover{ background: var(--surface-2); border-color: var(--border); }
.avatar-option.is-selected{ background: var(--surface-2); border-color: var(--xfix-green); }
.profile-form{ display:flex; flex-direction:column; gap:10px; padding: 10px 0; border-top:1px solid var(--border); }
.profile-form .btn{ align-self:flex-start; margin-top:2px; }
.profile-form-error{ color: var(--danger); font-size:12.5px; margin:0; }
.profile-form-success{ color: var(--xfix-green); font-size:12.5px; margin:0; }

/* Модалка "Новая вкладка" в "Оценке устройств" (владелец, 2026-07-30: "при создании новой вкладки
   нужна выборка: Память/Вариант, РРЦ, Опт — есть/нету"). Ширина как у профиля: в окне всего одно
   поле и три строки-галочки, растягивать его до ширины карточки сделки не на чем.
   Галочки в проекте до этого нигде не встречались (все переключатели были кнопками-чипами), поэтому
   правила ниже — новые, а не копия чего-то существующего. Ключевое в них: галочка не уезжает вниз
   при переносе подсказки (align-items:flex-start + margin-top на самом input), а название и
   подсказка живут в одной колонке грида — так подсказка выравнивается по названию, а не по
   квадратику. */
.pg-tab-modal{ max-width:440px; margin: 6% auto 0; }
/* Заголовок "Колонки" отделён от поля с названием: у .field нет нижнего отступа, и без этого
   подпись прилипала к рамке поля вплотную, читаясь как его подсказка, а не как заголовок
   следующего блока. */
.pg-tab-modal .field-block{ margin-top:15px; }
.pg-tab-col{
  display:grid; grid-template-columns: auto 1fr; align-items:flex-start; column-gap:9px;
  padding: 7px 8px; border-radius:8px; cursor:pointer;
  border:1px solid transparent; transition: background .14s ease, border-color .14s ease;
}
.pg-tab-col:hover{ background: var(--surface-2); border-color: var(--border); }
.pg-tab-col input[type="checkbox"]{
  grid-row: 1 / 3; width:15px; height:15px; margin:1px 0 0; cursor:pointer;
  accent-color: var(--xfix-green);
}
.pg-tab-col-name{ font-size:13.5px; font-weight:600; color: var(--text); }
.pg-tab-col-hint{ font-size:11.5px; color: var(--text-muted); line-height:1.35; }
.pg-tab-note{ font-size:11.5px; color: var(--text-muted); margin: 10px 0 12px; }
.pg-tab-error{ color: var(--danger); font-size:12.5px; margin: 2px 0 0; }
.pg-tab-error[hidden]{ display:none; }

/* Модалка "Права доступа" (владелец, 2026-08-02: "а можем создать инструмент в котором я как
   админ буду создавать пользователей и назначать им права?"). Пять групп галочек в две колонки —
   в один столбец список из 21 пункта не помещался на экран без прокрутки, а решение "кому что
   открыть" удобнее принимать, видя всё сразу. На узком экране колонка одна. */
.perms-modal{ max-width:720px; margin: 5% auto 0; }
.perms-modal .modal-body{ max-height: 62vh; overflow-y:auto; }
.perms-subject{ font-size:12.5px; color: var(--text-muted); margin-left:10px; }
.perms-note{ font-size:12px; color: var(--text-muted); margin: 0 0 12px; line-height:1.4; }
#perms-body{ display:grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 6px 22px; align-items:start; }
#perms-body .perms-note{ grid-column: 1 / -1; }
@media (max-width: 720px){ #perms-body{ grid-template-columns: 1fr; } }
.perms-group{ padding: 6px 0 10px; }
.perms-group-head{
  display:flex; align-items:baseline; justify-content:space-between; gap:8px;
  border-bottom:1px solid var(--border); padding-bottom:5px; margin-bottom:5px;
}
.perms-group-title{ font-size:11.5px; font-weight:700; letter-spacing:.03em; text-transform:uppercase; color: var(--text-muted); }
.perms-group-toggle{
  border:none; background:transparent; padding:0; cursor:pointer;
  font-size:11px; color: var(--text-muted); text-decoration: underline dotted;
}
.perms-group-toggle:hover:not(:disabled){ color: var(--text); }
.perms-group-toggle:disabled{ opacity:.4; cursor:default; }
.perms-item{
  display:flex; align-items:center; gap:9px; padding: 5px 8px; border-radius:8px; cursor:pointer;
  font-size:13px; color: var(--text);
  border:1px solid transparent; transition: background .14s ease, border-color .14s ease;
}
.perms-item:hover{ background: var(--surface-2); border-color: var(--border); }
.perms-item input[type="checkbox"]{ width:15px; height:15px; margin:0; cursor:pointer; accent-color: var(--xfix-green); flex:none; }
.perms-item input:disabled{ cursor:default; }
.perms-error{ color: var(--danger); font-size:12.5px; margin:0; align-self:center; }
.perms-error[hidden]{ display:none; }
/* Кнопка в колонке "Доступ" списка пользователей — не выглядит как основное действие строки,
   но кликабельна и показывает текущее состояние прав. */
.user-perms-btn{ font-size:12.5px; padding:4px 10px; white-space:nowrap; }
/* Подпись вместо кнопки "Сохранить" в карточке сделки, когда прав на изменение нет. */
.deal-readonly-note{ font-size:12px; color: var(--text-muted); margin:0; align-self:center; }
.deal-readonly-note[hidden]{ display:none; }

.audit-log-filters{ display:flex; align-items:flex-end; gap:14px; flex-wrap:wrap; margin-bottom:16px; }
.audit-log-filters .field{ min-width:160px; }
.audit-log-filters input, .audit-log-filters select{
  padding:8px 12px; border-radius:8px; border:1px solid var(--border); background: var(--input-bg); color: var(--text);
}

/* Общая простая таблица для "Журнала"/"Пользователей" — в отличие от .deals-table у неё нет
   фиксированных nth-child ширин под конкретные колонки списка сделок (там их 11 в строго
   определённом порядке), здесь набор колонок другой и с обеих сторон произвольный. */
.data-table{ width:100%; border-collapse: collapse; font-size:13.5px; }
.data-table th{ text-align:left; padding:8px 12px; border-bottom:2px solid var(--border); color: var(--text-muted); font-weight:600; white-space:nowrap; }
.data-table td{ padding:10px 12px; border-bottom:1px solid var(--border); vertical-align: middle; }
.data-table select{ padding:4px 8px; border-radius:6px; border:1px solid var(--border); background: var(--input-bg); color: var(--text); }
.user-table-avatar{ font-size:16px; text-align:center; width:1%; }

.deal-history-list{ list-style:none; margin:0; padding:0; max-height:220px; overflow-y:auto; display:flex; flex-direction:column; gap:6px; }
.deal-history-list li{ font-size:12.5px; padding:6px 8px; border-radius:6px; background: var(--surface-2); display:flex; gap:8px; flex-wrap:wrap; }
.deal-history-ts{ color: var(--text-muted); white-space:nowrap; }
.deal-history-user{ font-weight:700; }
.deal-history-text{ color: var(--text); }

.form-grid{ display:grid; grid-template-columns: 1fr 1fr; gap:8px; }
.form-section{
  border:1px solid var(--border); border-radius: var(--radius-md);
  padding: 4px 10px 5px; min-width:0;
}
.form-section-wide{ grid-column: 1 / -1; }
.form-section legend{ font-size:11.5px; font-weight:800; text-transform:uppercase; letter-spacing:.04em; color: var(--text-muted); padding: 0 6px; }

/* "Экономика XFIX" (компакт, рядом с "Устройство XFIX" на треке м.Тайм) — fieldset растянут
   grid'ом form-grid (align-items по умолчанию — stretch) до высоты соседней колонки
   ("Устройство XFIX"), но его СОДЕРЖИМОЕ по умолчанию короче и оставляет пустое место снизу
   внутри той же рамки. #econ-xfix-compact-comment занимает именно это оставшееся место —
   не увеличивает саму рамку (та и так уже растянута grid'ом), просто дотягивается до её низа. */
#section-econ-xfix-compact{ display:flex; flex-direction:column; }
/* Найдено при добавлении "Поступление м.Тайм" (см. STATUSES/"incoming_mtime" в app.js) — эти два
   ID-селектора задают display:flex НАПРЯМУЮ, и это ПЕРЕБИВАЕТ встроенное браузерное правило
   `[hidden]{ display:none }` (у ID-селектора выше специфичность, порядок в каскаде тут не важен) —
   то есть applyWarehouseTrackUI мог честно проставить el.hidden=true, а элемент всё равно
   оставался видимым (пустая рамка "Экономика XFIX" на обычных XFIX-сделках, дублирующаяся
   "Предлагаемое устройство" на треке м.Тайм — воспроизведено на скриншоте новой карточки
   "Поступление м.Тайм", но баг был И РАНЬШЕ, на уже существующих "Склад м.Тайм"/"Продажи м.Тайм",
   просто без скриншот-теста визуально не был замечен). Явный [hidden]-оверрайд по тому же
   паттерну, что уже используется в этом файле у .field/.result-chip/.field-row-3 и т.п. */
#section-econ-xfix-compact[hidden]{ display:none; }
.econ-xfix-compact-body{ display:flex; flex-direction:column; flex:1 1 auto; min-height:0; }
.econ-xfix-compact-comment{ flex:1 1 auto; display:flex; flex-direction:column; margin-top:4px; min-height:0; }
.econ-xfix-compact-comment textarea{ flex:1 1 auto; resize:none; min-height:32px; }

/* "Предлагаемое устройство" (обычный трек XFIX) — тот же класс .econ-xfix-compact-comment на
   узле "Комментарий" (это один и тот же DOM-узел, что и в компактной "Экономике XFIX" выше — см.
   moveEconNode/applyWarehouseTrackUI в app.js), но fieldset тут раньше НЕ был flex-колонкой,
   поэтому textarea не дотягивалась до низа рамки, растянутой form-grid'ом до высоты соседней
   колонки ("Устройство клиента"/"Устройство XFIX"), и снизу оставалось пустое место (по фидбэку
   владельца — "можно заполнить все пространство полем как это сделано в разделе мТайм"). Делаем
   fieldset такой же flex-колонкой — комментарий (уже flex:1 1 auto) сам дотягивается до низа. */
#section-replacement-device{ display:flex; flex-direction:column; }
/* См. комментарий у #section-econ-xfix-compact[hidden] выше — тот же самый класс бага. */
#section-replacement-device[hidden]{ display:none; }

/* "Экономика м.Тайм" — 3 колонки x 2 строки (Оценка выкупа/Маржа/Премия продавца, затем
   Цена продажи/НДС+комиссия/Заработок м.Тайм). ID м.Тайм/ФИО раньше были 4-й (левой) колонкой
   этой же сетки — по прямой просьбе владельца ("раздели на 2 отдельные рамки") вынесены в
   отдельный fieldset "Устройство м.Тайм" (см. index.html), поэтому сетка сузилась до 3 колонок. */
.mtime-econ-grid{ display:grid; grid-template-columns: repeat(3, 1fr); gap:8px; }

.field{ margin-bottom: 3px; display:flex; flex-direction:column; gap:2px; }
.field[hidden]{ display:none; }
.field label{ font-size:11.5px; font-weight:600; color:var(--field-label-color); }
.field input, .field select, .field textarea{
  padding: 7px 10px; border-radius: var(--radius-sm); border:1px solid var(--border);
  background: var(--input-bg); font-size:12.5px; width:100%; line-height:1.3; font-family:inherit;
}
.field select{
  appearance:none; -webkit-appearance:none; -moz-appearance:none;
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='%237b7b83' d='M7 10l5 5 5-5z'/></svg>");
  background-repeat:no-repeat; background-position: right 8px center; background-size:16px;
  padding-right:28px;
}
.field textarea{ min-height:32px; resize:vertical; }
.field input:focus, .field select:focus, .field textarea:focus{
  outline:none; border-color: var(--xfix-green); background: var(--input-focus-bg);
  box-shadow: 0 0 0 3px rgba(57,187,59,.15);
}
.field input:invalid:not(:placeholder-shown){ border-color: var(--border); }
/* Ошибка валидации (после blockAdvance) должна выглядеть красной — даже если поле в фокусе,
   она не должна перекрываться обычной зелёной подсветкой фокуса. */
.field input:focus:invalid, .field input.is-invalid,
.field select:focus:invalid, .field select.is-invalid,
.field textarea:focus:invalid, .field textarea.is-invalid{
  border-color: var(--danger); background: var(--input-focus-bg);
  box-shadow: 0 0 0 3px rgba(229,72,77,.15);
}
.field-row{ display:grid; grid-template-columns: repeat(4, 1fr); gap:8px; margin-bottom:3px; }
.field-row-2{ display:grid; grid-template-columns: repeat(2, 1fr); gap:8px; margin-bottom:2px; }
/* row-mtime-identity-top (см. index.html/applyWarehouseTrackUI в app.js) — тот же класс бага,
   что уже решён у .field-row-id-model ниже: без явного [hidden]-оверрайда браузер отдаёт приоритет
   авторскому display:grid над UA-стилем [hidden]{display:none}, и элемент остаётся видимым
   несмотря на атрибут hidden. */
.field-row-2[hidden]{ display:none; }
.field-row-3{ display:grid; grid-template-columns: repeat(3, 1fr); gap:8px; margin-bottom:3px; }
.field-row-3[hidden]{ display:none; }
.field-row-id-model{ display:grid; grid-template-columns: 110px 1fr; gap:8px; margin-bottom:2px; }
/* row-client-identity (см. index.html/applyWarehouseTrackUI в app.js) — первый раз, когда этот
   класс скрывается динамически (`.hidden = ...`); без явного [hidden]-оверрайда браузер отдаёт
   приоритет автору (эта строка) над UA-стилем `[hidden]{display:none}`, и элемент остаётся
   видимым несмотря на атрибут hidden — тот же класс бага, что уже решён у .field/.field-row-3
   выше и у #section-econ-xfix-compact ниже. */
.field-row-id-model[hidden]{ display:none; }
.field-stack{ display:flex; flex-direction:column; gap:3px; }
.field-third{ max-width: 33%; }
.status-row{ display:flex; flex-wrap:wrap; gap:8px; }
.status-row > .field{ flex: 1 1 150px; }
.status-row .field select, .status-row .field input{
  padding: 8px 11px; font-size:13px; box-sizing:border-box; min-height:34px; line-height:1.3;
}
.status-row .field select{ padding-right:30px; }

.field-block{ margin-bottom:2px; }
.field-block[hidden]{ display:none; }

/* "Фактическая оценка (м.Тайм)" — см. hint-mtime-actual-estimate в index.html/app.js */
.field-hint-actual-estimate{ font-size:12.5px; color:var(--text-muted); margin:-4px 0 10px; }
.field-hint-actual-estimate strong{ color: var(--text); font-weight:700; }
.field-hint-actual-estimate[hidden]{ display:none; }

/* Фото устройства — заготовка под будущую подгрузку из FileMaker (см. app.js renderPhotosSection) */
.photos-hint{ font-size:12px; color:var(--text-muted); margin-bottom:8px; line-height:1.4; }
.photos-columns{ display:grid; grid-template-columns: 1fr 1fr; gap:14px; }
.photos-stage{ border:1px solid var(--border); border-radius: var(--radius-md); padding:10px 12px; }
.photos-heading{ font-size:12px; font-weight:700; color:var(--text-muted); margin-bottom:8px; text-transform:uppercase; letter-spacing:.03em; }
.photos-grid{
  display:flex; flex-wrap:wrap; gap:8px; align-items:center; justify-content:center;
  min-height:64px; border:1px dashed var(--border); border-radius: var(--radius-sm); padding:10px;
}
.photos-grid.is-empty{ background: var(--surface-2); }
.photos-empty{ font-size:12px; color:var(--text-muted); text-align:center; }
.photo-thumb{ width:64px; height:64px; object-fit:cover; border-radius: var(--radius-sm); border:1px solid var(--border); }

/* Строка "добавить свой вариант" под select'ами с data-custom-key. */
.add-option-row{ display:flex; gap:6px; margin-top:3px; }
.add-option-row[hidden]{ display:none; }
.add-option-row input{
  flex:1; min-width:0; padding:6px 9px; border-radius: var(--radius-sm);
  border:1px solid var(--border); font-size:12px; font-family:inherit; background: var(--input-bg);
}
.add-option-row input:focus{ outline:none; border-color: var(--xfix-green); background: var(--input-focus-bg); }
.add-option-row button{
  padding:6px 11px; border-radius: var(--radius-sm); border:1px solid var(--xfix-green);
  background: var(--xfix-green); color:#fff; font-size:11.5px; font-weight:700;
  cursor:pointer; white-space:nowrap;
}
.add-option-row button:hover{ background: var(--xfix-green-dark); border-color: var(--xfix-green-dark); }
/* Select с data-custom-key + крестик удаления самостоятельно добавленного варианта
   сразу справа от него (виден только когда выбрано именно кастомное значение). */
.select-with-remove{ display:flex; gap:5px; align-items:stretch; }
.select-with-remove select{ flex:1; min-width:0; }
.remove-option-x{
  flex:0 0 auto; width:30px; border:1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--input-bg); color:var(--danger); font-size:13px; font-weight:700; cursor:pointer;
  line-height:1;
}
.remove-option-x[hidden]{ display:none; }
.remove-option-x:hover{ background: var(--danger-soft-bg-strong); border-color: var(--danger-soft-border); }

/* Прогноз/Факт — отдельные рамки-карточки, выровненные по строкам через subgrid,
   чтобы результаты (X/AE) всегда были на одном уровне с последней строкой чипов
   слева (AD), без подгонки магическими отступами. */
.econ-columns{
  display:grid; grid-template-columns: 3fr 2fr; grid-template-rows: auto auto auto;
  gap:6px 14px; align-items:stretch;
}
.econ-stage{
  display:grid; grid-template-rows: subgrid; grid-row: 1 / 4;
  border:1px solid var(--border); border-radius: var(--radius-md);
  padding: 6px 8px;
}
.econ-heading{ grid-row:1; align-self:start; font-size:11px; color:var(--text-muted); margin-bottom:3px; }
.econ-inputs{ grid-row:2; align-self:start; }
/* stretch (не end) — чтобы чипы «Факт» тянулись на всю высоту строки субгрида и совпадали
   по высоте с более высокими чипами «Прогноз» (у тех подписи иногда переносятся на 2
   строки из-за более узкой колонки), а не оставались ниже с пустым местом сверху. */
.econ-results{ grid-row:3; align-self:stretch; }
.result-row{ grid-template-columns: repeat(3, 1fr); }
.result-row-5{ display:flex; flex-wrap:wrap; gap:6px; margin-bottom:2px; }
.result-row-5 .result-chip{ flex: 1 1 110px; }
.result-chip{
  background: var(--surface-2); border:1px solid var(--border); border-radius: var(--radius-sm);
  padding: 5px 9px; display:flex; flex-direction:column; justify-content:center; gap:1px;
  min-height:50px; box-sizing:border-box;
}
.result-chip[hidden]{ display:none; }
/* Невидимые метки-якоря — держат "домашнее" место DOM-узла (поля/чипа "Экономики XFIX"),
   который JS физически переносит между общим блоком "Экономика" и компактным блоком рядом
   с "Устройство XFIX" (см. moveEconNode в app.js), а не клонирует. Всегда display:none, вне
   зависимости от того, в какой grid/flex-контейнер их вставили как соседей. */
.dom-anchor{ display:none !important; }
.result-chip span{ font-size:10px; color:var(--text-muted); }
.result-chip strong{ font-size:13px; }
.result-chip-accent{ background: var(--accent-soft-bg); border-color: var(--accent-soft-border); }
.result-chip-accent strong{ color: var(--accent-soft-text); }
.total-chip{
  margin-top:12px; display:flex; align-items:center; justify-content:space-between;
  background: var(--total-chip-bg); border: 1px solid var(--total-chip-border); color:#fff; border-radius: var(--radius-md); padding: 11px 18px;
}
.total-chip span{ font-size:11.5px; color:#9a9aa0; text-transform:uppercase; letter-spacing:.04em; font-weight:700; }
.total-chip strong{ font-size:18px; font-weight:800; }

@media (max-width: 1100px){
  .app{ grid-template-columns: 1fr; }
  .sidebar{ position:relative; height:auto; flex-direction:row; align-items:center; }
  /* В узкой раскладке сайдбар лежит строкой — вертикальный скролл .nav (см. выше) тут не нужен
     и только резал бы содержимое; прокручиваем по горизонтали. */
  .nav{ flex-direction:row; gap:8px; overflow-y:visible; overflow-x:auto; min-height:auto; }
  .nav-group{ flex-direction:row; }
  .nav-divider{ width:1px; height:auto; align-self:stretch; margin: 0 4px; }
  .sidebar-footer{ margin-top:0; margin-left:auto; border-top:none; }
  .form-grid{ grid-template-columns: 1fr; }
  .econ-columns{ grid-template-columns: 1fr; grid-template-rows: none; }
  .econ-stage{ grid-template-rows: auto; grid-row: auto; }
  .econ-heading, .econ-inputs, .econ-results{ grid-row: auto; }
  .photos-columns{ grid-template-columns: 1fr; }
  .dash-row-5{ grid-template-columns: repeat(2, 1fr); }
  .dash-row-4{ grid-template-columns: repeat(2, 1fr); }
  .dash-row-2{ grid-template-columns: 1fr; }
}

/* ===========================================================
   "Алиса Дисконт" — калькулятор трейд-ин (владелец, 2026-07-29: прислал отдельный
   HTML-файл tradein_calculator.html, "хочу разместить это на сайте" → уточняющий вопрос
   показал, что нужна не отдельная площадка, а 3 новых пункта в ЭТОМ сайдбаре — по одному на
   каждую внутреннюю вкладку исходного файла (Калькулятор/Оценка частей/Оценка устройств),
   отгороженные линией от остальных групп, и "самое интересное" — при переходе на них весь
   визуальный стиль сайта должен меняться на стиль исходного файла).
   Реализовано БЕЗ второго сайдбара/шапки: у Trade-IN уже есть архитектура токенов темы
   (:root / [data-theme="dark"] выше, см. также mtime-brand-style-prompt.md/
   xfix-brand-style-prompt.md — тот же приём "инверсия сайдбара", там же описан). Здесь —
   третий скин поверх той же архитектуры: html[data-skin="alisa"] переопределяет СУЩЕСТВУЮЩИЕ
   токены (--bg, --surface, --text, --sidebar- и т.п., --xfix-green и т.д. — переименовывать их
   не нужно, просто задаём им другие значения, и ВЕСЬ существующий сайдбар/шапка/кнопки
   перекрашиваются сами) + добавляет НОВЫЕ токены под собственным неймспейсом исходного файла
   (--ink, --accent, --mono, --mk- и т.п. — они нужны только компонентам ниже, специфичным для
   "Алисы", и никогда не попадают на остальные вкладки). ВНИМАНИЕ на будущее для того, кто будет
   редактировать этот блочный комментарий: внутри него нельзя случайно поставить символ звёздочки
   вплотную перед символом слеша (например при перечислении токенов через дефис-звёздочку) — CSS-
   парсер закрывает блочный комментарий по первой же такой паре символов, без учёта контекста, и
   именно такая опечатка здесь однажды уже "съела" всё правило html[data-skin="alisa"] целиком
   (оно просто исчезало из применённых стилей, без единой ошибки в консоли) — воспроизведено и
   найдено через Playwright + CSSOM-инспекцию. Скин включается/выключается из switchView() в app.js.
   Классы вроде .data-table/.modal/.table-wrap/.col-resize-handle из исходного файла
   ПЕРЕИМЕНОВАНЫ в .alisa-data-table/.alisa-modal/.alisa-table-wrap/.alisa-col-resize-handle —
   у Trade-IN уже были одноимённые классы для таблицы сделок, без переименования произошла бы
   путаница в каскаде. Сайдбар/навигация/кнопка темы исходного файла НЕ перенесены вообще —
   Trade-IN использует свой собственный сайдбар и переключатель темы для всех вкладок разом,
   в т.ч. этих трёх (см. applyTheme в app.js — туда добавлен вызов alisaBuildBG()). */

html[data-skin="alisa"]{
  --bg:#f0eef2;
  --surface:#ffffff;
  --surface-2:#f6f4fb;
  --border:rgba(91,54,232,.16);
  --border-soft:rgba(91,54,232,.10);
  --text:#20143f;
  --text-muted:#5c5175;
  --field-label-color:#5c5175;
  --ink:#20143f;
  --ink-dim:#5c5175;
  --ink-faint:#8b82a6;
  --xfix-green:#5B36E8;
  --xfix-green-dark:#4A2BC4;
  --accent:#5B36E8;
  --accent-hover:#4A2BC4;
  --accent-ink:#FFFFFF;
  --on-accent:#FFFFFF;
  --accent-soft-bg:rgba(91,54,232,.08);
  --accent-soft-border:rgba(91,54,232,.22);
  --spark:#7c4dff;
  --arc:#8a5cff;
  --soul-pink:#ffd6f6;
  --soul-lilac:#c9a8ff;
  --success:#22b07a;
  --success-soft-bg:rgba(34,176,122,.10);
  --success-soft-border:rgba(34,176,122,.28);
  --danger:#e5475b;
  --danger-soft-bg:rgba(229,71,91,.09);
  --danger-soft-bg-strong:rgba(229,71,91,.09);
  --danger-soft-border:rgba(229,71,91,.28);
  --warning:#9c6b00;
  --warning-text:#9c6b00;
  --warning-soft-bg:rgba(224,163,0,.14);
  --warning-soft-border:rgba(224,163,0,.32);
  --mk-g:#22b07a; --mk-gy:#6aac3e; --mk-y:#e0a300; --mk-yr:#e07a00; --mk-r:#e5475b;
  --input-bg:#FFFFFF;
  --input-focus-bg:#FFFFFF;
  --shadow-sm:0 2px 8px rgba(32,20,63,.07);
  --shadow-md:0 14px 34px rgba(32,20,63,.12);
  --shadow-lg:0 24px 60px rgba(32,20,63,.18);
  --radius:16px;
  --mono:"SF Mono",ui-monospace,"JetBrains Mono","Roboto Mono",Menlo,Consolas,monospace;
  --sans-alisa:"YS Display",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Arial,sans-serif;
  --btn-primary-bg:#5B36E8;
  --btn-primary-bg-hover:#4A2BC4;
  --btn-primary-text:#FFFFFF;
  --total-chip-bg:#150F29;
  --total-chip-border:transparent;
  --sidebar-bg:#150F29;
  --sidebar-border:rgba(255,255,255,.08);
  --sidebar-divider:rgba(255,255,255,.14);
  --sidebar-text:#EDE8FA;
  --sidebar-text-active:#FFFFFF;
  --sidebar-text-muted:#9A8FC0;
  --sidebar-hover-bg:rgba(255,255,255,.06);
}
html[data-skin="alisa"][data-theme="dark"]{
  --bg:#120C22;
  --surface:#1C1533;
  --surface-2:#251C3E;
  --border:rgba(255,255,255,.10);
  --border-soft:rgba(255,255,255,.06);
  --text:#F1EDFB;
  --text-muted:#B7ADD4;
  --field-label-color:#B7ADD4;
  --ink:#F1EDFB;
  --ink-dim:#B7ADD4;
  --ink-faint:#8B82A6;
  --xfix-green:#8a5cff;
  --xfix-green-dark:#9C74FF;
  --accent:#8a5cff;
  --accent-hover:#9C74FF;
  --accent-ink:#FFFFFF;
  --on-accent:#FFFFFF;
  --accent-soft-bg:rgba(138,92,255,.16);
  --accent-soft-border:rgba(138,92,255,.34);
  --spark:#a98bff;
  --arc:#8a5cff;
  --success:#3FBE85;
  --success-soft-bg:rgba(63,190,133,.14);
  --success-soft-border:rgba(63,190,133,.32);
  --danger:#FF6B7F;
  --danger-soft-bg:rgba(255,107,127,.12);
  --danger-soft-bg-strong:rgba(255,107,127,.12);
  --danger-soft-border:rgba(255,107,127,.32);
  --warning:#F0B429;
  --warning-text:#F0B429;
  --warning-soft-bg:rgba(240,180,41,.14);
  --warning-soft-border:rgba(240,180,41,.34);
  --input-bg:#251C3E;
  --input-focus-bg:#2C2249;
  --shadow-sm:0 2px 8px rgba(0,0,0,.32);
  --shadow-md:0 14px 34px rgba(0,0,0,.4);
  --shadow-lg:0 24px 60px rgba(0,0,0,.5);
  --btn-primary-bg:#8a5cff;
  --btn-primary-bg-hover:#9C74FF;
  --total-chip-bg:#F4F2F8;
  --sidebar-bg:#F4F2F8;
  --sidebar-border:rgba(25,21,33,.08);
  --sidebar-divider:rgba(25,21,33,.12);
  --sidebar-text:#241E30;
  --sidebar-text-active:#141018;
  --sidebar-text-muted:#6F6884;
  --sidebar-hover-bg:rgba(25,21,33,.06);
}
/* Шрифт YS Display — подключается ТОЛЬКО в этом скине (см. assets/alisa-fonts.css,
   вынесен из-за размера — 3 начертания base64, ~150КБ). На остальных вкладках — обычный
   Wix Madefor Text, его переопределение здесь никак не задевает. */
html[data-skin="alisa"] body{ font-family: var(--sans-alisa); }
/* Сайдбар в этот шрифт НЕ переодеваем (владелец, 2026-07-29: "поправь Старовецкий продакшен —
   при переходе на Алиса Дисконт он в одну строчку смещается") — YS Display заметно у́же Wix
   Madefor Text, из-за чего "Starovetsky Production" в сайдбаре переставало переноситься на две
   строки и "прыгало". Сайдбар и так перекрашивается под скин через токены --sidebar-* (см.
   выше) — цвет меняется, а вот шрифт/раскладка остаются как везде, чтобы бренд-блок не дёргался
   при переключении между обычными вкладками и "Алисой". */
html[data-skin="alisa"] .sidebar{ font-family:"Wix Madefor Text", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }

/* ===========================================================
   СКИН "м.Тайм" — html[data-skin="mtime"]
   ===========================================================
   Владелец, 2026-07-30: "все вкладки м.Тайм по аналогии с вкладками Алисы Дисконта стилизуем
   под его фирменные цвета... Оформление должно отличаться от Алисы дисконта и XFIX".
   Включается из switchView() в app.js для вкладок: Поступление м.Тайм, Склад м.Тайм,
   Продажи м.Тайм, Дашборд м.Тайм, Ошибки м.Тайм и Оценка стоимости.

   ТОТ ЖЕ ПРИЁМ, ЧТО У "АЛИСЫ": переопределяем СУЩЕСТВУЮЩИЕ токены темы, а не заводим
   параллельные с новыми именами — тогда сайдбар, топбар, кнопки, таблицы, модалки и т.д.
   перекрашиваются сами, без единой правки в их правилах. Свои токены скина живут в
   неймспейсе --mt-… и никому больше не мешают.

   ОСТОРОЖНО, старая грабля этого файла: звёздочка, за которой сразу идёт слэш, внутри
   русского блок-комментария молча закрывает комментарий и съедает следующее правило целиком,
   без ошибок в консоли. Поэтому имена групп токенов пишем как --sidebar-… (многоточием),
   а не через звёздочку.

   ЧЕМ ОТЛИЧАЕТСЯ ОТ ДВУХ ДРУГИХ (это было прямым требованием):
     · палитра — брендбук м.Тайм, с. 16: 6E61ED Service (фиолетовый), 4DC9B1 Team (бирюзовый),
       C51617 Premium (красный), 34173F тёмно-фиолетовый, 161716 тёплый почти-чёрный.
       У XFIX акцент зелёный, у Алисы — холодный синий-фиолет 5B36E8 на сиреневом фоне;
       здесь фиолетовый ТЁПЛЕЕ и всегда идёт в паре с бирюзой, которой нет ни там, ни там.
     · фон рабочей области — тёплая бумага F1F0EC, а не холодный серый (XFIX) и не сирень (Алиса);
       сайдбар — тёплый чёрный 161716, а не нейтральный 0c0c0d и не тёмно-фиолетовый 150F29.
     · геометрия — радиусы 12/8/6 вместо 20/14/10 у XFIX и 16 у Алисы: скругления заметно
       острее, это считывается мгновенно, ещё до цвета.
     · шрифт — Manrope (брендбук, с. 19), см. assets/mtime-fonts.css.
     · фирменные носители из брендбука (с. 18) — "Круг" и "Линия", см. .mtime-bg ниже.
       У Алисы вместо них фотографический фон-дуга, у XFIX декора нет вовсе. */

html[data-skin="mtime"]{
  /* фирменная палитра как есть — на неё ссылаются декоративные слои ниже */
  --mt-purple:#6E61ED;
  --mt-purple-deep:#5849D6;
  --mt-teal:#4DC9B1;
  --mt-teal-deep:#2FA893;
  --mt-red:#C51617;
  --mt-violet:#34173F;
  --mt-ink:#161716;
  --sans-mtime:"Manrope",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Arial,sans-serif;

  --bg:#F1F0EC;
  --surface:#FFFFFF;
  --surface-2:#F7F6F2;
  --border:rgba(22,23,22,.13);
  --border-soft:rgba(22,23,22,.07);
  --text:#161716;
  --text-muted:#6B6C67;
  --field-label-color:#4A4B47;

  --xfix-green:#6E61ED;
  --xfix-green-dark:#5849D6;
  --on-accent:#FFFFFF;
  --accent-soft-bg:rgba(110,97,237,.09);
  --accent-soft-border:rgba(110,97,237,.24);
  --accent-soft-text:#5849D6;

  --danger:#C51617;
  --danger-soft-bg:rgba(197,22,23,.06);
  --danger-soft-bg-strong:rgba(197,22,23,.11);
  --danger-soft-border:rgba(197,22,23,.22);
  --warning-text:#9A6B00;

  --input-bg:#FFFFFF;
  --input-focus-bg:#FFFFFF;

  /* Кнопка-действие тёмно-фиолетовая (34173F), а не акцентная: фиолетовый акцент уже занят
     активной вкладкой сайдбара и чипами, вторая такая же заливка в шапке их бы забила. */
  --btn-primary-bg:#34173F;
  --btn-primary-bg-hover:#48225A;
  --btn-primary-text:#FFFFFF;
  --total-chip-bg:#34173F;
  --total-chip-border:transparent;

  --radius-lg:12px;
  --radius-md:8px;
  --radius-sm:6px;
  --shadow-sm:0 1px 2px rgba(22,23,22,.05), 0 1px 1px rgba(22,23,22,.04);
  --shadow-md:0 10px 28px rgba(52,23,63,.10);
  --shadow-lg:0 22px 56px rgba(52,23,63,.16);

  --sidebar-bg:#161716;
  --sidebar-border:rgba(255,255,255,.08);
  --sidebar-divider:rgba(255,255,255,.16);
  --sidebar-text:#C7C7C1;
  --sidebar-text-active:#FFFFFF;
  --sidebar-text-muted:#83847E;
  --sidebar-hover-bg:rgba(255,255,255,.07);
  --sidebar-toggle-bg:rgba(255,255,255,.07);
  --sidebar-toggle-hover-bg:rgba(255,255,255,.14);
  --sidebar-toggle-hover-text:#FFFFFF;
}
/* Тёмная тема скина. Сайдбар, как и везде в проекте, инвертируется в светлый — это
   осознанное решение владельца, скин его не отменяет. Фиолетовый осветлён до 8B80F2:
   фирменный 6E61ED на тёмном фоне даёт мало контраста для текста и обводок. */
html[data-skin="mtime"][data-theme="dark"]{
  --bg:#111211;
  --surface:#1A1B19;
  --surface-2:#222320;
  --border:rgba(255,255,255,.11);
  --border-soft:rgba(255,255,255,.06);
  --text:#F2F2EE;
  --text-muted:#9B9C95;
  --field-label-color:#C6C7C0;

  --xfix-green:#8B80F2;
  --xfix-green-dark:#A197F6;
  --on-accent:#12101F;
  --accent-soft-bg:rgba(139,128,242,.16);
  --accent-soft-border:rgba(139,128,242,.34);
  --accent-soft-text:#A197F6;

  --danger:#F2585A;
  --danger-soft-bg:rgba(242,88,90,.12);
  --danger-soft-bg-strong:rgba(242,88,90,.18);
  --danger-soft-border:rgba(242,88,90,.32);
  --warning-text:#E8B33D;

  --input-bg:#222320;
  --input-focus-bg:#2A2B27;

  --btn-primary-bg:#8B80F2;
  --btn-primary-bg-hover:#A197F6;
  --btn-primary-text:#12101F;
  --total-chip-bg:#222320;
  --total-chip-border:#34352F;

  --shadow-sm:0 1px 2px rgba(0,0,0,.3), 0 1px 1px rgba(0,0,0,.2);
  --shadow-md:0 10px 28px rgba(0,0,0,.42);
  --shadow-lg:0 22px 56px rgba(0,0,0,.56);

  --sidebar-bg:#F3F2EE;
  --sidebar-border:#E3E2DC;
  --sidebar-divider:#D3D2CB;
  --sidebar-text:#57584F;
  --sidebar-text-active:#161716;
  --sidebar-text-muted:#87887F;
  --sidebar-hover-bg:rgba(22,23,22,.06);
  --sidebar-toggle-bg:rgba(22,23,22,.05);
  --sidebar-toggle-hover-bg:rgba(22,23,22,.10);
  --sidebar-toggle-hover-text:#161716;
}
/* Manrope — только в этом скине (assets/mtime-fonts.css, вынесен из-за размера: переменное
   начертание base64, ~75КБ). Сайдбар в него НЕ переодеваем — ровно та же ловушка, что была с
   YS Display у Алисы: другой по ширине шрифт ломает перенос подписи "Starovetsky Production"
   в бренд-блоке, и она прыгает при переключении вкладок. Сайдбар и так перекрашивается
   токенами --sidebar-… выше, этого достаточно. */
html[data-skin="mtime"] body{ font-family: var(--sans-mtime); }
html[data-skin="mtime"] .sidebar{ font-family:"Wix Madefor Text", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
/* Manrope переменный — в заголовках берём верх оси wght (800), иначе браузер подставит
   ближайший статический вес и заголовки выйдут вялыми. */
html[data-skin="mtime"] .topbar-heading h1{ font-weight:800; letter-spacing:-.02em; }

/* ---- фирменные носители брендбука: "Круг" и "Линия" ----
   Разметка — #mtime-bg в index.html, первый ребёнок .main (там же, где alisa-bg, и по той же
   причине: только так фон заливает колонку целиком, включая паддинг .main и область под шапкой).
   z-index:-1 обязателен и намеренно отрицательный — непозиционированный контент в потоке
   рисуется ПОВЕРХ позиционированных элементов с z-index:auto/0, поэтому 0 здесь перекрыл бы
   всё содержимое .main (см. тот же комментарий у .alisa-bg ниже). */
.mtime-bg{display:none;position:absolute;inset:0;z-index:-1;pointer-events:none;overflow:hidden}
html[data-skin="mtime"] .mtime-bg{display:block}
/* "Круг" — кольцо УГЛОВЫМ градиентом: брендбук (с. 18) прямо рекомендует именно угловой,
   а не линейный. conic-gradient даёт кольцо, mask-image вырезает середину — заливки нет,
   есть только обод, как в знаке. Держим очень бледным: это водяной знак, а не декорация
   поверх данных.
   Первый заход рисовал кольцо диаметром 620px с вылетом за угол — получалась не фирменная
   фигура, а большая размытая дуга, почти как фон "Алисы", то есть ровно то, от чего надо было
   уйти. Теперь кольцо целиком в кадре и с тонким ободом — читается как знак, а не как заливка. */
.mtime-bg-ring{
  position:absolute; top:34px; right:-86px; width:340px; height:340px; border-radius:50%;
  background:conic-gradient(from 210deg, var(--mt-purple), var(--mt-teal), var(--mt-purple));
  -webkit-mask-image:radial-gradient(closest-side, transparent 86%, #000 87%, #000 100%);
          mask-image:radial-gradient(closest-side, transparent 86%, #000 87%, #000 100%);
  opacity:.45;
}
html[data-skin="mtime"][data-theme="dark"] .mtime-bg-ring{ opacity:.38; }
/* "Линия" — тонкая горизонтальная растяжка 6E61ED→4DC9B1 по верхней кромке рабочей области.
   Градиент строго в одном направлении и не как сплошная заливка фона — так требует брендбук. */
.mtime-bg-line{
  position:absolute; top:0; left:0; right:0; height:3px;
  background:linear-gradient(90deg, var(--mt-purple) 0%, var(--mt-teal) 100%);
}

/* ---- компоненты вкладок Алиса Дисконт (перенесено из tradein_calculator.html владельца,
   классы отранжированы под неймспейс алисы — см. пояснение блока выше) ---- */

  /* ---------- main area ---------- */
  /* Владелец, 2026-07-29, второй заход: "ну ты же не заполняешь полностью! почему?" — первая
     правка (min-height на этом блоке) только чуть нарастила высоту, но фон всё ещё жил ВНУТРИ
     `.alisa-main-area`, которая начинается ниже шапки/KPI/поиска и вписана в паддинг `.main` —
     поэтому сверху и по бокам оставалась рамка из обычного фона страницы. Реальный фон
     (`#alisa-bg`) теперь переехал в начало `<main class="main">` целиком (см. index.html и
     `.alisa-bg`/`html[data-skin="alisa"] .alisa-bg` ниже) — заливает всю колонку контента от
     края до края, включая область под шапкой. Этому блоку `min-height`/абсолютный фон больше не
     нужны, оставлен просто как обычный контейнер для `.alisa-main-content`.  */
  .alisa-main-area{flex:1;min-width:0;position:relative}
  /* Скрыт по умолчанию — показываем только в скине "Алисы" (см. правило ниже), чтобы на
     остальных вкладках этот div (первый ребёнок `.main`, см. index.html) вообще не участвовал
     в разметке/рендере. position:absolute;inset:0 относительно `.main` (см. `.main{position:
     relative}` выше) — заливает ВСЮ `.main`, включая её паддинг, а не только `.alisa-main-area`. */
  /* z-index:-1 — намеренно ОТРИЦАТЕЛЬНЫЙ: шапка/KPI/поиск и сами вкладки идут в обычном потоке
     без своего z-index, а непозиционированный контент в потоке рисуется ПОВЕРХ позиционированных
     элементов с z-index:auto/0 (см. правило стэкинга) — то есть z-index:0 здесь красился бы
     ПОВЕРХ всего остального содержимого `.main`, перекрывая его. -1 гарантированно кладёт фон
     под низ (в пределах stacking context `.main`, см. `.main{z-index:0}` выше). */
  /* background-attachment:fixed — почему именно так (2026-08-01, владелец: «когда я снимаю галочки
     с частей в калькуляторе, фон на заднем плане смещается, он не должен двигаться»).
     Причина бага: этот блок растянут на всю .main (inset:0), а картинка лежала в нём как
     `cover` + `center`. Значит область позиционирования фона = высота КОНТЕНТА. Стоило
     содержимому подрасти хоть на 7px (в калькуляторе так и происходит: снятая галочка меняет
     сценарий, появляются/исчезают строки ремонта и предупреждения) — как cover пересчитывал
     масштаб, а center заново centrировал картинку, и весь фон заметно дёргался.
     С `fixed` областью позиционирования становится ОКНО браузера: высота контента на фон больше
     не влияет вообще, и он стоит неподвижно. Замерено: было — сдвиг на каждом изменении высоты,
     стало — картинка попиксельно одинаковая. */
  .alisa-bg{display:none;position:absolute;inset:0;z-index:-1;pointer-events:none;overflow:hidden;
    background-size:cover;background-position:center;background-repeat:no-repeat;
    background-attachment:fixed;
    transition:opacity .25s ease}
  html[data-skin="alisa"] .alisa-bg{display:block}
  /* Паддинг был 90px снизу, потом 26px 30px 26px — и владелец, 2026-07-30, прислал скриншот с
     пятью обведёнными пустыми полосами: "тебя не смущает эти отступы?". Смущают. Разбор полос
     (замер на 1500x950, вкладка «Оценка частей»):
       поиск -> плашка о ценах      50px = 24 (margin-bottom у .global-search) + 26 (этот паддинг);
       плашка -> строка моделей     32px = 16 (margin у плашки) + 16 (margin-top у .models);
       модели -> таблица            64px = 16 (margin-bottom .models) + 36 (.table-toolbar, где
                                           лежала ОДНА кнопка "+ добавить строку") + 12 (её margin);
       под карточкой                74px = 26 (этот паддинг) + 48 (padding-bottom у .main).
     Итого 220px пустого фона на странице, где таблице досталось 447px, и она при этом
     прокручивалась. Ни один из четырёх интервалов не задавался осознанно — все сложились из
     наложения отступов соседей.
     Правка: паддинг обнуляется целиком. Вертикальные интервалы теперь задаёт РОВНО ОДИН сосед в
     каждой паре (у .global-search — свой margin-bottom 24, у плашки — свой margin-bottom 16,
     у .models — свой margin-bottom 12), полосу под моделями убирает переезд кнопки внутрь
     таблицы (см. .add-row ниже и renderParts в app.js), а низ задаёт padding-bottom у .main —
     он для скина «Алисы» уменьшен с 48 до 24 (правило сразу ниже).
     Нулевой паддинг по бокам — заодно и выравнивание: колонка контента «Алисы» была вписана
     внутрь паддинга .main ещё на 30px, поэтому таблица и плашка стояли на 30px правее строки
     поиска и шапки прямо над ними. Теперь все левые края страницы совпадают. */
  .alisa-main-content{position:relative;z-index:1;padding:0}
  /* Под карточкой таблицы (её высоту дотягивает до этой границы fitTableWrapHeight в app.js)
     48px читались как незанятое место — при том, что по бокам поля 36px. 24px снизу при 36 по
     бокам смотрятся как поле, а не как дыра. */
  html[data-skin="alisa"] .main{padding-bottom:24px}

  .ghost-btn{display:inline-flex;align-items:center;gap:6px;background:var(--surface);border:1px solid var(--border);
    border-radius:10px;padding:8px 14px;cursor:pointer;color:var(--ink-dim);font-size:12.3px;font-weight:650;
    transition:.14s;box-shadow:var(--shadow-sm);white-space:nowrap}
  .ghost-btn:hover{border-color:var(--accent-soft-border);color:var(--accent)}
  .ghost-btn svg{width:14px;height:14px}

  /* ---------- filter / search row ---------- */
  .filter-row{display:flex;gap:9px;flex-wrap:wrap;margin:16px 0 14px}
  .search-input-wrap{flex:1;min-width:240px;position:relative}
  .search-input-wrap svg{position:absolute;left:12px;top:50%;transform:translateY(-50%);width:15px;height:15px;color:var(--ink-faint);pointer-events:none}
  .search-input-wrap input{width:100%;padding:10px 12px 10px 34px;font-size:12.8px;
    background:var(--input-bg);border:1px solid var(--border);border-radius:10px;outline:none;transition:border-color .14s}
  .search-input-wrap input:focus{border-color:var(--accent);background:var(--input-focus-bg)}
  .filter-select{min-width:150px;padding:10px 30px 10px 13px;font-size:12.8px;font-weight:600;
    background:var(--input-bg);border:1px solid var(--border);border-radius:10px;outline:none;cursor:pointer;
    appearance:none;
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%238B879A' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat:no-repeat;background-position:right 11px center}
  .filter-select:focus{border-color:var(--accent)}

  /* Верхний margin убран (было 16px 0 16px): над строкой моделей стоит плашка о ценах со своим
     margin-bottom:16px, и два отступа складывались в 32px — вторая из полос, обведённых
     владельцем 2026-07-30. Интервал в паре задаёт кто-то один; здесь — плашка сверху и .models
     снизу. */
  .models{display:flex;flex-wrap:wrap;gap:7px;margin:0 0 12px}
  /* Снимок в чипе увеличен с 22 до 38px (2026-08-01, владелец: «давай сделаем картинки больше —
     сейчас очень плохо видно, что на них»). На 22px колонка и её подставка сливались в пятно.
     object-fit:contain вместо cover — на этих снимках устройство снято целиком, и обрезка по
     кругу срезала верх и низ; теперь колонка видна полностью, а фон добирает подложка. */
  .model-btn{display:inline-flex;align-items:center;gap:9px;
    appearance:none;border:1px solid var(--border);background:var(--surface);
    color:var(--ink-dim);font-family:var(--sans);font-size:12.6px;font-weight:600;
    padding:5px 14px 5px 5px;border-radius:999px;cursor:pointer;transition:.14s;box-shadow:var(--shadow-sm)}
  .model-btn .thumb{width:46px;height:46px;border-radius:50%;flex:none;object-fit:contain;
    padding:2px;background:var(--surface-2);border:1px solid var(--border-soft)}
  .model-btn .thumb.none{display:flex;align-items:center;justify-content:center;
    font-size:12px;color:var(--ink-faint);font-family:var(--mono)}
  .model-btn:hover{border-color:var(--accent-soft-border);color:var(--ink)}
  .model-btn[aria-pressed="true"]{background:var(--accent);color:var(--accent-ink);border-color:transparent}
  .model-btn .thumb{position:relative}
  .model-btn[title] .thumb:hover{outline:2px solid var(--accent);outline-offset:1px}
  .chip-del{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;
    border-radius:50%;margin-left:2px;font-size:9px;color:inherit;opacity:.55}
  .chip-del:hover{opacity:1;background:rgba(0,0,0,.15)}
  .model-btn-add{border-style:dashed;color:var(--ink-faint)}
  .model-btn-add .thumb.none{font-size:15px;font-weight:700}
  .model-btn-add:hover{color:var(--accent);border-color:var(--accent-soft-border)}

  .alisa-modal-overlay{position:fixed;inset:0;background:rgba(15,10,30,.55);display:flex;
    align-items:center;justify-content:center;z-index:80;backdrop-filter:blur(2px)}
  .alisa-modal{background:var(--surface);border-radius:var(--radius);padding:22px;width:340px;max-width:calc(100vw - 40px);
    box-shadow:var(--shadow-lg);display:grid;gap:8px;border:1px solid var(--border-soft)}
  .alisa-modal-title{font-weight:700;font-size:15px;color:var(--ink);margin-bottom:4px}
  .alisa-modal-label{font-size:11.5px;color:var(--ink-faint);margin-top:6px}
  .alisa-modal-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:12px}

  /* ---------- card ---------- */
  .card{background:var(--surface);border:1px solid var(--border-soft);border-radius:var(--radius);
    overflow:hidden;box-shadow:var(--shadow-md)}
  .card-head{display:flex;align-items:center;gap:16px;padding:18px 20px;border-bottom:1px solid var(--border-soft)}
  .card-head .photo{width:58px;height:58px;border-radius:14px;flex:none;object-fit:contain;
    background:var(--surface-2);padding:5px;border:1px solid var(--border-soft)}
  .card-head .photo.none{display:flex;align-items:center;justify-content:center;
    font-size:10px;color:var(--ink-faint);font-family:var(--mono);text-align:center;padding:8px}
  .card-head .htext{flex:1;min-width:0}
  .card-head .name{font-size:16.5px;font-weight:700;letter-spacing:-.01em;color:var(--ink)}
  .badge{font-family:var(--mono);font-size:10px;text-transform:uppercase;letter-spacing:.09em;
    padding:5px 10px;border-radius:999px;border:1px solid var(--border);color:var(--ink-dim);white-space:nowrap}

  .section-label{font-family:var(--mono);font-size:10.3px;text-transform:uppercase;
    letter-spacing:.13em;color:var(--ink-faint);padding:15px 20px 6px}

  .rows{padding:4px 10px 10px}
  .row{display:grid;grid-template-columns:20px 12px 1fr auto;align-items:center;gap:11px;
    padding:10px 12px;border-radius:11px}
  .row + .row{border-top:1px solid var(--border-soft)}
  .row:hover{background:var(--surface-2)}
  .part-chk{width:18px;height:18px;accent-color:var(--accent);cursor:pointer}
  .part-name{font-size:13.4px;color:var(--ink)}
  .price{font-family:var(--mono);font-size:13.2px;font-weight:600;text-align:right;color:var(--ink);white-space:nowrap}
  .price::after{content:" ₽";color:var(--ink-faint);font-weight:400}
  /* Снятая галочка = деталь неисправна (владелец, 2026-07-30). Зачёркнутое название и красная
     цена с «+»: эта деталь больше не часть оценки, а расход — «Требуемые вложения» в блоке
     справа. Класс навешивается на лету в panel.oninput, без перерисовки панели. */
  .row-broken .part-name{color:var(--ink-dim);text-decoration:line-through;
    text-decoration-color:var(--ink-faint);text-decoration-thickness:1px}
  .row-broken .price{color:var(--danger)}
  .row-broken .price::before{content:"+ "}
  .row-diag .part-name{color:var(--ink-dim);font-style:italic}
  .row-diag .price{color:var(--ink-dim)}
  .row-diag .diag-mark{display:flex;align-items:center;justify-content:center;color:var(--ink-faint);font-weight:700}

  /* 3 колонки (владелец, 2026-07-30: "делаем 3 блока в ряд … эти блоки сжимать и справа от них
     ставить 2 нижних блока"). Третья уже двух первых: в ней только две карточки-предложения с
     короткими числами, а в первых двух — списки деталей и строки экономики, которым ширина
     нужнее. align-items:start — иначе короткая третья карточка растягивалась бы по высоте
     самой длинной колонки и внутри неё висела бы пустота. */
  /* Третья колонка была самой узкой (.82fr), пока держала только два предложения. С 2026-07-31
     под ними стоит ещё и «Доплата и заработок» с длинными подписями и суммами — ширины ей добавили
     за счёт средней, из которой этот блок как раз ушёл. */
  .calc-top-grid{display:grid;grid-template-columns:1fr .92fr 1.04fr;align-items:start;gap:11px;padding:6px 20px 20px}
  /* Средняя колонка держит два блока друг под другом (2026-08-01): «Клиент получает взамен»
     и «Что делаем с колонкой клиента». align-content:start — чтобы карточки прижимались к верху,
     а не растягивались на всю высоту сетки. */
  .calc-col{display:grid;gap:11px;align-content:start;min-width:0}
  .calc-subcard{border-radius:14px;border:1px solid var(--border-soft);background:var(--surface-2);overflow:hidden}
  .calc-subcard .section-label{padding:12px 14px 4px}
  .calc-subcard .legend{padding:0 14px 10px}
  .calc-subcard .rows{padding:2px 8px 10px}
  .calc-subcard .row:hover{background:var(--surface)}
  .calc-subcard .ref-wrap{padding:2px 12px 12px}
  .calc-subcard .ref-row{background:var(--surface)}
  /* 1300px — точка, где третья колонка становится слишком узкой для "1 234 567 ₽" в одну
     строку: сначала уходим на 2 колонки (предложения занимают вторую строку целиком, снова
     в ряд по горизонтали), и только на 1000px — на одну. */
  @media (max-width:1300px){
    .calc-top-grid{grid-template-columns:1fr 1fr}
    .calc-top-grid > .calc-subcard:last-child{grid-column:1 / -1}
    .calc-top-grid > .calc-subcard:last-child .calc-offers{grid-template-columns:1fr 1fr}
  }
  @media (max-width:1000px){
    .calc-top-grid{grid-template-columns:1fr}
    .calc-top-grid > .calc-subcard:last-child .calc-offers{grid-template-columns:1fr}
  }

  /* маркер-светофор ликвидности */
  .dot{width:11px;height:11px;border-radius:50%;flex:none;display:inline-block}
  .dot-g{background:var(--mk-g);box-shadow:0 0 8px color-mix(in srgb,var(--mk-g) 55%,transparent)}
  .dot-gy{background:var(--mk-gy);box-shadow:0 0 8px color-mix(in srgb,var(--mk-gy) 55%,transparent)}
  .dot-y{background:var(--mk-y);box-shadow:0 0 8px color-mix(in srgb,var(--mk-y) 55%,transparent)}
  .dot-yr{background:var(--mk-yr);box-shadow:0 0 8px color-mix(in srgb,var(--mk-yr) 55%,transparent)}
  .dot-r{background:var(--mk-r);box-shadow:0 0 8px color-mix(in srgb,var(--mk-r) 55%,transparent)}
  .legend{display:flex;gap:14px;flex-wrap:wrap;padding:0 20px 12px;font-size:11px;color:var(--ink-faint)}
  .legend span{display:inline-flex;align-items:center;gap:6px}
  .legend .dot{width:9px;height:9px;box-shadow:none}
  /* Легенда светофора перестала быть подписью и стала органом управления (2026-07-31): рядом с
     каждым маркером — процент реализации при разборе. Раньше светофор не влиял ни на что, все
     детали считались по 100 %, из-за чего «разобрать» всегда выглядело выгоднее, чем оно есть. */
  .legend-liq{gap:10px 16px;align-items:center}
  .legend-liq span{gap:4px}
  .legend-liq .field-input{height:24px;padding:0 4px;font-size:11px;text-align:center;
    font-family:var(--mono);color:var(--ink-dim)}
  /* В калькуляторе светофор остался только для чтения (проценты правит инженер во вкладке
     «Оценка частей», 2026-07-31) — цифра вместо поля ввода. */
  .legend-ro{cursor:default}
  .legend-ro b{font-family:var(--mono);font-size:11px}

  /* Панель инженера над прайсом «Оценки частей» (2026-07-31): проценты реализации (переехали из
     калькулятора) и общий регулятор «% от AIDE» на все позиции модели. */
  .parts-tools{background:var(--surface);border:1px solid var(--border-soft);border-radius:var(--radius);
    padding:12px 16px;margin-bottom:11px;display:grid;gap:9px}
  .parts-tools-row{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
  .parts-tools-label{font-size:11.6px;font-weight:650;color:var(--ink-dim);min-width:168px;
    letter-spacing:.02em;text-transform:uppercase}
  .parts-tools .legend{padding:0}
  .parts-tools-btn{padding:7px 13px;font-size:12.5px;border-radius:9px}
  .parts-tools-hint{font-size:11.5px;color:var(--ink-faint)}
  .marker-select-wrap{display:inline-flex;align-items:center;gap:8px}
  select.js-marker{appearance:none;font-family:var(--sans);font-weight:500;padding-right:26px;
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%238B879A' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat:no-repeat;background-position:right 9px center;cursor:pointer}
  select.js-marker.marker-g{background-color:color-mix(in srgb,var(--mk-g) 14%,var(--surface));color:var(--mk-g);border-color:color-mix(in srgb,var(--mk-g) 40%,transparent)}
  select.js-marker.marker-gy{background-color:color-mix(in srgb,var(--mk-gy) 14%,var(--surface));color:var(--mk-gy);border-color:color-mix(in srgb,var(--mk-gy) 40%,transparent)}
  select.js-marker.marker-y{background-color:color-mix(in srgb,var(--mk-y) 14%,var(--surface));color:var(--mk-y);border-color:color-mix(in srgb,var(--mk-y) 40%,transparent)}
  select.js-marker.marker-yr{background-color:color-mix(in srgb,var(--mk-yr) 14%,var(--surface));color:var(--mk-yr);border-color:color-mix(in srgb,var(--mk-yr) 40%,transparent)}
  select.js-marker.marker-r{background-color:color-mix(in srgb,var(--mk-r) 14%,var(--surface));color:var(--mk-r);border-color:color-mix(in srgb,var(--mk-r) 40%,transparent)}

  .ref-wrap{padding:2px 20px 8px;display:grid;gap:7px}
  .ref-wrap.ref-last{padding-bottom:20px}
  .ref-row{display:flex;align-items:center;justify-content:space-between;gap:14px;
    padding:11px 15px;background:var(--surface-2);border:1px solid var(--border-soft);border-radius:12px}
  .ref-row .ref-name{font-size:13px;font-weight:600;color:var(--ink)}
  /* Единый кегль для ВСЕХ денежных значений в строках (2026-08-01, владелец: «тут текст разного
     размера (суммы)»). Раньше в одном столбце соседствовали 12.8 / 13.6 / 14.5 / 15px — активный
     сценарий, «К доплате», «Итого» и обычные строки были каждый своего размера, и колонка цифр
     выглядела рваной. Теперь размер один, а важность передаётся насыщенностью и цветом. */
  .ref-row .ref-val{font-family:var(--mono);font-size:13.2px;color:var(--ink-dim);text-align:right}
  .ref-row.total{border-top:1px dashed var(--border);padding-top:12px;margin-top:2px;background:transparent;border-left:0;border-right:0;border-bottom:0;border-radius:0}
  .ref-row.total .ref-name{font-weight:700}
  .ref-row.total .ref-val{font-weight:700;color:var(--ink)}
  /* итоговая строка — тёмная плашка в цвет левого бара */
  .ref-row.total-dark{background:var(--sidebar-bg);border:1px solid var(--sidebar-border);
    border-radius:12px;padding:13px 15px;margin-top:4px}
  .ref-row.total-dark .ref-name{font-weight:700;color:var(--sidebar-text-active)}
  .ref-row.total-dark .ref-val{font-weight:700;color:var(--sidebar-text-active)}
  .ref-row.total-dark .ref-val span{color:var(--sidebar-text-muted)!important}
  /* Выбранный сценарий выхода (2026-07-31). Три сценария считаются всегда и показываются рядом —
     активный подсвечен акцентной рамкой, чтобы было видно не только «сколько», но и «за счёт чего». */
  .ref-row.sc-active{background:color-mix(in srgb,var(--accent) 8%,var(--surface-2));
    border-color:color-mix(in srgb,var(--accent) 45%,transparent)}
  /* Сценарий выбирается кликом по карточке (2026-07-31) — курсор и подсветка при наведении. */
  .ref-row.sc-click{cursor:pointer;transition:border-color .12s,background .12s,box-shadow .12s}
  .ref-row.sc-click:hover{border-color:color-mix(in srgb,var(--accent) 55%,transparent);
    background:color-mix(in srgb,var(--accent) 5%,var(--surface-2))}
  .ref-row.sc-click:active{transform:translateY(.5px)}
  /* Полоса сценариев в шапке карточки (2026-08-01) — заняла место убранного заголовка с названием
     модели. Отступы повторяют прежний .card-head (18px/20px), чтобы карточка не «поехала»; снизу
     та же разделительная линия. Три равные ячейки в ряд: подпись слева, сумма справа. */
  .sc-bar{padding:14px 20px 12px;border-bottom:1px solid var(--border-soft)}
  .sc-bar-row{display:grid;grid-template-columns:repeat(3,1fr);gap:9px}
  .sc-cell{display:flex;align-items:center;justify-content:space-between;gap:10px;min-width:0;
    padding:9px 13px;border-radius:11px;background:var(--surface-2);
    border:1px solid var(--border-soft);transition:border-color .12s,background .12s}
  .sc-cell-name{font-size:12.6px;font-weight:600;color:var(--ink);min-width:0;
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .sc-cell-val{font-family:var(--mono);font-size:13.2px;color:var(--ink-dim);white-space:nowrap}
  .sc-better{color:var(--ink-faint);font-weight:400;font-size:11.4px}
  .sc-cell.sc-active{background:color-mix(in srgb,var(--accent) 8%,var(--surface-2));
    border-color:color-mix(in srgb,var(--accent) 45%,transparent)}
  .sc-cell.sc-active .sc-cell-name,.sc-cell.sc-active .sc-cell-val{font-weight:700}
  .sc-cell.sc-click{cursor:pointer}
  .sc-cell.sc-click:hover{border-color:color-mix(in srgb,var(--accent) 55%,transparent);
    background:color-mix(in srgb,var(--accent) 5%,var(--surface-2))}
  @media (max-width:1000px){ .sc-bar-row{grid-template-columns:1fr} }

  /* Здесь было правило .sc-hint — подпись под строкой сценариев. Сама подпись убрана
     2026-08-01 («там вообще ничего не нужно»), правило удалено вместе с ней. */

  /* Косметический грейд (2026-08-01) — три кнопки над чек-листом деталей. Суммы вычета видны
     прямо на кнопках, чтобы продавец выбирал состояние, а не вспоминал цифры. */
  .grade-row{display:grid;grid-template-columns:repeat(3,1fr);gap:7px;padding:2px 12px 10px}
  .grade-btn{display:flex;flex-direction:column;align-items:flex-start;gap:2px;
    padding:8px 10px;border-radius:10px;cursor:pointer;text-align:left;
    background:var(--surface);border:1px solid var(--border-soft);
    font-family:var(--sans);transition:border-color .12s,background .12s}
  .grade-btn:hover{border-color:color-mix(in srgb,var(--accent) 50%,transparent)}
  .grade-btn .grade-name{font-size:11.6px;font-weight:650;color:var(--ink)}
  .grade-btn .grade-cut{font-family:var(--mono);font-size:11.6px;color:var(--ink-faint)}
  .grade-btn.is-on{background:color-mix(in srgb,var(--accent) 8%,var(--surface));
    border-color:color-mix(in srgb,var(--accent) 45%,transparent)}
  .grade-btn.is-on .grade-name{font-weight:700}
  .grade-btn.is-on .grade-cut{color:var(--danger)}

  .price-edit{display:inline-flex;align-items:center;gap:5px}
  .price-edit input,.field-input{
    font-family:var(--mono);font-size:13px;font-weight:600;color:var(--ink);background:var(--input-bg);
    border:1px solid var(--border);border-radius:8px;padding:7px 10px;outline:none;transition:border-color .14s}
  .price-edit input{width:100px;text-align:right}
  .price-edit input:focus,.field-input:focus{border-color:var(--accent);background:var(--input-focus-bg)}
  .price-edit input::placeholder,.field-input::placeholder{color:var(--ink-faint)}
  .price-edit .cur{color:var(--ink-faint);font-family:var(--mono);font-size:12.6px}

  select.field-input{appearance:none;font-family:var(--sans);font-weight:500;padding-right:26px;
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%238B879A' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat:no-repeat;background-position:right 9px center;cursor:pointer}
  input:disabled,select:disabled{background:var(--surface-2);color:var(--ink-faint)}

  /* Одна колонка: карточки "Выкуп"/"Trade-IN" теперь стоят СТОЛБИКОМ в третьей колонке
     .calc-top-grid (см. #calcOffers в renderCalc), а не в ряд под ней. Отступы — как у
     остальных внутренностей .calc-subcard (14px по бокам), а не 20px как у прежнего
     полноширинного блока. */
  /* Второй заголовок внутри той же карточки (правая колонка: «Выкуп и трейд-ин», ниже —
     «Доплата и заработок», 2026-07-31). Тонкая линия сверху отделяет блоки, не разбивая
     карточку на две. */
  .calc-sub-split{border-top:1px solid var(--border-soft);margin:2px 14px 0;padding-top:12px!important}
  .calc-offers{display:grid;grid-template-columns:1fr;gap:11px;padding:6px 14px 14px}
  .offer-card{border-radius:14px;padding:15px 17px;border:1px solid var(--border-soft);background:var(--surface)}
  .offer-card.offer-tradein{position:relative;background:var(--accent-soft-bg);border:1px solid transparent;
    background-image:linear-gradient(var(--surface),var(--surface)),linear-gradient(120deg,var(--accent),var(--arc));
    background-origin:border-box;background-clip:padding-box,border-box}
  .offer-label{font-size:11.6px;color:var(--ink-dim);font-weight:650;margin-bottom:6px}
  .offer-value{font-family:var(--mono);font-size:22px;font-weight:700;color:var(--ink);letter-spacing:-.01em}
  .offer-note{font-family:var(--mono);font-size:10.4px;color:var(--ink-faint);margin-top:6px}
  .calc-warn{margin:0 20px 10px;padding:11px 14px;border-radius:10px;background:var(--danger-soft-bg);
    border:1px solid var(--danger-soft-border);color:var(--danger);font-size:12px;line-height:1.5}
  .calc-hint{margin:2px 20px 20px;padding:12px 14px;border-radius:10px;background:var(--surface-2);
    border:1px dashed var(--border);color:var(--ink-faint);font-size:12px}

  .pill{display:inline-flex;align-items:center;gap:5px;font-size:10.6px;font-weight:700;
    padding:4px 9px;border-radius:999px;border:1px solid transparent;white-space:nowrap}
  .pill-part{background:var(--surface-2);color:var(--ink-dim);border-color:var(--border)}
  .pill-diagnostic{background:var(--warning-soft-bg);color:var(--warning);border-color:var(--warning-soft-border)}
  .pill-reference{background:var(--border-soft);color:var(--ink-faint);border-color:var(--border)}
  select.js-type.pill-part{background-color:var(--surface-2);color:var(--ink-dim)}
  select.js-type.pill-diagnostic{background-color:var(--warning-soft-bg);color:var(--warning);border-color:var(--warning-soft-border)}
  select.js-type.pill-reference{background-color:var(--border-soft);color:var(--ink-faint)}

  /* Здесь были .table-toolbar (панель над таблицей) и .sync-note. Обе больше не используются:
     в .table-toolbar на вкладке «Оценка частей» к 2026-07-30 осталась одна кнопка
     "+ добавить строку", прижатая распоркой к правому краю, — то есть целая полоса 36+12px
     держалась ради одной кнопки в пустоте (третья из обведённых владельцем полос). Кнопка
     переехала последней строкой внутрь самой таблицы (.add-row ниже), панель удалена из
     разметки renderParts. .sync-note осталась без разметки ещё раньше. */
  .alisa-table-wrap{background:var(--surface);border:1px solid var(--border-soft);border-radius:var(--radius);
    box-shadow:var(--shadow-md);overflow:auto}
  table.alisa-data-table{border-collapse:separate;border-spacing:0;width:100%;min-width:640px;table-layout:fixed}
  /* white-space:normal вместо nowrap (2026-08-01, владелец: «когда меняется ширина столбиков при
     ужатии, названия столбиков перекрываются — давай сделаем так, чтобы названия смещались в две
     строки, если ширина слишком узкая»). При nowrap длинный заголовок не переносился, а вылезал
     из своей ячейки и наезжал на соседний. Теперь он переносится по словам; overflow-wrap ловит
     случай, когда даже одно слово длиннее колонки, а vertical-align:bottom держит все заголовки
     на одной линии, независимо от того, в одну они строки или в две. */
  table.alisa-data-table thead th{position:sticky;top:0;z-index:2;background:var(--surface-2);
    text-align:left;font-family:var(--mono);font-size:10px;text-transform:uppercase;letter-spacing:.08em;
    color:var(--ink-faint);padding:10px 13px;border-bottom:1px solid var(--border);
    white-space:normal;overflow-wrap:anywhere;line-height:1.35;vertical-align:bottom}
  /* Кусочки заголовка, которые нельзя рвать переносом (например «б/у» — слэш иначе становится
     местом разрыва, и получается «Б/» на одной строке и «У» на следующей). */
  table.alisa-data-table thead th .nb{white-space:nowrap}
  /* Строки «Оценки устройств» держит снимок модели, поэтому вертикальные отступы ячеек здесь
     поджаты с общих 7px до 5px (2026-08-01, «очень большое расстояние от позиции до позиции») —
     на других таблицах Алисы это не сказывается. */
  #marketTableWrap table.alisa-data-table td{padding-top:5px;padding-bottom:5px}
  .alisa-col-resize-handle{position:absolute;top:0;right:0;width:9px;height:100%;cursor:col-resize;
    z-index:6;touch-action:none}
  .alisa-col-resize-handle::after{content:'';position:absolute;top:0;bottom:0;right:2px;width:2px;
    background:transparent;transition:.12s}
  .alisa-col-resize-handle:hover::after,.alisa-col-resize-handle.active::after{background:var(--accent)}
  table.alisa-data-table td{padding:7px 13px;border-bottom:1px solid var(--border-soft);vertical-align:middle}
  table.alisa-data-table tbody tr:hover{background:var(--surface-2)}
  table.alisa-data-table tbody tr:last-child td{border-bottom:0}
  .row-model-cell{display:flex;align-items:center;gap:9px;font-weight:600;font-size:12.6px}
  /* Снимок в списке «Оценки устройств»: 22 → 40 → 52 → 44px. Последний шаг назад — 2026-08-01,
     владелец: «очень большое расстояние от позиции до позиции». Именно фото задаёт высоту строки
     (всё остальное в ячейке ниже), и на 52px строка вырастала до 81px против 46px в «Оценке
     частей». На 44px строка ≈ 58px: снимок по-прежнему вдвое крупнее исходного, а список снова
     читается как список. contain вместо cover — круглая обрезка съедала верх и низ устройства. */
  .row-model-cell img{width:44px;height:44px;border-radius:50%;object-fit:contain;padding:2px;
    background:var(--surface-2);border:1px solid var(--border-soft);flex:none}
  /* Снимок модели в «Оценке устройств» стал кнопкой — клик открывает выбор файла (2026-07-31). */
  .model-img-btn{padding:0;border:0;background:none;cursor:pointer;flex:none;display:inline-flex;
    align-items:center;justify-content:center;border-radius:50%;transition:box-shadow .12s,opacity .12s}
  .model-img-btn:hover{box-shadow:0 0 0 2px color-mix(in srgb,var(--accent) 45%,transparent);opacity:.9}
  .model-img-none{width:44px;height:44px;border-radius:50%;display:inline-flex;align-items:center;
    justify-content:center;background:var(--surface-2);border:1px dashed var(--border);
    color:var(--ink-faint);font-size:13px;line-height:1}
  /* Название модели — переносящееся поле (2026-08-01): при узкой колонке уходит на вторую строку,
     а не прячет хвост. resize:none — ручку растягивания у textarea показывать незачем, высотой
     управляет autosizeModelNames(); overflow:hidden убирает полосу прокрутки, из-за которой
     scrollHeight в той же функции дал бы лишние пиксели. */
  .row-model-cell .js-model-name{font-weight:600;flex:1;min-width:0;resize:none;overflow:hidden;
    line-height:1.35;font-family:var(--mono);word-break:break-word;display:block}
  .table-price-input{width:100%;min-width:88px}
  .table-text-input{width:100%}
  /* Здесь жил отдельный набор стилей .market-cell-input — безрамочные поля «Оценки устройств»,
     сделанные в июле «на манер м.Тайм». Удалены 2026-08-01: владелец попросил применить к этой
     вкладке оформление «Оценки частей», и поля вернулись к общим .field-input/.price-edit —
     с рамкой в покое и знаком «₽» справа. Ячейка «Разница» — просто контейнер (класс .diff-cell
     остался: по нему обработчик находит её для пересчёта), а вид ей задаёт .part-used-price
     внутри, ровно как колонке «Цена б/у» в «Оценке частей». */
  /* Колонки «% от AIDE» и «Цена б/у» — владелец, 2026-07-30 (см. partUsedPrice в app.js).
     У поля процента свой min-width: общий 88px не давал столбцу сжиматься ручкой изменения
     ширины уже колонки-заголовка, а число там максимум трёхзначное. Расчётная цена — не поле
     ввода, а текст: её нельзя править напрямую, она всегда произведение цены AIDE на процент,
     поэтому оформлена моноширинным акцентом, чтобы визуально отличаться от вводимых цен. */
  .js-pct{min-width:52px}
  /* 2026-07-31, вместе с переходом таблицы частей с 7 колонок на 9. .price-edit — inline-flex со
     shrink-to-fit шириной, а у поля внутри min-width:88px, поэтому вся связка «поле + ₽» всегда
     занимала ~113px независимо от того, сколько места есть в ячейке. Пока столбцы были по 126px
     это не было видно; на 112 и 92px поле начало вылезать в соседнюю ячейку, и рамка «% от AIDE»
     перечёркивала число в «Цене б/у». Растягиваем связку по ширине ячейки и разрешаем полю
     сжиматься (min-width:0 обязателен: у flex-элемента по умолчанию min-width:auto, и он не даёт
     сжаться ниже min-width самого input). Правило нарочно ограничено таблицами Алисы — те же
     классы носят поля калькулятора и форм, где ширина по содержимому уместна. */
  table.alisa-data-table td > .price-edit{display:flex;width:100%;min-width:0}
  table.alisa-data-table td > .price-edit .table-price-input{flex:1 1 auto;min-width:0}
  .part-used-price{font-family:var(--mono);font-size:12.8px;font-weight:700;color:var(--ink)}
  .part-used-price::after{content:' ₽';font-weight:400;color:var(--ink-faint)}
  .row-del{appearance:none;border:1px solid var(--border);background:transparent;color:var(--ink-faint);
    width:26px;height:26px;border-radius:8px;cursor:pointer;font-size:14px;line-height:1;
    display:flex;align-items:center;justify-content:center;transition:.14s}
  .row-del:hover{border-color:var(--danger);color:var(--danger)}
  /* Владелец, 2026-07-30: "на вкладке оценка частей сдвинь крестик 'удалить позицию' левее он
     сейчас слишком близко к краю". Крестик — единственное содержимое последней ячейки, а у той
     были обычные td-паддинги 7px/13px, так что от кнопки до края таблицы оставалось 13px.
     .row-del — блочный flex-бокс фиксированной ширины 26px, поэтому он прижат к левому краю
     content-box ячейки: сдвинуть его влево = уменьшить padding-left и увеличить padding-right.
     Ширина самой колонки поднята 40 -> 62px (см. th в renderParts), иначе двигать было бы некуда. */
  #partsTableWrap table.alisa-data-table tr:not(.add-row) td:last-child{ padding-left:8px; padding-right:28px; }
  /* Последняя строка таблицы — кнопка "+ добавить строку" (см. fillPartsBody в app.js).
     position:sticky;bottom:0 — чтобы кнопка не уезжала за нижний край при прокрутке длинного
     списка: она всегда видна у нижней границы карточки, как шапка всегда видна у верхней
     (thead th{position:sticky;top:0} выше). Работает потому, что у таблицы
     border-collapse:separate — на collapse sticky у ячеек не действует.
     Фон обязателен и непрозрачен: прилипшая строка проезжает поверх остальных.
     tbody tr:hover красит всю строку — для этой строки подсветка не нужна, кнопка подсвечивается
     сама. */
  /* Растягиваемая таблица: высота 100% от карточки (её карточке задаёт fitTableWrapHeight).
     Излишек высоты уходит в .fill-row — единственную строку с процентной высотой, — и строка с
     кнопкой оказывается прижата к нижнему краю карточки. Класс ставится точечно (сейчас — только
     таблице деталей): у таблицы без распорки height:100% размазал бы излишек по обычным строкам,
     и они стали бы неоправданно высокими. */
  table.alisa-table-stretch{height:100%}
  tr.fill-row td{padding:0;border-bottom:0}
  table.alisa-data-table tbody tr.fill-row{height:100%}
  table.alisa-data-table tbody tr.fill-row:hover{background:transparent}
  table.alisa-data-table tr.add-row td{position:sticky;bottom:0;z-index:2;
    background:var(--surface);border-top:1px solid var(--border-soft);border-bottom:0;
    padding:8px 13px}
  table.alisa-data-table tbody tr.add-row:hover{background:transparent}
  .add-btn{appearance:none;border:1px dashed var(--border);background:transparent;color:var(--ink-dim);
    font-family:var(--sans);font-size:12.4px;font-weight:600;padding:8px 14px;border-radius:10px;
    cursor:pointer;transition:.14s;display:inline-flex;align-items:center;gap:6px}
  .add-btn:hover{border-color:var(--accent);color:var(--accent)}
  .no-results{padding:30px 20px;text-align:center;color:var(--ink-faint);font-size:12.8px}

@media (max-width:860px){
  .alisa-main-content{padding:0}
  .calc-offers{grid-template-columns:1fr}
  table.alisa-data-table{min-width:560px}
}

/* =====================================================================================
   БРЕНДБУК XFIX — приведение вкладок XFIX к фирменному стилю (владелец, 2026-07-30:
   «пройдись еще раз внимательно по вкладкам XFIX и проверь стиль и цвета, нужно чтобы
   все было по брэндбуку. Внимание! не задень вкладки м.Тайм и Алисы»).

   ВСЕ правила этого раздела начинаются с html:not([data-skin]) — и это не стилистика,
   а гарантия. switchView (app.js, ~4691) вешает на <html> data-skin="mtime" или
   data-skin="alisa" на соответствующих вкладках и снимает атрибут на вкладках XFIX:
       if (skin) document.documentElement.setAttribute('data-skin', skin);
       else      document.documentElement.removeAttribute('data-skin');
   Значит html:not([data-skin]) — это ровно множество вкладок XFIX, и ни одно правило
   отсюда физически не может сработать на м.Тайм или Алисе. Раздел лежит в конце файла,
   чтобы выигрывать у одноимённых правил темы по порядку, а не только по весу.

   Источник требований — брендбук XFIX:
     с. 9  — палитра ровно из четырёх цветов: #39BB3B, #FFFFFF, #000000, #D2D2D2.
             «Зеленый цвет используется для акцента» — то есть зелёный это акцент,
             а не один из десяти равноправных оттенков;
     с. 10 — фирменный шрифт Wix Madefor Text (теперь свой, см. assets/xfix-fonts.css);
     с. 15 — «Разрядка используется только для написания текста КАПСОМ. Прописные буквы
             всегда разряжаются, строчные — никогда»;
     с. 16 — интерлиньяж: для цифровых носителей ≈125% от кегля;
     с. 75-82 — цифровые макеты: белое или чёрное доминирует, зелёный работает точечно,
             в том числе как обводка (кнопка «Узнать больше» — зелёная рамка + зелёный
             текст на белом). Отсюда приём «заливка против обводки» ниже.
   ===================================================================================== */

/* --- с. 16, интерлиньяж ---------------------------------------------------------------
   Раньше line-height на body не было вовсе. Пока грузится свой Wix Madefor Text, у него
   собственная метрика даёт ровно 1.26 — то есть верные 125% уже были, но только пока
   шрифт свой. Фиксируем то же число явно: при подстановке системного шрифта (первые
   миллисекунды, офлайн, отключённые шрифты) межстрочное расстояние остаётся брендовым.
   Значение подобрано равным текущему рендеру, поэтому вёрстка не сдвигается. */
html:not([data-skin]) body{ line-height:1.26; }

/* --- с. 15, разрядка ------------------------------------------------------------------
   Было h1..h4{letter-spacing:-0.01em} — отрицательный трекинг на заголовках, которые
   набраны строчными («Сделки», «Оценка устройств»). Брендбук это прямо запрещает:
   строчные не разряжаются и не поджимаются. Возвращаем нормальный трекинг. */
html:not([data-skin]) h1,
html:not([data-skin]) h2,
html:not([data-skin]) h3,
html:not([data-skin]) h4{ letter-spacing:0; }

/* Обратная половина того же правила: подписи КАПСОМ обязаны быть разряжены. По коду они
   стояли на .03-.04em — это техническая компенсация, а не разрядка, глазом её нет.
   .08em — видимая разрядка на 11-13px. */
html:not([data-skin]) .kpi .kpi-label,
html:not([data-skin]) .dash-card h3,
html:not([data-skin]) .dash-hidden-bar-label,
html:not([data-skin]) .spend-chart-total h4,
html:not([data-skin]) .form-section legend,
html:not([data-skin]) .photos-heading,
html:not([data-skin]) .total-chip span{ letter-spacing:.08em; }

/* Заголовки таблиц — тоже капс, но ширина колонок фиксирована (table-layout:fixed,
   .deals-table min-width:1450px), и длинные ярлыки уже балансируют на грани переноса
   на вторую строку. Поэтому здесь разрядка меньше — .06em: она читается, но не
   утаскивает заголовки в лишнюю строку и не меняет высоту шапки. */
html:not([data-skin]) .deals-table th,
html:not([data-skin]) .price-guide-table th{ letter-spacing:.06em; }

/* --- с. 9, ярлык канала ---------------------------------------------------------------
   Владелец просил «по цветам синий для мтайм и зеленый для xfix» — договорённость в силе,
   меняется не смысл, а сами оттенки. Было #d7f4e3/#0f7a3d — зелёный «вообще», из палитры
   XFIX он не выводится. Теперь XFIX-ярлык берёт уже существующую пару, посчитанную от
   фирменного #39BB3B. Синий м.Тайма — это фирменный цвет м.Тайма (#6E61ED, тот же, что
   в его скине), а не случайный Tailwind blue #1d4ed8: обозначать чужой бренд его же
   цветом честнее, чем произвольным. */
html:not([data-skin]) .channel-badge-xfix{ background: var(--accent-soft-bg); color: var(--accent-soft-text); }
html:not([data-skin]) .channel-badge-mtime{ background:#eeedfd; color:#4b3fd4; }
html:not([data-skin])[data-theme="dark"] .channel-badge-xfix{ background: var(--accent-soft-bg); color: var(--accent-soft-text); }
html:not([data-skin])[data-theme="dark"] .channel-badge-mtime{ background: rgba(110,97,237,.20); color:#a79df7; }

/* --- с. 9 + с. 75-82, статусы ---------------------------------------------------------
   Самая заметная небрендовая поверхность вкладок XFIX: десять статусов были раскрашены
   десятью разными тонами — индиго, янтарь, бирюза, розовый, фиолетовый, небесный. При
   палитре из четырёх цветов и правиле «зелёный — акцент» это ровно то, чего брендбук
   не допускает: акцент перестаёт быть акцентом, когда вокруг ещё девять акцентов.

   Пересобрано не «в серый», а в три смысловых группы, как на цифровых макетах бренда:
     • процесс (черновик → оффер → ремонт → в работе → склад → м.Тайм) — лестница
       нейтральных ступеней от почти белого к чёрному. Различаются светлотой, а
       не тоном; наконец задействован четвёртый цвет палитры #D2D2D2, который до сих
       пор был объявлен в :root и не использован нигде;
     • результат заработан (продано, выкуплено) — фирменный зелёный;
     • утрата (разобрано, сломано) — красный.
   Внутри групп пары различаются заливкой против обводки — приём со с. 79 брендбука
   (там зелёная кнопка-обводка рядом с зелёной кнопкой-заливкой). Это же сохраняет
   требование владельца от 2026-07-27: «Сломано» и «Разобрано» по-прежнему не сливаются
   — одно залито, другое обведено.

   Обводка сделана inset box-shadow, а не border: у .status-pill нет рамки, и настоящий
   border добавил бы 2px к размеру пилюли и сдвинул бы строки таблиц. */
html:not([data-skin]) .status-draft{         background:#f5f5f6; color:#6e6e75; box-shadow:inset 0 0 0 1px #ebebed; }
html:not([data-skin]) .status-offer{         background:#ffffff; color:#000000; box-shadow:inset 0 0 0 1px #000000; }
html:not([data-skin]) .status-repair{        background:#ececee; color:#3a3a40; }
html:not([data-skin]) .status-in_use{        background:#e0e0e2; color:#2c2c31; }
html:not([data-skin]) .status-stock{         background:var(--xfix-gray); color:#17171a; }
html:not([data-skin]) .status-incoming_mtime{ background:#ffffff; color:#4a4a52; box-shadow:inset 0 0 0 1px #c9c9cd; }
html:not([data-skin]) .status-stock_mtime{   background:var(--xfix-black); color:var(--xfix-white); }
html:not([data-skin]) .status-sold{          background:var(--accent-soft-bg); color:var(--accent-soft-text); }
html:not([data-skin]) .status-traded_in{     background:#ffffff; color:var(--xfix-green-dark); box-shadow:inset 0 0 0 1px var(--xfix-green); }
html:not([data-skin]) .status-disassembled{  background:#fbe9e9; color:#b3312f; }
html:not([data-skin]) .status-broken{        background:#ffffff; color:#b3312f; box-shadow:inset 0 0 0 1px #e3aeae; }

/* Пилюли комплектации переиспользуют переменные статусов (см. .kit-* выше), но берут их
   через var(), а не через классы .status-*, поэтому новые правила до них не долетают.
   Повторяем ту же тройку явно, сохраняя исходное соответствие смыслов:
   «Разукомплектовать» = как ремонт, «Укомплектовать» = как склад XFIX, «Укомплектовано»
   = как продано. */
html:not([data-skin]) .kit-need_dekit{ background:#ececee; color:#3a3a40; }
html:not([data-skin]) .kit-need_kit{   background:var(--xfix-gray); color:#17171a; }
html:not([data-skin]) .kit-done{       background:var(--accent-soft-bg); color:var(--accent-soft-text); }

/* Тёмная тема — отдельно выбранные ступени, а не автоматически инвертированные светлые:
   лестница нейтралей строится от фона карточки вверх, а «глубокая» ступень (склад м.Тайм)
   становится белой заливкой, потому что чернее фона уже некуда. */
html:not([data-skin])[data-theme="dark"] .status-draft{         background:#232327; color:#a6a6ae; box-shadow:inset 0 0 0 1px #2f2f35; }
html:not([data-skin])[data-theme="dark"] .status-offer{         background:transparent; color:#f2f2f5; box-shadow:inset 0 0 0 1px #f2f2f5; }
html:not([data-skin])[data-theme="dark"] .status-repair{        background:#2e2e34; color:#c8c8d0; }
html:not([data-skin])[data-theme="dark"] .status-in_use{        background:#3a3a41; color:#dcdce2; }
html:not([data-skin])[data-theme="dark"] .status-stock{         background:#55555d; color:#ffffff; }
html:not([data-skin])[data-theme="dark"] .status-incoming_mtime{ background:transparent; color:#b8b8c0; box-shadow:inset 0 0 0 1px #55555d; }
html:not([data-skin])[data-theme="dark"] .status-stock_mtime{   background:var(--xfix-white); color:var(--xfix-black); box-shadow:none; }
html:not([data-skin])[data-theme="dark"] .status-sold{          background:var(--accent-soft-bg); color:var(--accent-soft-text); }
html:not([data-skin])[data-theme="dark"] .status-traded_in{     background:transparent; color:#5fd473; box-shadow:inset 0 0 0 1px var(--xfix-green); }
html:not([data-skin])[data-theme="dark"] .status-disassembled{  background:#3a1c1c; color:#ea7c79; }
html:not([data-skin])[data-theme="dark"] .status-broken{        background:transparent; color:#ea7c79; box-shadow:inset 0 0 0 1px #6d3231; }
html:not([data-skin])[data-theme="dark"] .kit-need_dekit{ background:#2e2e34; color:#c8c8d0; }
html:not([data-skin])[data-theme="dark"] .kit-need_kit{   background:#55555d; color:#ffffff; }
html:not([data-skin])[data-theme="dark"] .kit-done{       background:var(--accent-soft-bg); color:var(--accent-soft-text); }

/* Выпадающий список статуса в модалке носит те же классы .status-*, но по каскаду до сих
   пор оставался нейтральным (.status-badge-select объявлен ниже .status-* и с тем же весом,
   его background-color перекрывал цвет статуса). Новые правила выше весят больше и случайно
   бы его перекрасили — это не входило в задачу «привести цвета к брендбуку», поэтому
   поведение контрола фиксируем ровно тем, каким оно было. */
html:not([data-skin]) select.status-badge-select{
  background: var(--surface-2); color: var(--text); box-shadow:none;
}

/* Плотность списков. Свой файл шрифта отличается от того, что раздавал Google Fonts,
   вертикальными метриками: line-height:normal у него 1.26, у гугловской сборки было 1.149.
   Из-за одного этого строки таблиц стали бы на 3px выше и в окно помещалось бы на строку
   меньше — а владелец за плотность («опять место не задействованное снизу»). Интерлиньяж
   с. 16 брендбука — правило для НАБОРА, для читаемого текста; строка таблицы — это данные,
   а не абзац. Поэтому 125% остаётся на body, а ячейки списков прибиты к прежнему значению:
   1.15 даёт ровно ту же высоту строки, что была до замены шрифта (53/66px, проверено). */
html:not([data-skin]) .deals-table td,
html:not([data-skin]) .deals-table th{ line-height:1.15; }
