/* Оформлення веб-версії bud_pro.
 *
 * ГОЛОВНЕ ПРАВИЛО: кольори тут — це СЕМАНТИКА, перенесена з Excel, а не декор.
 * У VBA колір ніс зміст (mod_30:1169, 1787-1798):
 *   синій обсяг    — перекритий користувачем (custom_quantity + quantity_link_id)
 *   сірий текст    — рядок складу, а не робота
 *   жовтий фон     — комірка, яку в Excel можна редагувати
 *   жирний рядок   — розгорнута робота
 * Тому класи названі за змістом (.qty-custom, .row-child, .cell-editable),
 * а не за виглядом (.blue, .grey).
 */

:root {
  --ink: #202020;
  --ink-muted: #787878;
  --line: #c8c8c8;
  /* СІТКА ТАБЛИЦЬ — ЧОРНА, і це окрема змінна, а не той самий `--line`.
   * Сірі лінії зливалися з фоном заголовків груп, і на широкій специфікації око
   * втрачало, у якому саме стовпці стоїть число. Чорне обрамлення лишається
   * ТІЛЬКИ на комірках таблиць: обвести ним ще й кожну дрібну рамку (номер
   * рядка, папку, панель) означало б перетворити екран на решітку, у якій
   * головне знову не видно. */
  --grid-line: #000;
  --header-bg: #ddebf7;
  --editable-bg: #fffcdc;
  /* РОБОТА Й ЇЇ СКЛАД РОЗРІЗНЯЮТЬСЯ ТОНОМ РЯДКА, а не лише кольором тексту.
   *
   * ЧОМУ ОДНОГО СВІТЛО-ЖОВТОГО СКЛАДУ ВИЯВИЛОСЯ ЗАМАЛО. Поля вводу залиті
   * `--editable-bg`, і їх у рядку більшість — найменування, обсяг, ціни, норми.
   * Тобто обидва види рядка на екрані це насамперед смуга жовтих полів, і
   * різниця фонів під ними (біле проти майже білого кремового) зникала між
   * ними зовсім: робота й ресурс виглядали однаково.
   *
   * Тому темніє РЯДОК РОБОТИ, а не світлішає склад: світлішати йому нікуди —
   * він і так майже білий. Тепер робота це сіро-тепла смуга, склад — кремова
   * майже біла, а жовті поля читаються на обох.
   *
   * Діє скрізь, де є розгортання: у редакторі документів і у відомості
   * актування. */
  --row-work-bg: #efece3;
  --child-bg: #fffdf0;
  --custom-fg: #0000ff;
  --accent: #0070f0;
  /* НЕБЕЗПЕЧНЕ / ВІД'ЄМНЕ. Доти змінної не було зовсім, а `var(--danger, …)`
   * вживалося з ДВОМА різними запасними — `#b00020` і `#c0392b`, — тобто один
   * і той самий сенс мав у системі два кольори. Оголошена тут, вона зводить їх
   * докупи; наявні запасні лишаються дійсними й нічого не ламають. */
  --danger: #c0392b;
  --selected-bg: #bdd7ee;
  --group-3: #e2efda;

  /* ЗАЛИВКА РІВНІВ СТРУКТУРИ — ДВІ РАМПИ, а не одна за глибиною.
   * Координати дві й вони незалежні: будівельна відповідає на «де», кошторисна
   * на «що це коштує». Спільна сіра шкала за глибиною їх зрівнювала, і в
   * мішаному порядку («Кошт·Р1 › Кошт·Р2 › Буд·Р1…») сказати, який заголовок з
   * якої структури, можна було лише прочитавши підпис.
   * Тепер будівельна синя, кошторисна зелена, а нижчі рівні світліші — тобто
   * колір несе І вимір, І глибину. */
  --bld-1: #8db3e2;
  --bld-2: #b8cce4;
  --bld-3: #dbe5f1;
  --bld-4: #eaf0f8;
  --est-1: #93cca4;
  --est-2: #c4e0cd;
  --est-3: #e2efda;
  --est-4: #f0f7ec;

  /* Крок відступу на один рівень вкладеності в деревах */
  --tree-step: 28px;

  /* РОЗМІР ШРИФТУ БЕРЕТЬСЯ ЗІ ШКАЛИ, А НЕ ПИШЕТЬСЯ ЧИСЛОМ.
   *
   * Нове число означає НОВИЙ РІВЕНЬ ЗНАЧЕННЯ, а не «тут хай буде трохи
   * дрібніше». Саме друге й відбувалося: «зменшити локально» — найдешевша дія в
   * цьому файлі, її роблять не думаючи, і в ньому завелося дев'ять різних
   * розмірів. Наслідок видно неозброєним оком: у блоці ціноутворення підпис
   * групи був на два пункти більший за суму в ТОМУ САМОМУ рядку, бо один узяв
   * клас зі шкали, а другий не взяв нічого.
   *
   * Заголовки сторінок (18/20/22px) до шкали не належать — вони поодинокі й
   * кожен свій.
   *
   * Перехід іде поступово: нові й перероблені правила беруть змінну, старі
   * числа лишаються, доки до них дійдуть руки. Щоб напрямок не забувся, їх
   * КІЛЬКІСТЬ тримає тест-храповик (`tests/test_findings.py`): опустити межу
   * можна будь-коли, підняти — ні. */
  --fs-micro: 10px;  /* позначки в рядку: лічильники, значки */
  --fs-note: 11px;   /* службовий дріб'язок: підказки, довідки під полем */
  --fs-dense: 12px;  /* щільні таблиці й панелі: специфікація, блоки, форми */
  --fs-ui: 13px;     /* підписи поза таблицями: панелі, режими, довідка */
  --fs-base: 14px;   /* текст сторінки — те саме, що в `body` */
  --fs-brand: 24px;  /* назва програми «єКабінет» поруч зі знаком — одна на екран */

  /* ВИСОТА ОРГАНІВ КЕРУВАННЯ — одна на всі ряди: вибір об'єкта, вкладки
   * режимів, кнопки вигляду структури, тулбар редактора. Доти кнопки тулбара
   * були на шість пікселів вищі за вибір об'єкта над ними, і три ряди поспіль
   * читалися як три різні за важливістю смуги, хоч поводяться однаково. */
  --ctl-h: 22px;
}

/* ---------- Відступ за рівнем вкладеності ---------- */
/* Один механізм на всі дерева: структури об'єкта, кошторисні розділи, папки
 * класифікатора. Рівень передається змінною `--depth` прямо в розмітці, тож
 * ГЛИБИНА НЕ ОБМЕЖЕНА — на відміну від колишніх класів `.depth-1…4`, де кожен
 * рівень потребував власного правила, а глибші за 4 виглядали однаково.
 *
 * Змінна `--depth` ставиться на рядок, а клас — на елемент, який реально треба
 * зсунути (комірку чи посилання). Так працює тому, що CSS-змінні успадковуються.
 *
 * СЕЛЕКТОРИ НАВМИСНО «ВАЖКІ». Місця, куди лягає цей клас, уже мають власний
 * `padding` скороченим записом: `.grid td` (0,1,1) для таблиць, `.node` і
 * `.folder` (0,1,0) для списків. При рівній вазі перемагає те правило, що нижче
 * у файлі, — тому тут вага свідомо більша, і порядок у файлі значення не має. */
.grid td.tree-indent,
a.tree-indent { padding-left: calc(6px + var(--depth, 0) * var(--tree-step)); }

* { box-sizing: border-box; }

body {
  margin: 0;
  font: 14px/1.45 "Segoe UI", system-ui, sans-serif;
  color: var(--ink);
  background: #fff;
}

/* ---------- Шапка й режими ---------- */
.app-header {
  background: #404040;
  color: #fff;
  padding: 10px 16px;
}
.app-header h1 { margin: 0 0 8px; font-size: 18px; letter-spacing: .04em; }

/* ---------- Знак програми в шапці ---------- */
/* Перше, що видно на будь-якому екрані: система відкривається за посиланням, і
 * знак тут відповідає на питання «куди я потрапив».
 *
 * БЕЗ ПЛАШКИ — ПРОСТО НА ТЕМНІЙ ШАПЦІ. Білу підкладку знак мав, поки в шапці
 * стояв лише логотип (чужі логотипи роблять під білий фон); синю — один день,
 * щоб жовтий напис не здавався темнішим за будиночок. Від обох відмовились:
 * знак і назва стоять на тому самому темному полі, що й решта шапки.
 *
 * СИНЬОГО В ЗНАКУ НЕМАЄ ЗОВСІМ. Синьо-жовте поєднання читається як державний
 * сайт, а система не державна. Кольори знака — жовтий #ffd700 і зелений
 * #2e7d32 (див. `logo.svg` і `.ekab-e` нижче). */
.brand {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  flex: 0 0 auto;
  min-width: 0;
  max-width: 480px;
  color: #ffd700;
  font-weight: 700;
  letter-spacing: .01em;
}
/* ЛОГОТИП — 52px (було 83px): праворуч від нього тепер назва «єКабінет»
 * текстом, і знак лише супроводжує її, а не несе сам. Межа ширини зменшена в
 * тій самій пропорції (255 -> 160) — пояснення нижче.
 *
 * Історія: 110px -> 83px, коли весь блок зменшували на чверть.
 *
 * `max-width` мусить БУТИ БІЛЬШИМ за висоту в стільки разів, у скільки логотип
 * широкий: при `object-fit: contain` завузька коробка не обрізає картинку, а
 * вписує її цілком — і задана висота тихо перетворюється на меншу фактичну.
 * Саме це й сталося колись із межею 220px на знаку 3:1. Тому межа зменшена в
 * ТІЙ САМІЙ пропорції, що й висота (340 -> 255): 83px висоти лишаються для
 * логотипа аж до пропорції 3:1, як і раніше. Зменшити висоту, не чіпаючи межі,
 * було б безпечно, але тоді широкий банер і далі з'їдав би 340px ширини шапки.
 *
 * `flex-shrink: 0` не дає емблемі стискатися разом із назвою: розчавлений до
 * двох пікселів логотип гірший за його відсутність. */
.brand-logo {
  height: 52px;
  width: auto;
  max-width: 160px;
  flex: 0 0 auto;
  object-fit: contain;
  display: block;
}

/* ---------- Назва «єКабінет» ---------- */
/* Одна розмітка (`templates/_brand.html`) на шапку системи й сторінки заявки.
 * «є» — зелена на жовтій плашці, щоб назва читалася як «є кабінет». Кольори —
 * зі знака (`logo.svg`): жовтий #ffd700 і зелений #2e7d32.
 *
 * ЗЕЛЕНИЙ #2e7d32, А НЕ ТЕМНІШИЙ: той самий колір стоїть полем у знаку на
 * темно-сірій шапці, і темніший зелений там зливався б із фоном. На жовтому
 * жирна літера цього розміру читається з контрастом 3,7:1 — вище за 3:1,
 * потрібні великому тексту. */
.ekab-brand { font-weight: 700; letter-spacing: .01em; white-space: nowrap; }
.ekab-e {
  display: inline-block;
  margin-right: .1em;
  padding: 0 .18em .04em;
  border-radius: .24em;
  background: #ffd700;
  color: #2e7d32;
  line-height: 1.15;
}
/* «€» ЗАВВИШКИ ЯК МАЛЕНЬКА «є». Знак валюти в шрифті — висоти великої літери
 * (виміряно в Segoe UI Bold: «€» 72 одиниці, «є» 52), і на плашці він
 * виглядав великою літерою серед малих. 0,72 — рівно це співвідношення.
 * Зменшується ЛИШЕ знак: плашка тримає висоту рядка батьківського шрифту, тож
 * її розмір і місце на базовій лінії не змінюються.
 *
 * Зменшений знак ще й ВУЖЧИЙ за «є» (41 проти 48 одиниць). Різницю повертаємо
 * відступами — інакше плашка звужувалась би й читалась як інший знак. */
.ekab-e-glyph { font-size: .72em; padding: 0 .05em; }
.brand .ekab-brand { font-size: var(--fs-brand); }
/* До входу знак стоїть сам над заголовком сторінки, а не в ряду з навігацією. */
.brand-anon { margin-bottom: 10px; }

/* Вкладок «Налаштувань» вісім (люди, ролі, п'ять довідників, організація) — на
 * вузькому екрані вони мусять переноситися, а не виїжджати за край.
 *
 * ВИГЛЯД — ЯК У ВИБОРУ ОБ'ЄКТА (`.pick-inline`). Обидва ряди стоять в одному
 * місці шапки й означають те саме — «оберіть, з чим працюєте». Поки вкладки
 * були великими «папками» з заокругленим верхом, два сусідні ряди виглядали як
 * два різні механізми, хоча поводяться однаково. */
.modes { display: flex; flex-wrap: wrap; gap: 4px; font-size: 13px; }
.mode-tab {
  display: inline-flex;
  align-items: center;
  min-height: var(--ctl-h);
  padding: 0 8px;
  background: #ebebeb;
  color: #282828;
  text-decoration: none;
  border: 1px solid #969696;
  border-radius: 3px;
}
.mode-tab.is-active {
  background: var(--selected-bg);
  border-color: var(--accent);
  font-weight: 600;
}

main { padding: 14px 16px 40px; }

/* ---------- Хто увійшов ---------- */
/* Аналога в Excel немає: там усі ходили в БД спільним обліковим записом, тож
   питання «під ким я працюю» не існувало. */
/* Назва організації й кнопки розділів ліворуч, підпис користувача праворуч.
 *
 * `justify-content: space-between` тут був би помилкою: він розганяв би три
 * блоки по всій ширині, і кнопки опинялися б десь посеред екрана, відірвані від
 * назви. Тому все притиснуте вліво, а підпис користувача відсувається сам
 * (`margin-left: auto`).
 *
 * ВІДСТУП МІЖ НАЗВОЮ Й КНОПКАМИ — САНТИМЕТР. Доти його задавала порожня клітинка
 * всередині `.main-nav`, тобто він дорівнював ЦІЛІЙ кнопці й ріс разом із нею:
 * від найдовшого підпису розділу залежало, наскільки далеко ряд стоїть від
 * назви. Сантиметр — величина, яку видно лінійкою на екрані, і від
 * перейменування розділів вона не змінюється. */
.header-top {
  display: flex;
  flex-wrap: wrap;          /* на вузькому екрані підпис користувача піде вниз */
  justify-content: flex-start;
  align-items: center;
  gap: 8px 0;
  margin-bottom: 8px;
}
.header-top .whoami { margin-left: auto; padding-left: 16px; }
/* КУТ СЕАНСУ — ДВА РЯДКИ, ВИРІВНЯНІ ПРАВИМ КРАЄМ. Угорі компанія, під нею
 * людина з нагадуваннями й виходом (див. коментар у `base.html`). Правий край
 * спільний, тож назва компанії стоїть над «Вийти», а не над дзвіночком. */
.whoami {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
  font-size: var(--fs-dense);
  color: #d0d0d0;
  white-space: nowrap;
}
/* НА ПУНКТ БІЛЬШЕ за рядок під нею — і зі шкали: 12px -> 13px, тобто `--fs-ui`. */
.whoami-org { font-size: var(--fs-ui); }
/* П'ЯТЬ МІЛІМЕТРІВ — ОДИНИЦЕЮ, ЯКУ ВИДНО ЛІНІЙКОЮ, як сантиметр від назви в
 * `.header-top`. Один `gap` на весь рядок робить обидва проміжки однаковими:
 * дзвіночок ↔ ім'я й ім'я ↔ «Вийти». */
.whoami-user { display: flex; align-items: center; gap: 5mm; }
.whoami .btn { padding: 2px 10px; font-size: 12px; }

/* Дзвіночок нагадувань. Без насталих — приглушений і без числа: він має бути
   помітним рівно тоді, коли є про що нагадувати, а решту часу не смикати око. */
.bell {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  text-decoration: none;
  font-size: 14px;
  opacity: .45;
  color: #d0d0d0;
}
.bell:hover { opacity: .8; }
.bell.has-due { opacity: 1; }
.bell.has-due span {
  background: var(--danger);
  color: #fff;
  border-radius: 9px;
  padding: 0 6px;
  font-size: 11px;
  font-weight: 700;
  line-height: 16px;
}

/* ПОЗНАЧКА СТОЇТЬ У НИЖНЬОМУ РЯДУ ТУЛБАРА, поруч із «Ціноутворенням», а не по
   центру між рядами: кнопки в тулбарі розкладені рядами, і єдиний елемент, що
   висить між ними, читається як збій верстки. `flex-end` — це і є нижній ряд:
   тулбар вирівнює по низу найвищого блока, а найвищі тут пари на два ряди.

   ЖИРНА, бо це відповідь на питання «чому кнопок правки немає», і шукають її
   очима саме тоді, коли вони зникли. */
.status-readonly {
  align-self: flex-end;
  color: var(--ink-muted);
  font-size: 13px;
  font-weight: 700;
}

/* ---------- Адмінка ---------- */
.login-panel { max-width: 320px; margin: 40px auto; }
.login-panel label, .panel label { display: block; margin-bottom: 8px; }
.login-panel input { width: 100%; padding: 5px; }

/* Перелік допусків у конструкторі ролей і об'єктів у картці користувача */
.checklist { list-style: none; padding: 0; margin: 4px 0 10px; }
.checklist li { padding: 2px 0; }
.checklist label { display: flex; gap: 6px; align-items: center; margin: 0; }

/* Технічний ключ допуску поруч із підписом — щоб було видно, що саме вмикається */
.perm-key { color: var(--ink-muted); font-size: 11px; }

.tag {
  font-size: 11px;
  padding: 1px 6px;
  border-radius: 8px;
  background: #ebebeb;
  color: var(--ink-muted);
}
.is-muted { opacity: .55; }
.is-selected { background: var(--selected-bg); }

.message { color: #1f6f3f; }
.error { color: var(--danger, #b00020); }

/* ---------- Кнопки ---------- */
.toolbar { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-bottom: 12px; }
.toolbar-gap { width: 16px; }
/* ТУТ БУЛО `.toolbar-lines` — І ЙОГО ПРИБРАНО.
 *
 * Задум був такий: ліві блоки дій — сітки на два ряди (`.toolbar-pairs`), а
 * решта кнопок заввишки в один, тож при вирівнюванні по центру вони зависали
 * МІЖ рядами сусіднього блока й спільної лінії в ряду не лишалося. `stretch`
 * тягнув одиночні кнопки на всю висоту ряду.
 *
 * ЦІНА ВИЯВИЛАСЬ ВИЩОЮ ЗА ЗИСК. Розтягнута кнопка стає ВДВІЧІ вищою за
 * звичайну — і читається не як «вирівняно по лінії», а як роздута кнопка,
 * тобто як помилка верстки. Незбіг ліній помічає око тренованого верстальника;
 * кнопку заввишки в дві помічає кожен.
 *
 * Кнопки повертаються до `--ctl-h`, ряд — до `align-items: center` з `.toolbar`.
 * `.pair-tall` це не зачіпає: там форма справді займає два ряди СВОЄЇ сітки. */
/* Ряд кнопок ОДНАКОВОЇ ширини. Той самий прийом, що в кнопках папок і в
 * навігації шапки: колонки `1fr` у контейнері `max-content` виходять однакові
 * й рівні найдовшій. Це тримається саме, без числа, яке довелося б підганяти
 * після кожного перейменування кнопки. */
.toolbar-equal {
  display: inline-grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  width: max-content;
  max-width: 100%;
}
.toolbar-equal .btn { justify-content: center; }
/* ПАРИ КНОПОК — ДВА РЯДИ, СТОВПЕЦЬ НА ПАРУ. Ряд дій редактора був один і тягся
 * на весь екран: вісім кнопок поспіль, серед яких потрібну шукали очима зліва
 * направо. Пара — це те саме питання про два види рядка («+ Робота» /
 * «+ Ресурс») або два кроки однієї дії («Копіювати» / «Вставити»), тож
 * читається вниз, а ряд стає вдвічі коротшим.
 *
 * Ширина стовпця — за вмістом (`max-content`), а не рівними частками: підписи
 * тут різняться втричі («Пакет» проти «+ З бібліотеки роботу»), і рівні колонки
 * розтягнули б увесь ряд під найдовший. */
.toolbar-pairs {
  display: inline-grid;
  grid-template-rows: auto auto;
  grid-auto-flow: column;
  grid-auto-columns: max-content;
  gap: 3px 6px;
  max-width: 100%;
}
.toolbar-pairs > * { display: flex; }
.toolbar-pairs .btn { width: 100%; justify-content: center; }
/* Кнопка без пари займає обидва ряди — інакше під нею лишалася б дірка, у якій
   око шукало б неіснуючу другу дію. */
.toolbar-pairs .pair-tall { grid-row: span 2; align-items: stretch; }
/* РЯД ЗАДАНО ЯВНО. Автопотік по стовпцях кладе елементи згори вниз, тобто ряд
   кнопки залежить від того, скільки їх у блоці — а в режимі актування «Залишок»
   з'являється лише з обраним актом, і без нього АВР стрибав би у верхній ряд.
   Кнопка мусить стояти в тому самому ряду завжди: її шукають очима по лінії. */
.toolbar-pairs .pair-top { grid-row: 1; }
.toolbar-pairs .pair-bottom { grid-row: 2; }

/* ПИТАННЯ НАД ВІДПОВІДДЮ: список приймачів у верхньому ряду, кнопка дії — під
 * ним. Форма «наступної ланки» стояла в один ряд, і поруч із сусідніми парами
 * її список висів МІЖ їхніми рядами — ні на рівні верхньої кнопки, ні на рівні
 * нижньої.
 *
 * Ширину задає стовпець, тобто довший з двох елементів (це список), а кнопка
 * тягнеться під нього. Приховані поля форми (`csrf`, стан редактора, об'єкт)
 * рядів не займають: `input[type=hidden]` не малюється зовсім.
 *
 * Коли приймачів немає, списку немає теж — лишається один ряд, і кнопка стоїть
 * рівно там, де стояла доти. Окремого правила на цей випадок не треба. */
/* СЕЛЕКТОР З ТЕГОМ, І ЦЕ НЕ ПРИКРАСА: нижче стоїть `.inline-form { display:
 * inline }`, і при однаковій вазі перемагає ПІЗНІШЕ правило — сітка мовчки не
 * вмикалася, а стек тримався на тому, що його діти й так блокові. Ширина при
 * цьому не тримала нічого: кнопка тягнулася на всю ширину рядка. */
form.toolbar-stack {
  display: inline-grid;
  grid-auto-flow: row;
  gap: 3px;
  max-width: 100%;
  /* Той самий запобіжник, що й у `.main-nav`: без нього flex-ряд тулбара
     стискає стек нижче його вмісту, і список стає смужкою на два символи. */
  flex: 0 0 auto;
}
.toolbar-stack select, .toolbar-stack .btn { width: 100%; }
/* СТЕЛЯ ШИРИНИ: список сам розтягується під найдовший рядок, а під ним
   стоїть кнопка тієї ж ширини — тобто довгий номер документа роздував би
   обидва. За стелею рядок обрізається, і це нормальний вигляд списку:
   повний текст усе одно видно розкритим. */
.toolbar-stack select { max-width: 200px; }
/* Верхній ряд перевиставлення: поле націнки й список приймачів поруч, бо разом
   вони відповідають на одне питання — чим саме перевиставляємо. */
.rebill-what { display: flex; align-items: center; gap: 6px; min-width: 0; }
.rebill-what select { min-width: 0; }

/* ТУМБЛЕР РЕЖИМУ — не дія над рядком, а відповідь на питання «що я тут роблю».
   Тому він і стоїть окремо, під «Назад», і виглядає інакше за кнопки дій:
   увімкнений заливається акцентом на весь підпис, а не лише підсвічується. */
/* НАПИС ЧОРНИЙ, А НЕ БІЛИЙ. Білий на цьому синьому не читався: тумблер —
   найдовший підпис у ряду й водночас той, за яким перевіряють, у якому режимі
   екран. Тло світлішає до відтінку акценту, щоб чорний на ньому лишався
   чорним, а сам стан «увімкнено» тримає рамка й насичена лівá смуга. */
.btn-mode.is-active {
  background: #cfe0f5;
  border-color: var(--accent);
  box-shadow: inset 3px 0 0 var(--accent);
  color: #000;
  font-weight: 600;
}
/* КНОПКА АКТА-ПРИЙМАЧА, ПОКИ АКТА НЕМАЄ. Кольори перейшли сюди з позначки
 * `.act-target.is-empty`, яка стояла поруч із кнопкою й казала «оберіть акт»
 * замість неї. Тепер це каже сама кнопка, і червоною вона не «привертає увагу»,
 * а називає стан: решту кнопок у цю мить сховано, і натиснути на екрані більше
 * нема на що. */
.btn.is-act-empty {
  border-color: var(--danger, #b00020);
  background: #fdeaea;
  color: var(--danger, #b00020);
}
.btn.is-act-empty:hover { background: #fbdcdc; }
/* ОДНА ВИСОТА НА ВСІ РЯДИ КЕРУВАННЯ — `--ctl-h`.
 *
 * У редакторі їх три поспіль: вибір об'єкта, кнопки вигляду структури, тулбар
 * дій. Доти кнопка була 29px, а вибір об'єкта 23px, і ряди читалися як три
 * різні за важливістю смуги, хоч поводяться однаково.
 *
 * `inline-flex` замість `inline-block` — щоб `min-height` не просто резервував
 * місце, а центрував підпис у ньому. Через це ж потрібен `justify-content` у
 * сітках однакових кнопок нижче: `text-align: center` на flex-контейнер не
 * діє. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--ctl-h);
  padding: 0 10px;
  font-size: 13px;
  background: #ebebeb;
  color: #282828;
  border: 1px solid #969696;
  border-radius: 4px;
  text-decoration: none;
  cursor: pointer;
}
.btn:hover { background: #e0e0e0; }
.btn.is-active { background: var(--selected-bg); border-color: var(--accent); font-weight: 600; }
/* НЕАКТИВНА КНОПКА — ПРОСТО СІРА. `cursor: not-allowed` малював закреслене
 * коло, і виходило нерівно: на кнопках-формах воно було, а на сусідніх
 * кнопках-посиланнях (`.btn.is-disabled`, `pointer-events: none`) — ні. Тобто
 * один і той самий сірий ряд поводився двома різними способами.
 *
 * Знак заборони тут ще й перебільшує: кнопка не заборонена назавжди, їй просто
 * бракує виділеного рядка — про що каже підказка. Приглушеності досить. */
.btn[disabled] { opacity: .45; cursor: default; }

/* ---------- Каскад ---------- */
.cascade-level { margin-bottom: 18px; }

/* Фільтри — поруч, а не стовпчиком: так увесь вибір видно за один погляд.
 * На вузькому екрані вони самі переносяться.
 *
 * ШИРИНА КОЛОНКИ — ЗА НАЙДОВШИМ ЗНАЧЕННЯМ У НІЙ, не більше. Було
 * `repeat(auto-fit, minmax(220px, 1fr))`: колонки ділили всю ширину екрана
 * порівну, тож «Типи» з двома короткими словами розтягувалися на пів екрана.
 * Тепер `flex` без `grow` — кожна займає рівно стільки, скільки треба вмісту.
 *
 * Стабільність ширини забезпечує не CSS, а шаблон: значення з нульовим
 * лічильником не зникають, тож найдовший рядок у колонці той самий завжди. */
.cascade-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 0 18px;
  align-items: flex-start;
}
.cascade-filters .cascade-level { min-width: 0; }
.cascade-filters .pick-list { width: max-content; max-width: 280px; }
/* Довгі назви (повне ім'я контрагента, предмет договору) переносяться на другий
 * рядок замість того, щоб розсувати колонку або обриватися.
 *
 * Рядки СТИСЛІ: фільтрів чотири, значень у кожному буває з десяток, і зона
 * відбору не має з'їдати екран у документів, заради яких її й відкрили. */
.cascade-filters .pick {
  display: flex;
  gap: 8px;
  align-items: baseline;
  justify-content: space-between;
  padding: 0 7px;
  font-size: 13px;
  line-height: 1.5;
  white-space: normal;
  overflow-wrap: anywhere;
}
/* Заголовок фільтра теж підтягуємо — інакше стислі рядки під масивною
   плашкою виглядають як помилка верстки */
.cascade-filters .section-header {
  padding: 1px 7px;
  margin-bottom: 3px;
  font-size: 13px;
}
.cascade-filters .cascade-level { margin-bottom: 10px; }
/* Лічильник — скільки документів лишиться, якщо обрати саме це значення */
.pick-count {
  flex: 0 0 auto;
  font-size: 11px;
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}
.pick.is-selected .pick-count { color: inherit; }
/* Значення, яке не поєднується з рештою вибору: видно, що воно існує, але клік
   нічого не дав би — тому це не посилання */
.pick.is-unavailable { color: var(--ink-muted); opacity: .55; cursor: default; }
.pick.is-unavailable:hover { background: none; }

/* Рядок документа відкривається подвійним кліком, тож курсор має це підказувати */
.doc-list tbody tr { cursor: pointer; }

/* ЧЕРНЕТКА — ПРИГЛУШЕНИМ ТЕКСТОМ. У журналі поруч лежать папери, на які вже
 * спираються, і папери, яких ще ніби немає; статус написаний словом у своїй
 * колонці, але слово читають після того, як пробіглися рядком. Колір розрізняє
 * їх одразу.
 *
 * САМЕ КОЛІР, А НЕ `opacity`: прозорість гасить і підсвітку виділеного рядка, і
 * значки в ньому — тобто приглушувала б те, що приглушувати не просили.
 *
 * `td` І `a` ОКРЕМО: номер документа це посилання зі своїм кольором, і без
 * другого правила він лишався б синім у сірому рядку. */
.doc-list tbody tr.is-draft > td,
.doc-list tbody tr.is-draft > td > a { color: var(--ink-muted); }

/* ЗАТВЕРДЖЕНИЙ — ЖИРНИМ. Сірий колір чернетки сам по собі відрізняв слабо:
 * різниця між чорним і сірим помітна лише коли рядки поруч. Жирність працює
 * інакше — вона видно на самому рядку, без сусіда для порівняння. Разом виходять
 * три стани, які око розрізняє з першого погляду: приглушений (ще ніби немає),
 * звичайний (у роботі), жирний (на нього вже спираються). */
.doc-list tbody tr.is-approved > td { font-weight: 600; }

/* ОДИНИЦЯ ШИРИНИ — ДАТА.
 *
 * `31.12.2026` при 12px займає 57px, плюс відступи комірки й рамки (16px) —
 * 73px. Беремо 74px: рівно найширша дата, без запасу «про всяк випадок».
 *
 * Решта колонок виражена через цю одиницю, бо саме так їх і замовляли: номер —
 * як дата, сторони — по п'ять дат, договір — три, примітка — чотири. Записувати
 * це готовими числами (370px, 222px) означало б втратити зв'язок: змінився б
 * шрифт — і пропорції поїхали б непомітно. */
.doc-list { --jdate: 74px; }

/* Шрифт на 2 пункти менший за загальний (14px -> 12px). Шапка теж, інакше `th`
 * лишалися б більшими за дані під ними. */
/* Кегль — від `.grid` (див. правило там). Тут лишається лише сітка колонок. */

/* `table-layout: fixed` ПРАЦЮЄ ЛИШЕ ПРИ ЗАДАНІЙ ШИРИНІ таблиці — при `auto`
 * браузер відкидає фіксовану розкладку й рахує колонки за вмістом.
 *
 * Ширина задана ЧИСЛОМ (сума колонок), а не `100%`: при `100%` колонки
 * розтягуються на весь екран, і задана ширина перетворюється на пропорцію —
 * тобто на широкому моніторі дата була б не «шириною найширшої дати», а на
 * десяту частину ширшою. `max-width: 100%` лишає запобіжник для вузького
 * екрана: там усе стискається пропорційно, замість вилізти за край.
 *
 * Селектор `table.doc-list`, а не `.doc-list`: спільне правило `.grid` нижче
 * задає `width: 100%` з такою самою вагою, але стоїть ПІЗНІШЕ — і перемагало б,
 * розтягуючи таблицю на весь монітор. */
table.doc-list {
  table-layout: fixed;
  width: 1716px;
  max-width: 100%;
}
.doc-list .col-date { width: var(--jdate); }
.doc-list .col-number { width: var(--jdate); }
/* Тип і статус — під найдовше значення довідника («Договірна ціна»,
 * «На погодженні»): 83px тексту + 16px комірки */
/* ТИП НЕСЕ СХОДИНКУ ланцюжка, тож йому потрібен запас під відступ: при
   ширині 100px «Договірна ціна» на другому рівні обрізалася до «Договір…».
   Крок сходинки тут дрібніший за загальний (28px): глибина ланцюжка рівно
   три — кошторис, ціна, акт, — і широкий крок платив би шириною колонки за
   вкладеність, якої майже немає. */
.doc-list { --tree-step: 14px; }
.doc-list .col-type { width: 150px; }
.doc-list .col-status { width: 100px; }
/* Сторони договору: ті самі координати, за якими фільтрують ліворуч */
.doc-list .col-party { width: calc(var(--jdate) * 5); }
.doc-list .col-contract { width: calc(var(--jdate) * 3); }
/* Під суму в 9 млрд: «9 999 999 999.99» — 91px тексту + 16px комірки */
.doc-list .col-cost { width: 110px; }
.doc-list .col-notes { width: calc(var(--jdate) * 4); }
/* Задовгий текст обрізається трьома крапками; повне значення — у підказці */
.doc-list td { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Ознака ПДВ усередині блоку договору: приглушена, бо це уточнення до номера,
 * а не окреме значення. «без ПДВ» — сірим, як і в журналі договорів. */
.doc-list .vat-mark { color: var(--ink-muted); }
/* Назва розділу В РЯДУ КНОПОК — смуга починається за кнопкою й займає решту
   ширини. Власного рядка екрана вона не варта: поруч із «Назад» усе одно
   стояла порожнеча. */
.toolbar .section-header { flex: 1; margin: 0; }
.section-header {
  margin: 0 0 6px;
  font-size: 14px;
  background: #404040;
  color: #fff;
  padding: 4px 10px;
}
.pick-list { list-style: none; margin: 0; padding: 0; max-width: 640px; }
.pick-list li { display: flex; align-items: center; gap: 8px; }
.pick {
  display: block;
  flex: 1;
  padding: 4px 10px;
  border: 1px solid var(--line);
  border-top: none;
  text-decoration: none;
  color: var(--ink);
}
.pick-list li:first-child .pick { border-top: 1px solid var(--line); }
.pick:hover { background: #f4f8fd; }
.pick.is-selected { background: var(--selected-bg); font-weight: 600; }
.empty { color: var(--ink-muted); font-style: italic; padding: 6px 2px; }

/* ---------- Таблиці ---------- */
/* КЕГЕЛЬ ТАБЛИЦЬ — ОДИН НА ВСЮ СИСТЕМУ, І ЗАДАЄТЬСЯ ТУТ.
 *
 * Доти його писала кожна таблиця сама, і вони розійшлися: специфікація, журнал
 * документів, договори, склад розцінки й ціноутворення стояли на 12px, а
 * адміністрування, довідники, звіти, обміри, історія і журнал цін лишалися на
 * 14px від `body`. На екрані «Розцінок» це видно було просто оком — журнал цін
 * більший за склад розцінки поруч; у звіті план-факту три таблиці йшли двома
 * кеглями. Дві таблиці ТЕП розійшлися навіть усередині себе: комірки 12px,
 * шапка 14px, бо правило написали лише для `td`.
 *
 * Причина розходження не в смаку, а в тому, що правило доводилося ПАМ'ЯТАТИ:
 * нова таблиця його не діставала, поки хтось не дописав рядок. Тепер вона
 * дістає його від самого класу `.grid`, тобто від того, що робить її таблицею.
 *
 * ОКРЕМІ ПРАВИЛА НА ТАБЛИЦЮ БІЛЬШЕ НЕ ПИШЕМО. Якщо десь потрібен інший кегль —
 * це новий рівень значення, і йому місце у шкалі `--fs-*`, а не в одному
 * селекторі. Тримає це тест `test_findings.py`. */
.grid { border-collapse: collapse; width: 100%;
        font-size: var(--fs-dense); line-height: 1.2; }
.grid.narrow { max-width: 480px; }
.grid th, .grid td {
  border: 1px solid var(--grid-line);
  padding: 3px 7px;
  vertical-align: top;
}
/* ШАПКА ТАБЛИЦІ — ОДНАКОВА ПО ВСЬОМУ ПРОЄКТУ: підпис по центру і впоперек, і
 * вздовж, жирний, з переносом.
 *
 * `vertical-align: middle` тут не косметика: `.grid th, .grid td` задають
 * `vertical-align: top`, і в таблиці, де один заголовок переноситься на два
 * рядки, а сусідні ні, короткі підписи прилипали до верху й ряд читався
 * сходинкою. Перенос (`white-space: normal` + `overflow-wrap`) потрібен,
 * бо колонки фіксованої ширини: без нього довгий підпис або обрізався б,
 * або розпирав колонку. */
.grid thead th {
  background: var(--header-bg);
  font-weight: 700;
  text-align: center;
  vertical-align: middle;
  white-space: normal;
  overflow-wrap: anywhere;
  /* ШАПКА ВИЩА ЗА РЯДОК У ПІВТОРА РАЗА. Комірки щільні навмисно — у таблицях
   * цього проєкту рядків сотні, — але через це шапка зливалася з першим рядком
   * даних, і око не мало за що зачепитися, повертаючись до неї очима. Висоту
   * дає ВІДСТУП, а не `height`: заголовок із перенесенням на два рядки мусить
   * рости далі, а `height` на комірці таблиці поводиться як мінімум і мовчки
   * ламав би саме такі шапки. 3px -> 8px дає рівно ті півтора. */
  padding-top: 8px;
  padding-bottom: 8px;
}
/* ЛИШЕ КОМІРКИ, НЕ ШАПКА. Доти правило звучало `.grid td.num, .grid th.num` —
 * і мовчки скасовувало центрування заголовка з правила вище, бо стоїть
 * пізніше й важить стільки ж. У специфікації з `class="num"` майже всі
 * заголовки, тож уся шапка таблиці стояла праворуч замість центру.
 *
 * `white-space: nowrap` на шапці був такою самою помилкою: заголовки колонок
 * фіксованої ширини мусять переноситися, інакше довгий підпис або обрізається,
 * або розпирає колонку. Числа в комірках, навпаки, розривати не можна. */
.grid td.num { text-align: right; white-space: nowrap; }

/* ---------- Налаштування: користувачі й ролі ---------- */
/* `width: 100%` на `.grid` розганяв п'ять коротких значень по всій ширині
 * монітора: логін ліворуч, кількість об'єктів десь за півметра праворуч.
 * Явна ширина + `table-layout: fixed` тримають колонки на місці; `max-width`
 * не дає таблиці вилізти за край на вузькому екрані. */
.users-list, .roles-list {
  table-layout: fixed;
  width: 900px;
  max-width: 100%;
  margin-bottom: 14px;
}
.users-list .uc-login { width: 140px; }
.users-list .uc-name { width: 220px; }
.users-list .uc-role { width: 170px; }
.users-list .uc-num { width: 80px; }
.users-list .uc-access { width: 95px; }
.users-list .uc-act { width: auto; }

.roles-list .rc-name { width: 200px; }
.roles-list .rc-notes { width: auto; }
.roles-list .rc-num { width: 105px; }
.roles-list .rc-act { width: 165px; }

/* Довга назва ролі чи ім'я переносяться, а не розсувають колонку: ширини
 * фіксовані, тож без цього текст просто вилазив би за межу комірки. */
.users-list td, .roles-list td { overflow-wrap: anywhere; }
/* Дії — посилання й кнопка в один рядок, без переносу посеред слова */
.users-list td:last-child, .roles-list td:last-child {
  white-space: nowrap;
  overflow-wrap: normal;
}
.doc-list td a { color: var(--accent); }

/* Семантика рядків специфікації */
/* РОБОТА — ТЕМНІША СМУГА, СКЛАД — СВІТЛІША. Різниця саме в тоні рядка, бо
 * кольором тексту вона вже була (склад сірий), і цього виявилося замало: поля
 * вводу залиті жовтим і займають більшість колонок, тож обидва рядки на екрані
 * читалися як та сама смуга жовтих полів. Див. `--row-work-bg`.
 *
 * ПОРЯДОК ЦИХ ЧОТИРЬОХ ПРАВИЛ ЗНАЧУЩИЙ: усі важать однаково (0,1,1), тож
 * перемагає те, що нижче. Виділення й робота-сходинка мусять перебивати
 * звичайний тон рядка, тому стоять після нього. */
.row-work td { background: var(--row-work-bg); }
.row-child td { background: var(--child-bg); color: var(--ink-muted); }
.row-expanded td { font-weight: 600; }
.row-selected td { background: var(--selected-bg); }
/* РОБОТА-СХОДИНКА при фільтрі по ресурсу: показана заради свого ресурсу, у
 * підсумок не входить. Приглушена, щоб її вартість не читалася як частина суми,
 * — інакше око складало б і роботу, і матеріал у ній, тобто те саме двічі. */
.row-context td { background: #f6f6f6; color: var(--ink-muted); }
.row-context .cell-input { color: var(--ink-muted); }

/* Номер рядка — він же кнопка виділення. Виділити можна кілька рядків; повторний
   клік знімає. Тому вигляд саме як у прапорця: порожня рамка / залита. */
.row-pick {
  display: inline-block;
  min-width: 22px;
  padding: 1px 4px;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: #fff;
  color: var(--ink);
  text-align: center;
  text-decoration: none;
  font-variant-numeric: tabular-nums;
}
.row-pick:hover { border-color: var(--accent); }
/* Номер — єдина «ручка» рядка: за нього і вибирають, і тягнуть. Курсор каже
 * про друге, бо про перше говорить сам вигляд кнопки. */
.spec tr[draggable="true"] .row-pick { cursor: grab; }
.spec tr[draggable="true"].is-dragging .row-pick { cursor: grabbing; }
.row-pick.is-picked {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  font-weight: 600;
}
.cell-editable { background: var(--editable-bg); }
.row-child .cell-editable { background: var(--editable-bg); }
.qty-custom { color: var(--custom-fg); }
.total-row td { font-weight: 700; border-top: 2px solid #808080; }
/* ПІДПИС ПІДСУМКУ — ОДНА ШИРОКА КОМІРКА, а не напис у вузькій «Ціні
   матеріалів». Раніше порожній блок і підпис були двома комірками, і між
   «Обсягом» та «Ціною матеріалів» проходила вертикальна риска — рівно посеред
   напису, який туди не вміщався: «в т.ч. ресурсів поставки Генпідрядника:»
   удвічі довше за ту колонку.

   Об'єднавши їх `colspan`-ом, дістаємо все одразу: риски посеред напису немає,
   бо немає межі; місця вистачає, бо комірка тягнеться від найменування; а
   `text-align: right` тримає підпис упритул до першого числа, щоб око не
   губило, до чого він.

   Розраховувати на переповнення (`overflow: visible` з виходом за ліву межу)
   було б крихко: таблиця має `table-layout: fixed`, і підрізати вміст браузер
   має повне право. */
.total-row td.total-label { text-align: right; white-space: nowrap; }
/* Сума відфільтрованого стоїть НАД підсумком документа й відділена від нього
   тоншою межею: це не другий підсумок, а відповідь на інше питання. */
.total-filtered td { border-top: 1px solid var(--accent); color: var(--accent); }

.marker {
  display: inline-block;
  width: 14px;
  text-align: center;
  text-decoration: none;
  color: var(--accent);
  font-weight: 700;
}
.marker-flat { color: var(--line); }
/* Маркер-місце в рядках складу: він НЕ видимий, але місце тримає. Без нього
   склад і його позиція мали б різне походження відступу — див. пояснення в
   `_structured.html`. `visibility`, а не `display: none`: сховане зберігає
   розміри, а прибране їх не має. */
.marker-hidden { visibility: hidden; }

/* ЗАГОЛОВКИ ГРУП — КОЛІР ЗА ВИМІРОМ І ВЛАСНИМ РІВНЕМ (StructHdrColor, mod_30:1386).
 *
 * Доти класи звалися `.group-depth-N`, де N — місце рівня в ПОСЛІДОВНОСТІ
 * групування. Через це в мішаному порядку («e1,e2,b1,b2,b3») кошторисний рівень
 * і будівельний могли пофарбуватися однаково — залежно від того, у якому місці
 * послідовності опинились, — і структури було не розрізнити.
 *
 * Тепер номер береться з ВЛАСНОГО рівня виміру (`b2` -> 2 незалежно від місця),
 * а вимір задає рампу: будівельна синя, кошторисна зелена. Глибші за 4 беруть
 * четвертий відтінок — далі різниця в світлоті вже не читається. */
.group-b-1 td { background: var(--bld-1); }
.group-b-2 td { background: var(--bld-2); }
.group-b-3 td { background: var(--bld-3); }
.group-b-4 td { background: var(--bld-4); }
.group-e-1 td { background: var(--est-1); }
.group-e-2 td { background: var(--est-2); }
.group-e-3 td { background: var(--est-3); }
.group-e-4 td { background: var(--est-4); }

/* ---------- Структурований вивід: дерево груп ---------- */
/* Сітка специфікації спільна для обох виглядів (`_spec_head.html`). Ширини —
 * ПРОПОРЦІЇ: `table-layout: fixed` працює лише при заданій ширині таблиці. Саме
 * завдяки їм колонки не рухаються ні від перемикання вигляду, ні від
 * розгортання складу розцінки. */
/* `min-width` — щоб одинадцять колонок лишалися ПОВНИМИ навіть тоді, коли тіло
 * редактора вузьке: бічна панель забирає 456px, і без нього найменування
 * стискалося до сотні пікселів. Коли не вміщається — горизонтально прокручується
 * саме тіло (`.editor-body { overflow-x: auto }`), а не сторінка.
 * Службові колонки обрізані до реального вмісту, щоб прокрутка з'являлася якомога
 * пізніше: «1 234 567.89» у вартості, «0.00» в решті чисел. */
/* КРОК СХОДИНКИ ТУТ СВІЙ — 16px замість загальних 28px.
 *
 * 28px розраховані на широкі дерева довідників, де рядок 14px і повітря вдосталь.
 * У специфікації рядок 12px, відступ комірки 1px, а порядок групування буває на
 * п'ять-шість рівнів: із кроком 28px найглибший заголовок з'їжджав на 146px,
 * тобто виходив за колонки «№» і «Шифр» разом узяті й наповзав на найменування.
 * Той самий крок, що в блоці фільтра (`.struct-block`), — обидва про щільні
 * переліки, а не про дерева на весь екран. */
.spec { table-layout: fixed; width: 100%; min-width: 1560px; --tree-step: 16px; }
/* Ширший за сам номер: ліворуч від нього стоїть іще хрестик видалення. */
.spec .sc-num { width: 58px; }
/* Хрестик і номер в одному рядку, без переносу: комірка вузька, і без цього
 * номер зʼїжджав під хрестик, роблячи рядок удвічі вищим. */
.spec td.num .inline-form { display: inline; }
.spec .sc-name { width: auto; }        /* уся вільна ширина — найменуванню */
/* Ширша за напис одиниці: у комірці стоїть СПИСОК, а він малює ще й стрілку
 * розкриття праворуч від тексту. При 62px «м.куб» разом зі стрілкою за межі
 * комірки й виходив. */
.spec .sc-unit { width: 82px; }
.spec .sc-qty { width: 96px; }
/* «Заактовано» ширша за обсяг: у ній стоять ДВА числа — заактоване й залишок у
   дужках поруч. Колонка є лише в режимі актування. */
.spec .sc-acted { width: 126px; }
/* Ціни й вартості — по дві колонки (матеріали / роботи) плюс «всього».
 * Підписи довгі («Вартість матеріалів з ПДВ»), тож шапка переноситься на два
 * рядки — ширину задає число, а не заголовок. */
/* ШИРША ЗА ЧИСЛО, БО В НІЙ БУВАЄ ВИРАЗ І ЙОГО РЕЗУЛЬТАТ. Ціну частіше рахують,
 * ніж знають («80*1,2+50»), і поруч із виразом стоїть сіре «= 146,00» — інакше
 * невідомо, у що вираз вилився. При 96px ця пара не вміщалася, і результат
 * доводилося ховати в підказку. Ця сама колонка тримає значення розгорнутої
 * картки формування ціни. */
.spec .sc-price { width: 124px; }
.spec .sc-sum { width: 112px; }
.spec .sc-sum-all { width: 118px; }
.spec .sc-norm { width: 72px; }
.spec .sc-norm-cust { width: 84px; }
.spec .sc-coef { width: 64px; }
.spec .sc-coef-cust { width: 80px; }
.spec .sc-act { width: 32px; }
/* Шапка з довгими підписами має ПЕРЕНОСИТИСЬ, а не витікати за межі колонки.
 *
 * Другий селектор не зайвий: майже всі ці заголовки мають клас `.num`, а
 * правило `.grid th.num { white-space: nowrap }` важить (0,2,1) проти (0,1,2)
 * у `.spec thead th` — тобто перемагало б незалежно від порядку у файлі, і
 * «Вартість матеріалів з ПДВ» лягало б одним рядком поверх сусідніх колонок. */
.spec thead th,
.spec thead th.num { white-space: normal; overflow-wrap: anywhere; }
/* «Вартість всього» — підсумкова колонка, тож виділена так само, як підсумки */
.spec .sum-all { font-weight: 600; }

/* ВИСОТА РЯДКА — ПО ШРИФТУ, з одним пікселем зверху й знизу.
 *
 * Специфікацію читають вертикально, десятками рядків одразу, і кожен зайвий
 * піксель відступу — це мінус рядок на екрані. Загальні 3px з `.grid td` тут
 * зайві: рамка вже розділяє рядки, а всередині комірки стоїть поле вводу з
 * власним фоном, тобто «повітря» намальоване й без відступу.
 *
 * Підлогу тримають ТРИ елементи, і задавати висоту рядка, не чіпаючи їх, марно:
 * поле вводу (`.cell-input`), список одиниць (`.unit-select`) і номер рядка
 * (`.row-pick`) — у кожного власні `padding` і рамка. Тому вони зведені сюди. */
/* Висота рядка тепер від `.grid` — вона однакова в усіх таблицях. Тут
   лишається те, що стосується САМЕ специфікації: три елементи нижче тримають
   підлогу рядка власними відступами, і без них кегль нічого не вирішує. */
/* КЕГЕЛЬ РЯДКІВ — ТАКИЙ САМИЙ, ЯК У ЗАГОЛОВКІВ ГРУП (12px).
 *
 * Доти рядки йшли на 14px від `body`, а групи вже були 12px, і дві шкали на
 * одному екрані читалися як два різні документи: заголовок гілки виглядав
 * дрібною приміткою до рядків, які він насправді очолює.
 *
 * Ставиться на `td`, а не на `.spec`: шапка лишається 12px власним правилом
 * вище, і піднімати кегель усієї таблиці, щоб потім двічі його опускати, ні до
 * чого. Поля вводу й списки успадковують його самі (`font: inherit`), тож
 * окремо їх чіпати не треба — а разом із кеглем осідає й висота рядка. */
.spec td { padding: 1px 6px; }
/* Форма-обгортка комірки — БЛОЧНИЙ flex, а не `display: inline`.
 * Inline-обгортка кладе поле вводу на базову лінію тексту, і під ним лишається
 * місце під нижні виносні («р», «у») — два пікселі на кожен рядок, яких на
 * екрані не видно, зате видно в сумі на сорока рядках. */
.spec .inline-form { display: flex; align-items: center; }
.spec .row-pick { padding: 0 4px; }
/* Список одиниць — ЄДИНЕ, що не слухається `line-height`: у `<select>` браузер
 * рахує висоту сам («normal»), і рядок із ним був на три пікселі вищий за
 * сусідні. Тому висота задана числом — під ту саму, що виходить у полів вводу:
 * 12px × 1.2 = 14.4px тексту + 2px рамки. Число доводиться правити разом із
 * кеглем рядків вище — інакше саме список одиниць знову тримав би підлогу. */
.spec .unit-select { height: 17px; }

/* Підсумок гілки — той самий стовпець, що й вартість рядків, інакше око не
   звело б їх докупи. Жирний, бо це число іншого порядку. */
.group-sum { font-weight: 700; }
/* КЕГЛЬ ЗВІДСИ ПРИБРАНО — ВІН ТЕПЕР СПІЛЬНИЙ. Тут стояло «на два пункти
 * дрібніше за рядки (12px проти 14px)», і на той час це була правда: рядки
 * діставалися 14px від `body`. Відтоді кегль таблиць звели в одне правило на
 * `.grid`, тож 12px мають ОБИДВА — і заголовок гілки, і рядки під ним.
 *
 * Заголовок від рядків усе одно відрізняється, просто іншим засобом: жирністю
 * й заливкою рівня. Тримати заради цього окремий розмір означало б завести
 * шостий кегль у системі, де їх і так було дев'ять. */
.group-label, .group-pick { font-weight: 600; }
/* Клікається ЛИШЕ найнижчий рівень: він один задає повну координату для нової
   позиції. Проміжні рівні лишаються текстом — щоб не обіцяти дії, якої немає. */
.group-pick { color: var(--ink); text-decoration: none; }
.group-pick:hover { text-decoration: underline; }
.group-row.is-active td { outline: 2px solid var(--accent); outline-offset: -2px; }
.group-pick.is-active { color: var(--accent); }

/* ---------- Редактор ---------- */
.doc-caption { margin: 4px 0 2px; }
/* ШЕСТІРНЯ — КНОПКА БЕЗ РАМКИ, впритул до назви документа: вона й відкриває
 * картку документа. Той самий прийом, що в `.total-recalc` у журналі —
 * приглушений гліф, який світлішає під рукою. Рамка зробила б із неї ще одну
 * кнопку в ряду, а вона належить самому підпису. */
.doc-gear {
  display: inline-flex;
  align-items: center;
  margin-left: 6px;
  padding: 0 3px;
  /* ЧОРНІША ЗА ПІДПИС, А НЕ ПРИГЛУШЕНА. Приглушений сірий (`--ink-muted`) годився
     шрифтовому гліфу, який і сам був блідим; мальована шестірня при ньому
     читалася як вимкнена кнопка. */
  color: var(--ink);
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
}
/* Трохи більша за кегль підпису — інакше зубці зливаються в коло. */
.doc-gear svg { width: 18px; height: 18px; }
.doc-gear:hover, .doc-gear.is-active { color: var(--accent); }
.doc-title { color: var(--accent); font-weight: 600; }
/* Довідка про документ ОДНИМ РЯДКОМ: примітка, договір, ланцюжок. Ланцюжок мав
   власний абзац (`.doc-chain`) — рядок екрана заради двох посилань. */
.doc-meta { margin: 0 0 10px; color: var(--ink-muted); font-size: 13px; }
.doc-meta .chain-label { color: var(--ink-muted); }

/* `.editor-layout` більше немає: бічної панелі теж. Тіло займає всю ширину, і
 * `overflow-x` лишається як запобіжник — колонок п'ятнадцять. */
.editor-body { min-width: 0; overflow-x: auto; }

/* ПОШУК НАД СТОВПЦЕМ НАЙМЕНУВАННЯ.
 *
 * Ширину й відступ ставить `spec_search.js` — тут лише вигляд. Тло те саме, що
 * в комірок, які правлять (`--editable-bg`): це поле вводу в таблиці, і виглядати
 * воно мусить як поле вводу в таблиці.
 *
 * `position: relative` — заради хрестика: він стоїть УСЕРЕДИНІ поля, при його
 * правому краї, а не поруч із ним. */
.spec-search { position: relative; margin: 0 0 4px; }
.spec-search-input {
  width: 100%;
  box-sizing: border-box;
  padding: 3px 22px 3px 7px;     /* праворуч — місце під хрестик */
  background: var(--editable-bg);
  border: 1px solid var(--line);
  border-radius: 2px;
  font: inherit;
}
/* Власний хрестик браузера прибираємо: він сірий, дрібний і є не скрізь. */
.spec-search-input::-webkit-search-cancel-button { display: none; }
.spec-search-clear {
  position: absolute;
  top: 50%;
  right: 4px;
  transform: translateY(-50%);
  color: var(--danger);
  text-decoration: none;
  font-size: 15px;
  line-height: 1;
  padding: 0 2px;
}
/* ХРЕСТИК ЛИШЕ ТОДІ, КОЛИ Є ЩО ЧИСТИТИ. У порожньому полі він пропонував би
 * дію, яка нічого не робить, і забирав би місце в самого підпису «Пошук».
 *
 * КЛАСОМ, А НЕ `:placeholder-shown`: підпис зникає при курсорі (див.
 * `spec_search.js`), тож у порожньому полі з курсором псевдоклас перестав би
 * діяти — і хрестик з'являвся б рівно там, де чистити нічого. Початковий стан
 * ставить сервер, живий — скрипт. */
.spec-search-clear { display: none; }
.spec-search.has-text .spec-search-clear { display: block; }

/* ---------- Ряд керування структурою ---------- */
/* Чотири кнопки під рядком об'єктів, замість бічної панелі на 456px. Панель
 * забирала майже третину ширини екрана під два довідники структур, які нічого
 * не говорили про сам документ, — і специфікація через це прокручувалась убік.
 *
 * Кнопки ОДНАКОВОЇ ширини (сітка `1fr` у контейнері `max-content`), а поточний
 * порядок групування стоїть у тому ж рядку праворуч: це головна координата
 * виводу, і ховати її за кнопкою означало б змушувати відкривати блок, щоб
 * просто згадати, як зараз згруповано. */
.struct-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-bottom: 8px;
}
/* УСІ ЧОТИРИ КНОПКИ ОДНАКОВОЇ ШИРИНИ — під найдовший підпис.
 *
 * Сітка з `1fr`-колонками робить це сама: рівні частки, а `width: max-content`
 * на самій сітці не дає їй розтягтися на весь екран. Разом це дає рівно те,
 * що треба: ширина найдовшої кнопки, помножена на чотири.
 *
 * На flex кнопки були різні за довжиною тексту (180 / 202 / 180 / 180 пікселів),
 * і ряд читався як випадковий набір. Задавати `min-width` числом теж не
 * годиться: найдовший підпис колись перейменують, і ряд знову роз'їдеться. */
.struct-btns {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
  width: max-content;
  max-width: 100%;
}
.struct-btns > .btn { font-size: 12px; padding: 0 8px; justify-content: center; }
/* РЯД, ЯКИЙ НЕ ТРИМАЄ ПОРОЖНІХ КОЛОНОК. Базова сітка розрахована на чотири
 * кнопки, і ряд із трьох лишав за собою порожнє місце шириною в четверту — а
 * одразу за ним стоїть тумблер області, і його «невеликий відступ» через це
 * ставав завбільшки з кнопку. Колонки тут наростають за числом самих кнопок і
 * лишаються рівними між собою. */
.struct-btns.is-fit {
  grid-template-columns: none;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
}
/* ТЕМНІША ЗАЛИВКА — ЩОБ РЯД ЧИТАВСЯ ЯК ІНШИЙ РІВЕНЬ КЕРУВАННЯ. Тут вирішують,
 * ЯК дивитися на документ (вигляд, групування, фільтр), а в ряду під ним — що з
 * ним робити. Кнопки в обох були однакові, тож два різні за змістом ряди
 * зливалися в один довгий набір.
 *
 * `:not(.is-active)` ОБОВ'ЯЗКОВИЙ. Синя підсвітка ввімкненого вигляду
 * (`.btn.is-active`) має ту саму вагу, що й це правило, а стоїть у файлі вище —
 * без винятку темно-сіре перемогло б її, і кнопка, яка ЄДИНА каже, який вигляд
 * увімкнено, перестала б відрізнятися від сусідніх трьох. */
.struct-btns > .btn:not(.is-active) { background: #dcdcdc; border-color: #8c8c8c; }
.struct-btns > .btn:not(.is-active):hover { background: #cfcfcf; }

/* Скільки позицій чекає на вставку. Видно завжди, поки буфер не порожній:
 * він живе в базі й переживає перехід в інший документ — без цього значка це
 * була б невидима пам'ять, про яку легко забути. */
.clip-badge {
  font-size: 11px;
  padding: 2px 8px;
  border-radius: 8px;
  background: var(--selected-bg);
  border: 1px solid var(--accent);
  white-space: nowrap;
}
/* Підпис відсунутий від кнопок на ширину кнопки — той самий прийом, що в шапці */
.struct-bar .seq-line { margin: 0 0 0 16px; }
/* ТУМБЛЕРИ ПОГЛЯДУ — В ПРОДОВЖЕННЯ РЯДУ, а не в його сітці: `.struct-btns`
 * ділить ширину на рівні колонки, і кнопка всередині неї злилася б із сусідами в
 * суцільний ряд. Невеликий відступ каже, що це вже інше питання.
 *
 * ДРУГА ПРИЧИНА ВАЖЛИВІША ЗА ПЕРШУ: підпис тумблера МІНЯЄТЬСЯ (він і є
 * відповідь на «як зараз»), а в сітці рівних колонок це перебудовувало б увесь
 * ряд від кожного натискання — кнопки міняли б ширину, і сусідні з'їжджали б
 * з-під руки. За сіткою тумблер рухає лише власну ширину. */
/* ВІДСТУПУ ПОНАД ЗВИЧАЙНИЙ ТУТ НЕМАЄ. Був `margin-left: 10px` понад проміжок
 * ряду — разом шістнадцять пікселів, і тумблер відлітав від «Відфільтрувати»
 * так, ніби належить іншому блоку. Що він не з сітки, видно й без відступу:
 * він завжди підсвічений і його підпис міняється. */
.struct-bar > .bar-toggle { font-size: 12px; padding: 0 8px; }
/* САНТИМЕТР МІЖ БЛОКАМИ ОДНОГО РЯДУ. Ліворуч вирішують, ЩО зводимо (режим),
 * праворуч — як на це дивитися (групування, фільтр, вигляд, область). Доти це
 * були два ряди один під одним: цілий рядок екрана заради межі, яку однаково
 * видно. Відстань та сама, що між назвою компанії й розділами в шапці
 * (`.main-nav`), — межа між блоками в цій системі виглядає однаково. */
.struct-bar > .bar-gap { margin-left: 1cm; }

/* Блок, який розгортається ПІД своєю кнопкою. Рамка, а не просто відступ: без
 * неї два блоки поспіль (рівні + фільтр) зливалися б в один довгий список. */
.struct-block {
  border: 1px solid var(--line);
  background: #fbfbfb;
  padding: 8px 10px;
  margin-bottom: 10px;
  max-width: 900px;
}
.struct-block .hint { margin: 8px 0 0; color: #a04000; }
/* ЧОТИРИ КОЛОНКИ ПОТРЕБУЮТЬ БІЛЬШЕ МІСЦЯ, ніж дві. 900px розраховувалися на два
 * дерева; у ті самі 900px чотири колонки лягають так, що найменування ресурсу
 * обрізається на третьому слові. Ширина стосується ЛИШЕ блока фільтра — у блоці
 * рівнів групування колонок так само дві, і розтягувати його ні до чого.
 *
 * 1240px -> 1600px: ДВА ЗВЕДЕННЯ СТАЛИ ШИРШИМИ НА ТРЕТИНУ. Місце взято не в
 * структур (їхні частки ті самі, тобто в пікселях вони не змінилися), а з
 * порожнечі праворуч від блока: 1240px лишали чверть широкого екрана
 * невикористаною, тоді як «Розчин цементно-вапняний М50» у своїй колонці не
 * вміщався. На вужчому екрані блок і далі стискається сам — це `max-width`, а
 * не задана ширина. */
.struct-block-filter { max-width: 1600px; }
.filter-note {
  margin: 8px 0 0;
  font-size: 12px;
  color: var(--accent);
  display: flex;
  align-items: center;
  gap: 8px;
}
.seq-line { color: var(--accent); font-size: 13px; }
.seq-line .hint { color: var(--ink-muted); font-weight: 400; }

.struct-cols { display: flex; gap: 12px; }
.struct-col { flex: 1 1 0; min-width: 0; }
/* ЧОТИРИ КОЛОНКИ РІЗНОЇ ШИРИНИ, а не чотири рівні частки.
 *
 * Структури підписані коротко («Секція 5.1», «Опоряджувальні роботи») і половина
 * їхньої колонки стояла порожньою; найменування ресурсу, навпаки, займає рядок і
 * переноситься. Тому будівельна віддала ПОЛОВИНУ своєї частки (0.5), кошторисна
 * — ТРЕТИНУ (0.7), а звільнене пішло двом зведенням (по 1).
 *
 * Частки, а не пікселі: блок обмежений `max-width`, і при чотирьох фіксованих
 * ширинах остання колонка вилазила б за край на вужчому екрані.
 *
 * Правило стоїть ПІСЛЯ `.struct-col`, бо переписує саме його `flex`. У блоці
 * рівнів групування колонок дві й обидві лишаються рівними — там ці класи не
 * ставляться взагалі. */
.struct-col-b { flex-grow: .5; }
.struct-col-e { flex-grow: .7; }
/* НАКОПИЧУВАЛЬНИЙ ЗВІТ: ВЕРХНІЙ ПОВЕРХ ЗОНИ — ЧОТИРИ КОЛОНКИ ІНШИХ ЧАСТОК.
 *
 * Поруч зі структурами тут виконавець і договори, і найдовший підпис зони —
 * договір із предметом, тож він бере найбільшу частку (1.3). Структури ж, які
 * в редакторі віддавали місце двом зведенням, тут стоять поруч із переліками
 * такої самої вузькості й отримують рівні частки (0.7). Нижній поверх — дві
 * рівні колонки робіт і ресурсів, частки за замовчуванням. */
.cum-filter .struct-col-contracts { flex-grow: 1.3; }
.cum-filter .struct-col-b,
.cum-filter .struct-col-e { flex-grow: .7; }
/* Договір — єдиний пункт зони, що переноситься: предмет договору і є тим, за
   чим його обирають, і обрізаний трикрапкою він не відповідав би ні на що. */
.cum-filter .node-wrap { white-space: normal; }
.struct-col h3 {
  font-size: 12px;
  margin: 10px 0 4px;
  background: var(--group-3);
  border: 1px solid var(--line);
  padding: 3px 6px;
  font-weight: 600;
}
.level-list, .tree { list-style: none; margin: 0; padding: 0; }
/* ВИСОТА РЯДКА БЛОКА — НА П'ЯТУ ЧАСТИНУ НИЖЧА за попередню.
 *
 * Блок розгортається НАД специфікацією й відсуває її вниз, тож кожен його рядок
 * коштує рядка таблиці, заради якої редактор і відкривають. Було 2px відступу
 * при висоті рядка «normal» (~1.4); стало 1px при 1.15 — разом це приблизно
 * −20% висоти, і зменшення видно саме на переліку з сорока рядків.
 *
 * `line-height` тут не косметика, а половина ефекту: самим лише `padding`
 * зменшити рядок на п'яту частину неможливо — його підлогу тримає висота рядка
 * тексту. Кегель лишився 12px: дрібніше в переліку, який читають назвами, вже
 * не читається. */
.level, .node {
  display: block;
  padding: 1px 6px;
  border: 1px solid var(--line);
  border-top: none;
  background: #f2f8f0;
  text-decoration: none;
  color: var(--ink);
  font-size: 12px;
  line-height: 1.15;
  white-space: pre;
  overflow: hidden;
  text-overflow: ellipsis;
}
.level-list li:first-child .level, .tree li:first-child .node { border-top: 1px solid var(--line); }
.level.is-picked, .node.is-active { background: var(--selected-bg); font-weight: 600; }
/* Рівень тепер кнопка форми (клік одразу зберігає порядок), а не посилання —
   вигляд мусить лишитися той самий, тож скидаємо все, що додає `<button>`. */
button.level { width: 100%; text-align: left; cursor: pointer; font-family: inherit; }

/* Дерева у блоці фільтра вузькі — повний крок з'їв би ширину під відступи */
.struct-block { --tree-step: 16px; }
/* Дерева бувають довгі; блок не має розтягувати сторінку на екран заввишки.
   Те саме обмеження — і на зведення: у документі бувають сотні ресурсів. */
.struct-block .tree, .struct-block .ledger { max-height: 320px; overflow-y: auto; }

/* ---------- Зведення документа: позиції й ресурси ---------- */
/* ДВА ПЕРЕЛІКИ ОДНІЄЇ ФОРМИ: назва ліворуч, число й одиниця праворуч колонкою.
 *
 * Рядок — flex, а не таблиця: таблиця вирівняла б числа ідеально, але зажадала б
 * четвертої колонки під кожну й фіксованих ширин, а тут їх чотири різні на один
 * блок. `margin-left: auto` на числі дає ту саму колонку без жодної з цих
 * складнощів.
 *
 * Ті самі висоти, кегель і рамки, що в `.node` поруч: це сусідні колонки одного
 * блока, і два різні розміри рядка в них читалися б як несправність. */
.ledger { list-style: none; margin: 0; padding: 0; }
.ledger-row {
  display: flex;
  align-items: baseline;
  gap: 4px;
  padding: 1px 6px;
  border: 1px solid var(--line);
  border-top: none;
  background: #fff;
  font-size: 12px;
  line-height: 1.15;
}
.ledger li:first-child { border-top: 1px solid var(--line); }
/* Обраний у фільтрі запис — так само, як обраний вузол дерева поруч: один
   вигляд на одне явище. */
.ledger-row.is-active { background: var(--selected-bg); font-weight: 600; }
.ledger-row a.ledger-name { color: var(--ink); text-decoration: none; }
.ledger-row a.ledger-name:hover { text-decoration: underline; color: var(--accent); }
/* Назва забирає вільну ширину й обрізається трикрапкою: повний текст є в
   підказці, а перенос на другий рядок зламав би рівність висот із деревами. */
.ledger-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ledger-qty { flex: 0 0 auto; margin-left: auto; font-variant-numeric: tabular-nums; }
.ledger-unit { flex: 0 0 auto; color: var(--ink-muted); }
/* Скільки разів позиція трапляється в документі — дрібно й приглушено: це
   довідка про повтор, а не друге число поруч з обсягом. */
.ledger-times { flex: 0 0 auto; color: var(--ink-muted); font-size: 11px; }
/* Заголовок типу ресурсів. Той самий сірий, що в шапках колонок, — це підзаголовок
   усередині переліку, а не його рядок. */
.ledger-group {
  display: flex;
  align-items: baseline;
  gap: 4px;
  padding: 1px 6px;
  border: 1px solid var(--line);
  border-top: none;
  background: var(--group-3);
  font-size: 11px;
  line-height: 1.15;
  font-weight: 600;
  text-transform: uppercase;
}
/* Плюсик згортання типу — ПЕРЕД назвою, як у групах специфікації й у дереві
   папок бібліотеки. Моноширинний, щоб «+» і «−» не зсували назву на піксель
   туди-сюди від кожного натискання. */
.ledger-fold {
  flex: 0 0 auto;
  width: 11px;
  text-align: center;
  font-family: monospace;
  font-weight: 700;
  color: var(--ink-muted);
  text-decoration: none;
}
.ledger-fold:hover { color: var(--accent); }
.ledger-group .ledger-times { margin-left: auto; }

/* ---------- Головна навігація ---------- */
/* Усі розділи одним рядом, кнопки ОДНАКОВОЇ ширини — під найдовший підпис
   («Статуси документів»).
 *
 * Як це працює: сітка з колонок `1fr` усередині контейнера завширшки
 * `max-content`. Під такою шириною рівні `fr`-колонки отримують однаковий
 * розмір, що дорівнює найширшому вмісту. Робити те саме на flex довелося б
 * підбором `min-width` в `em` — і воно б'їхало від довжини підписів. */
.main-nav {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  width: max-content;
  /* Сантиметр від блоку компанії — див. пояснення біля `.header-top`. */
  margin-left: 1cm;
  /* Без цього блок як flex-елемент стискається нижче своєї max-content ширини,
     колонки впираються в min-content — і кнопки перестають бути однаковими. */
  flex: 0 0 auto;
  /* РЯДКІВ ДВА: розділи вгорі, вибір об'єкта під ними. Проміжок між рядами
     трохи більший за проміжок між кнопками — інакше два ряди читаються як одна
     сітка з дірками. */
  grid-template-rows: auto auto;
  gap: 6px 4px;
}
/* Кнопки — ВСІ у верхньому ряду. Без цього автопотік по стовпцях розкладав би
   їх по два: перша вгорі, друга під нею. */
.main-nav > .nav-btn { grid-row: 1; }
/* ВИБІР ОБ'ЄКТА — ДРУГИЙ РЯД, ЗАВШИРШКИ З ДВІ КНОПКИ. Дві, а не одна: у
   підпис «Об'єкт: <назва>» одна кнопка не вміщає навіть короткої назви.
 *
 * `contain: inline-size` — не окраса, а те, що тримає ряд. У сітці з рівних
 * часток вміст елемента, який перетинає два стовпці, роздуває обидва, тож
 * довга назва об'єкта потягла б за собою ШИРИНУ ВСІХ кнопок розділів.
 * Containment робить власний вміст невидимим для вимірювання: ширину задає
 * сітка, а напис, що не вліз, обрізається трьома крапками. */
.main-nav > .object-line {
  grid-row: 2;
  grid-column: 1 / span 2;
  contain: inline-size;
  margin: 0;
}
.nav-btn {
  padding: 4px 12px;
  font-size: 12px;
  text-align: center;
  background: #2a2a2a;
  color: #c8c8c8;
  text-decoration: none;
  border: 1px solid #555;
  border-radius: 3px;
  white-space: nowrap;
}
.nav-btn:hover { background: #3a3a3a; color: #fff; }
.nav-btn.is-active { background: #fff; color: #202020; border-color: #fff; font-weight: 600; }
/* Розділи без змісту — видно, що вони є, але видно й що вони порожні */
.nav-btn.is-stub { opacity: .55; }
/* Порожня перша колонка сітки: відступ від назви організації рівно в одну
   кнопку. Розмір задавати не треба — `1fr` робить її такою ж, як решта. */
/* ---------- Довідники: підвкладки й фільтри ---------- */
.mode-tab.is-stub { opacity: .55; }

.sub-tabs { display: flex; flex-wrap: wrap; gap: 4px; margin: 10px 0; }
.sub-tab, .pricing-tab {
  padding: 4px 14px;
  font-size: 13px;
  background: #d7e6f5;
  color: #202020;
  text-decoration: none;
  border: 1px solid #a8c0d8;
  border-radius: 3px;
}
.sub-tab.is-active, .pricing-tab.is-active {
  background: #0070c0;
  color: #fff;
  border-color: #0070c0;
  font-weight: 600;
}

/* Вкладки «Розцінок»: шість типів ресурсів + «Константи» одним рядом, УСІ
 * однакової ширини. Той самий прийом, що в головній навігації: колонки `1fr`
 * усередині контейнера `max-content` виходять рівними за найширшим підписом.
 * На flex довелося б підбирати `min-width` в `em` — і воно б'їхало від
 * перейменування будь-якої вкладки. */
.pricing-tabs {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  width: max-content;
  max-width: 100%;
  gap: 4px;
  margin: 10px 0;
}
.pricing-tab { text-align: center; white-space: nowrap; }

/* ---------- Вибір об'єкта в шапці ---------- */
/* ОДНА КНОПКА З НАЗВОЮ ПОТОЧНОГО ОБ'ЄКТА замість ряду всіх об'єктів. Ряд
 * (`.object-picker`) з десятком об'єктів займав у шапці кілька рядків, а
 * відповідь на щоденне питання «де я зараз» доводилося шукати в ньому по
 * підсвітці. Тепер відповідь — сам напис на кнопці.
 *
 * ПЕРЕЛІК СПЛИВАЄ ПОВЕРХ СТОРІНКИ, а не розсуває шапку: інакше таблиця під нею
 * стрибала б на кожне відкриття списку. */
.object-line { margin: 6px 0 0; font-size: var(--fs-ui); }
.object-menu { position: relative; }
/* Поточне значення, а не дія, — тому вигляд обраної кнопки вибору
   (`.pick-inline.is-selected`), а не темної кнопки розділу. */
.object-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: var(--ctl-h);
  padding: 0 8px;
  background: var(--selected-bg);
  color: var(--ink);
  border: 1px solid var(--accent);
  border-radius: 3px;
  white-space: nowrap;
  cursor: pointer;
  /* Рідний трикутник `<summary>`: `list-style` прибирає його в Firefox,
     псевдоелемент нижче — у браузерах на WebKit. Свій значок стоїть праворуч
     від назви, де його й шукають. */
  list-style: none;
}
.object-btn::-webkit-details-marker { display: none; }
.object-btn:hover { background: #fff; }
/* Кнопка займає ВСЮ віддану сітчку ширину — рівно дві кнопки розділів. Не
   «скільки треба під напис»: ряд кнопок тримається на однакових ширинах, і
   кнопка, підігнана під назву об'єкта, ламала б цю лінію щоразу, коли об'єкт
   перемкнули. Назва, яка не влізла, обрізається трьома крапками; `min-width:
   0` — бо без нього flex-елемент не дає собі стиснутися нижче власного
   тексту. */
.object-btn { width: 100%; }
.object-current {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: 600;
}
.object-caret { display: inline-block; font-size: var(--fs-note); color: #505050; }
.object-menu[open] .object-caret { transform: rotate(180deg); }
.object-list {
  position: absolute;
  top: 100%;
  left: 0;
  /* Липка панель посилань має 5, тост — 50; список лягає між ними. */
  z-index: 20;
  display: flex;
  flex-direction: column;
  min-width: 100%;
  /* Об'єктів може бути скільки завгодно — довгий перелік прокручується, а не
     випадає за нижній край екрана. */
  max-height: 60vh;
  overflow: auto;
  margin-top: 2px;
  padding: 2px;
  background: #fff;
  border: 1px solid #969696;
  border-radius: 3px;
  box-shadow: 0 6px 16px rgba(0, 0, 0, .35);
}
.object-item {
  padding: 4px 10px;
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
  border-radius: 2px;
}
.object-item:hover { background: var(--bld-4); }
.object-item.is-selected { background: var(--selected-bg); font-weight: 600; }
.pick-inline {
  display: inline-flex;
  align-items: center;
  min-height: var(--ctl-h);
  padding: 0 8px;
  background: #ebebeb;
  color: #282828;
  text-decoration: none;
  border: 1px solid #969696;
  border-radius: 3px;
}
.pick-inline.is-selected { background: var(--selected-bg); border-color: var(--accent); font-weight: 600; }

.sub-header {
  font-size: 13px;
  margin: 12px 0 4px;
  background: var(--group-3);
  border: 1px solid var(--line);
  padding: 3px 8px;
}

/* ---------- Дерева довідника ---------- */
.tree-grid { max-width: 900px; }
.col-code, th.col-code { width: 130px; }

/* КОЛІР РІВНІВ — СПІЛЬНИЙ ІЗ РЕДАКТОРОМ ДОКУМЕНТІВ (`.group-b-N` / `.group-e-N`):
   будівельна структура синя, кошторисна зелена, глибші рівні світліші. Порт
   excel-івського `LevelFillColor` (mod_10:1350) звідси прибраний: він фарбував
   обидва дерева ОДНАКОВО — темно-сірий корінь, темно-синій перший рівень, — тож
   на цьому екрані структури не відрізнялися між собою, хоч у редакторі, де вони
   стоять поруч, відрізнялися. Той самий вузол мав два різні вигляди залежно від
   того, звідки на нього дивишся.

   Тут лишається тільки те, чого в редакторі немає: корінь-об'єкт (він не рівень
   структури, а сам об'єкт) і рамка виділення. */
.tree-row.tree-root td { background: #404040; color: #fff; font-weight: 600; }
.tree-row.group-b-1 td, .tree-row.group-e-1 td { font-weight: 600; }
.tree-row.is-selected td { outline: 2px solid var(--accent); outline-offset: -2px; }
.collapsed-hint { color: var(--ink-muted); font-size: 11px; }

/* ---------- Розцінки: чотири панелі ---------- */
.pricing-layout { display: flex; gap: 14px; align-items: flex-start; }
.panel { min-width: 0; }
/* Панель папок розширена з 260px під найдовшу з чотирьох кнопок
   («Перейменувати папку») — див. сітку кнопок нижче. */
.panel-folders { flex: 0 0 320px; }
/* `overflow-x: auto` — щоб задані ширини колонок договорів лишалися заданими.
 *
 * У таблиці з `table-layout: fixed` сума ширин колонок є її МІНІМУМОМ: `max-width`
 * її не стискає (перевірено — таблиця на 1688px лишалася 1688px у панелі на
 * 1494px і виштовхувала сторінку за край екрана). Тож або колонки «пливуть»,
 * або прокручується панель. Прокручується панель — саме так уже влаштоване
 * тіло редактора (`.editor-body`), і сторінка вбік не їде. */
.panel-items { flex: 1 1 auto; overflow-x: auto; }
/* ПАНЕЛЬ ВАРІАНТІВ — РІВНО ПІД СВІЙ ЗАГОЛОВОК. 230px лишилися від часів, коли в
   підпису варіанта стояв ще й шифр; тепер там саме «(м³)», і панель тримала
   сотню порожніх пікселів. «Варіанти розцінок» у Segoe UI Semibold 12px — це
   ~108px, плюс рамка й відступи `.panel-header` (14px). Решта вмісту вужча. */
.panel-recipes { flex: 0 0 132px; }
/* ...КРІМ ТИХ ХВИЛИН, КОЛИ В НІЙ ВІДКРИТА ФОРМА. Створення варіанта, зміна
   одиниці й видалення малюються в цій же панелі, а в них заголовок з назвою
   роботи й випадний список одиниць: у 132px вони стали б стовпчиком з одного
   слова в рядку. Форма зникає — панель звужується назад. */
.panel-recipes-form { flex-basis: 340px; }
.panel-actions { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 6px; }

/* Чотири кнопки папок — сіткою 2×2, УСІ однакової ширини. На flex вони ставали
   різними за довжиною підпису й вишиковувалися сходинкою: «+ Папку поруч» удвічі
   коротша за «Перейменувати папку», і ряд читався як випадковий набір. */
.panel-folders .panel-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px;
}
.panel-folders .panel-actions .btn { text-align: center; }
/* Дрібна кнопка панелей довідників. Власна висота, а не спільна `--ctl-h`:
   тут кнопки стоять над списком, а не в ряду з вибором об'єкта. */
.btn-sm { font-size: 11px; min-height: 18px; padding: 0 7px; }
.panel-header {
  font-size: 12px;
  margin: 0 0 4px;
  background: var(--group-3);
  border: 1px solid var(--line);
  padding: 3px 6px;
  text-align: center;
  font-weight: 600;
}
.panel-header.wide { text-align: left; background: #d9e1f2; }

/* ЗАГОЛОВОК ІЗ КНОПКАМИ В ОДНОМУ РЯДКУ. Ряд кнопок над заголовком забирав
   цілий рядок екрана заради двох кнопок, а праворуч від самого заголовка
   («Склад розцінки: (м³)») лишалася порожнеча на всю ширину.

   Кнопки притиснуті до ЛІВОГО краю, одразу за підписом, а не розігнані до
   протилежних країв: очі йдуть за текстом і одразу натрапляють на дію. Смуга
   при цьому не росте — `align-items: center` тримає кнопки в її висоті. */
.panel-header-actions {
  display: flex;
  align-items: center;
  gap: 10px;
}
.panel-header-btns { display: inline-flex; align-items: center; gap: 6px; }
/* ...А В ЖУРНАЛІ ЦІН — ДО ПРАВОГО КРАЮ. Між підписом і кнопками там стоїть
   назва ресурсу, яка буває і на два слова, і на пів рядка; «одразу за
   підписом» кнопки стрибали б по горизонталі на кожному переході до сусідньої
   позиції — а по них саме й клацають підряд. */
.panel-header-btns-right { margin-left: auto; }
/* Власного розміру кнопкам тут НЕ задаємо: `.btn-sm` уже дає 18px, і правило
   `.panel-header-btns .btn` із більшою вагою робило їх ВИЩИМИ за потрібне —
   економія рядка з'їдалася їхньою ж висотою. */

.folder-tree, .recipe-list { list-style: none; margin: 0; padding: 0; }
.folder, .recipe {
  display: block;
  padding: 3px 6px;
  border: 1px solid var(--line);
  border-top: none;
  text-decoration: none;
  color: var(--ink);
  font-size: 13px;
}
.folder-tree li:first-child .folder, .recipe-list li:first-child .recipe {
  border-top: 1px solid var(--line);
}
.folder:hover, .recipe:hover { background: #f4f8fd; }
.folder.is-selected, .recipe.is-selected { background: var(--selected-bg); font-weight: 600; }
/* ОЛІВЕЦЬ ПРАВИТЬ ОДИНИЦЮ РОЗЦІНКИ — поверх рядка, а не після нього: сам підпис
   це `display: block` із рамкою, і сусіднє посилання просто впало б наступним
   рядком, розірвавши перелік. Тому олівець накладено праворуч, а підпису додано
   відступ рівно під нього — інакше довгий шифр заповз би під олівець. */
.recipe-list li { position: relative; }
.recipe-list .recipe { padding-right: 42px; }
.recipe-edit {
  position: absolute;
  top: 3px;
  right: 6px;
  color: var(--ink-muted);
  text-decoration: none;
  font-size: 13px;
}
.recipe-edit:hover, .recipe-edit:focus { color: var(--accent); }

/* Хрестик видалення стоїть ЛІВІШЕ за олівець і червоніє під курсором — як усі
   інші видалення в системі. Обидва в тому самому куті рядка, тож `right`
   відсуває другий рівно на ширину першого. */
.recipe-del { right: 22px; }
.recipe-del:hover, .recipe-del:focus { color: var(--danger); }
/* Колонка папок вужча за головну таблицю — крок відповідно менший */
.folder-tree { --tree-step: 18px; }

/* Перелік позицій на вкладці «Роботи». Без обмеження панель забирала всю вільну
   ширину, і варіанти розцінок відсувалися до правого краю — через півекрана
   порожнечі від роботи, до якої вони належать. На решті вкладок розцінок немає,
   тож там панель і далі займає все, що лишилось.
   Ширина вдвічі проти першої спроби (460px): найменування робіт довгі, і в
   вузькій колонці вони перетворювалися на два-три рядки. */
.panel-items-narrow { flex: 0 1 800px; }

/* Ширше проти 900px: до складу додалися ціна з бібліотеки й вартість на
   одиницю роботи, і у вужчій таблиці найменування ресурсу ламалося на два
   рядки на кожній позиції. */
.recipe-lines { margin-top: 18px; max-width: 1120px; }

/* ОДИН ШРИФТ НА ПІКЕР І НА СКЛАД. Вони стоять один над одним, і різниця в
   розмірі читалася б як різниця в призначенні. 12px, а не успадковані 14: у
   складі вісім колонок, і чотирнадцятим шрифтом найменування ресурсу
   переносяться на два рядки. Саме склад розцінки, а не всі таблиці системи — у
   специфікації документа своя сітка й свій резизер ширин. */

/* РЯДКИ НИЖЧІ. `.grid th, .grid td` дає `3px 7px` на всю систему; тут склад
   стоїть під пікером, і кожен зайвий піксель у рядку множиться на десяток
   позицій. Поля вводу стискаємо разом із коміркою — інакше рядок із полем
   лишався б вищим за сусідній, і таблиця йшла б сходинками. */
.recipe-lines .grid th, .recipe-lines .grid td { padding: 1px 5px; }
.recipe-lines .norm-input { padding: 0 4px; }

/* ПАНЕЛЬ ФОРМИ ПІД ПІКЕР — ШИРША Й НИЖЧА. У `.form-panel` стоїть
   `max-width: 640px`, розрахований на форму в одну колонку полів; три колонки
   пікера просять ~750, і саме ця межа їх стискала — а не відступи всередині.

   Відступи стиснуті окремо: панель стоїть НАД складом розцінки, тож її висота
   прямо коштує рядків тому, заради чого її й відкрили. Шрифт зведено до 12px —
   такого самого, як у таблиці під нею: різниця в розмірі між сусідніми блоками
   читалася б як різниця в призначенні. */
.form-panel.form-panel-wide {
  max-width: 1120px;
  padding: 6px 8px;
  margin-bottom: 8px;
  font-size: 12px;
}
/* Заголовок блоку несе поруч «Закрити» — доти воно лежало під блоком і
   коштувало окремий рядок. */
.form-panel-wide h3 {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 4px;
  font-size: 12px;
}
.form-panel-wide .empty { padding: 3px 2px; }

/* РЯДОК ВИКОНАННЯ — той, у якому стоїть сама робота розцінки. Він відповідає на
   інше питання, ніж решта складу («скільки коштує зробити» проти «з чого
   роблять»), і стоїть завжди першим. Підсвітка тут не прикраса: без неї він
   нічим не відрізняється від звичайного рядка, а правлять у ньому ставку —
   тобто половину вартості розцінки.

   Той самий прийом і той самий колір, що в `.const-local`: «діє особливе
   правило». Два різні жовті на сусідніх екранах читалися б як два різні стани. */
.recipe-exec > td { background: #fff8e6; }
.recipe-exec > td:nth-child(2) { font-weight: 600; }

/* РУЧКА ПЕРЕТЯГУВАННЯ рядка складу. Живе в комірці «Ресурс», перед
   найменуванням: окремий стовпець забирав би ширину на весь екран заради
   вісімнадцяти пікселів. `cursor: grab` — єдине, що відрізняє її від тексту. */
.drag-handle {
  display: inline-block;
  color: var(--ink-muted);
  cursor: grab;
  line-height: 1;
  letter-spacing: -2px;
  user-select: none;
}
.drag-handle:active { cursor: grabbing; }

/* ХРЕСТИК ВИДАЛЕННЯ РЯДКА СКЛАДУ — ГОЛИЙ ГЛІФ, БЕЗ РАМКИ. Стоїть у комірці
   «Тип», перед підписом; квадратик кнопки поруч зі словом «Матеріали» читався б
   як окремий елемент керування рядка.

   Червоний одразу, а не лише під курсором, як у переліку варіантів: там хрестик
   висить над текстом рядка й мусить не заважати читати підпис, а тут він стоїть
   у своїй комірці й нічого не перекриває. */
.line-del {
  border: 0;
  background: none;
  padding: 0 4px 0 0;
  color: var(--danger);
  font: inherit;
  font-weight: 700;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
}
.line-del:hover, .line-del:focus { color: #a01e12; }

/* ОЛІВЕЦЬ ПРАВКИ — РЯДКОВИЙ ДВІЙНИК `.recipe-edit`. Той позиційований абсолютно
   (висить над підписом варіанта розцінки) і в комірку таблиці не годиться, а
   вигляд потрібен той самий: приглушений, доки на нього не дивляться, — на
   відміну від хрестика, який червоний одразу.

   Стоїть поруч із `.line-del` і тут, і на екрані: у журналі цін обидва в
   комірці «Дата», перед самою датою. */
.line-edit {
  padding: 0 5px 0 0;
  color: var(--ink-muted);
  text-decoration: none;
  font-size: 12px;
  line-height: 1;
}
.line-edit:hover, .line-edit:focus { color: var(--accent); }

/* МІСЦЕ ВСТАВКИ ПІД ЧАС ПЕРЕТЯГУВАННЯ — на `td`, а НЕ на `tr`.
   Загальні `.drop-before` / `.drop-after` кладуть `box-shadow` на сам елемент, а
   на рядку таблиці з `border-collapse: collapse` тінь не малюється взагалі. Саме
   тому дерево папок (там `<li>`) підсвічувалося, а склад розцінки ні — і вдале
   перетягування виглядало як мертве. Ті самі два рядки вже написані для `.spec`
   і `.tei-lib`; тут була третя таблиця, якій їх забули дати. */
.recipe-lines tr.is-dragging td { opacity: .5; }
.recipe-lines tr.drop-before td { box-shadow: inset 0 2px 0 var(--accent); }
.recipe-lines tr.drop-after td { box-shadow: inset 0 -2px 0 var(--accent); }

/* ---------- Пікер ресурсу для складу розцінки ---------- */
/* ТРИ КОЛОНКИ ПОРУЧ, а не каскад списків один під одним. Ширини різні за
   змістом: типів чотири й вони короткі, папки мають відступ вкладеності, а
   назви ресурсів довгі — тож остання колонка росте, а перші дві ні.
   `min-height` тримає рамку на місці, поки колонки порожні: без нього панель
   стрибала б на кожному кліку. */
/* ВИСОТА ТУТ — РОБОЧИЙ ПРОСТІР. Пікер стоїть НАД складом розцінки, тобто кожен
   зайвий піксель відсуває вниз саме те, заради чого його відкрили. Тому всі
   відступи стиснуті до межі, за якою рядки почали б зливатися. */
.line-picker {
  display: flex;
  gap: 8px;
  align-items: flex-start;
  margin: 3px 0 4px;
}
.lp-col {
  border: 1px solid var(--line, #d7dde5);
  border-radius: 4px;
  padding: 3px 5px;
  min-height: 100px;
  max-height: 240px;
  overflow-y: auto;
}
.lp-types { flex: 0 0 130px; }
.lp-folders { flex: 0 0 240px; }
.lp-resources { flex: 1 1 360px; }

.lp-head {
  margin: 0 0 3px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--ink-muted);
}
.lp-list { list-style: none; margin: 0; padding: 0; }

/* Рядок пікера — і посилання (тип, папка), і кнопка (ресурс) виглядають
   однаково: для людини це той самий жест «клікнути по назві», і різниця в тегу
   не має ставати різницею на екрані. */
.lp-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 1px 5px;
  border: 0;
  border-radius: 3px;
  background: none;
  color: inherit;
  /* КЕГЛЬ ЯВНО, а не `font: inherit`: пікер і склад розцінки стоять один над
     одним, і різниця в шрифті між ними читалася б як різниця в призначенні.
     Обидва зведені до `--fs-dense` — див. `.recipe-lines .grid` нижче.

     ОКРЕМИМИ ВЛАСТИВОСТЯМИ, А НЕ СКОРОЧЕННЯМ. Тут стояло `font: 12px/1.4
     inherit`, і це НЕ ПРАЦЮВАЛО: `inherit` не можна ставити складником
     скорочення, тож браузер відкидав оголошення цілком. Посилання (тип, папка)
     кегль однаково діставали — успадкуванням від панелі, — а кнопки (ресурс,
     константа) ні: `<button>` шрифт не успадковує, і вони лишалися на 13,3px
     власного замовчування браузера. Одні й ті самі рядки пікера стояли двома
     різними кеглями, і причину не видно ні в розмітці, ні в самому правилі. */
  font-size: var(--fs-dense);
  line-height: 1.4;
  font-family: inherit;
  text-decoration: none;
  cursor: pointer;
}
.lp-item:hover, .lp-item:focus { background: #f4f8fd; }
.lp-item.is-selected { background: var(--selected-bg); font-weight: 600; }

/* Звідки знахідка. Пошук іде по ВСЬОМУ довіднику, тож без типу й папки два
   однойменні матеріали не розрізнити. Другим рядком і приглушено: це довідка,
   а не сама назва. */
.lp-where {
  display: block;
  font-size: 11px;
  color: var(--ink-muted);
}

.lp-search-form { margin: 0 0 4px; }
.lp-search {
  width: 100%;
  padding: 2px 5px;
  font: inherit;
  font-size: 12px;
  border: 1px solid var(--line, #d7dde5);
  border-radius: 3px;
}
/* Дерево папок згортає скрипт; без нього `hidden` ніхто не ставить, і всі
   папки лишаються видимими — саме на цьому й тримається запасний шлях. */
.lp-tree li[hidden] { display: none; }
.lp-search:focus { outline: 2px solid var(--accent); outline-offset: -1px; }

/* Локальне значення константи — семантика «діє НЕ загальне значення» */
.const-local { background: #fff2cc; font-weight: 700; }
.const-none { color: var(--ink-muted); }
.constants { max-width: 780px; }

/* ---------- Партнери: перелік договорів ---------- */
/* `.partner-card` лишається на випадок, якщо картку реквізитів кудись
   повернуть, але з режиму «Партнери» вона прибрана: показувала ті самі десять
   полів, що й форма правки, тільки без можливості виправити. */
.partner-card { max-width: 720px; margin-bottom: 16px; }
.partner-card .card-label {
  width: 190px;
  background: var(--header-bg);
  text-align: left;
  font-weight: 600;
}
/* Панель контрагентів ширша за колонку папок у «Розцінках»: над переліком
   стоять три кнопки ОДНАКОВОЇ ширини (див. нижче), і вузька колонка зривала б
   найдовшу з них на другий рядок. */
.panel-partners { flex: 0 0 380px; }

/* ДВА РЯДИ КНОПОК СТОЯТЬ НА ОДНІЙ ЛІНІЇ. Доти над договорами був ще й заголовок
 * із назвою контрагента, який зсував цей ряд униз, — два ряди на одному екрані
 * читалися сходинкою. Заголовок пішов (назва є ліворуч підсвіченим рядком), тож
 * і відступ під нього тут більше ні до чого. */
.contracts-actions { margin-top: 0; }

/* КНОПКИ КОНТРАГЕНТІВ РОЗТЯГНУТІ НА ВСЮ ШИРИНУ ПАНЕЛІ, рівними частками. На flex
 * кожна була широка рівно настільки, наскільки довгий її підпис, і ряд
 * «+ Контрагент | Реквізити | Видалити» читався як набір різнокаліберних
 * плиток; далі вони стали однаковими, але ряд усе одно обривався на середині
 * панелі, лишаючи праворуч смугу порожнечі.
 *
 * `minmax(0, 1fr)`, а не `1fr`: нуль дозволяє колонці стиснутися вужче за свій
 * підпис. Без нього три кнопки не влізли б у вузьку панель і сітка вилізла б за
 * її межу.
 *
 * Ряд над договорами при цьому НЕ розтягується: там кнопка одна, і на всю
 * ширину таблиці договорів вона виглядала б як смуга, а не як кнопка. Спільної
 * ширини між рядами більше немає — вона трималася на однаковому мінімумі,
 * якого тепер немає в лівого ряду. */
.panel-partners .panel-actions {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  width: 100%;
}
.contracts-actions {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(104px, 1fr);
  width: max-content;
  max-width: 100%;
}
.panel-partners .panel-actions .btn,
.contracts-actions .btn { text-align: center; justify-content: center; }

/* Рядок договору обирає договір цілком — курсор має це підказувати */
.contract-list tbody tr { cursor: pointer; }
.contract-list tbody tr:hover { background: #f4f8fd; }
.contract-list tbody tr.row-selected:hover { background: var(--selected-bg); }

/* Ширини колонок договорів задані явно — інакше таблиця «дихає» від довжини
 * назв контрагентів, і сусідні рядки візуально не збігаються.
 *
 * ОДИНИЦЯ ТА САМА, ЩО В ЖУРНАЛІ ДОКУМЕНТІВ — ширина дати при 12px (74px).
 * Предмет договору замовлено «як дата ×5», і записувати це числом 370
 * означало б втратити зв'язок: змінився б шрифт — і пропорція поїхала б.
 *
 * Ширина таблиці — ЧИСЛОМ (сума колонок), а не `100%`: інакше на широкому
 * моніторі задані ширини стають пропорціями й таблиця розтягується на весь
 * екран. Селектор `table.contract-list` — щоб перебити `.grid { width: 100% }`,
 * яке стоїть у файлі пізніше з тією ж вагою.
 *
 * ЧИСЛО МУСИТЬ ДОРІВНЮВАТИ СУМІ: 212 + 74 + 135 + 132 + 260 + 260 + 370 + 95
 * = 1538. Доти тут стояло 1688 при сумі 2014 — тобто число вже було
 * меншим за колонки, і браузер його мовчки не слухався (у `table-layout: fixed`
 * ширина беруться більшою з двох). Помилка тиха: правиш число — нічого не
 * змінюється, і здається, що правило не працює зовсім. */
.contract-list { --cdate: 74px; }

table.contract-list {
  table-layout: fixed;
  width: 1538px;
  max-width: 100%;
}
.contract-list td { vertical-align: middle; }
/* НОМЕР НЕСЕ СХОДИНКУ дерева й позначку, чому договір вкладено («ДУ» / «суб»),
   тож 100px замало: на другому рівні номер обрізався, а позначка не влізала
   зовсім. Та сама причина, що в журналі документів, — і те саме лікування. */
.contract-list .cc-number { width: 212px; }
/* Олівець — ДРУГЕ ПОСИЛАННЯ комірки номера: номер веде на суми, олівець на
   реквізити. Він і робить подвійний клік необов'язковим — жесту не видно й з
   клавіатури його не зробити, а сюди Tab доходить, як до всього.
   Сірий, поки на нього не дивляться: у кожному рядку по одному, і чорними вони
   стали б смугою, яка перебиває самі номери. */
.contract-list .row-edit {
  margin-right: 4px;
  color: var(--ink-muted);
  text-decoration: none;
}
.contract-list .row-edit:hover, .contract-list .row-edit:focus { color: var(--accent); }
.contract-list .cc-date { width: var(--cdate); }
/* Вид і статус — у півтора раза ширші: «Субпідряд» і «Завершено» вміщалися
   тільки-но, а перенос на два рядки в коротких підписах читається погано */
.contract-list .cc-type { width: 135px; }
.contract-list .cc-status { width: 132px; }
/* Сторони — удвічі ширші: назви контрагентів найдовші в таблиці, і саме їх
   читають. `.cc-parent` і `.cc-vat` пішли разом зі своїми стовпцями: основний
   договір називає підказка сходинки, ставку ПДВ — картка по кліку. */
.contract-list .cc-party { width: 260px; }
.contract-list .cc-subject { width: calc(var(--cdate) * 5); }
.contract-list .cc-object { width: 95px; }
/* `.cc-money` прибрано разом із двома грошовими стовпцями: обидва числа
   показує картка договору по кліку, і 236px тепер дістаються таблиці. */

/* Службові значення однорядкові й обрізаються; повне — у підказці */
.contract-list td { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* А ось назви сторін і предмет ПЕРЕНОСЯТЬСЯ. Це головне, що читають у цій
 * таблиці, і обрізати «ТОВ Субпідрядна організація…» трьома крапками означає
 * сховати саме те, заради чого в неї дивляться.
 *
 * Клас стоїть на КОМІРЦІ, а не лише на `<col>`: колонка вміє задавати ширину й
 * фон, але `white-space` крізь неї не передається — це властивість вмісту. */
.contract-list td.party, .contract-list td.subject {
  white-space: normal;
  overflow: visible;
  overflow-wrap: anywhere;
}
/* ПЕРЕВИТРАТА ЛІМІТУ — ПОЗНАЧЕНА, а не просто написана текстом: це відповідь,
   заради якої в панель сум і дивляться.
   Правило доти було прив'язане до `.contract-list` — стовпця ПДВ, якого вже
   немає, — і на панель сум не діяло ЖОДНОГО разу, хоча клас там стоїть від
   початку. Тобто «перевищення» писалося сірим нарівні з рештою. */
.contract-limits .vat-off { color: var(--custom-fg); font-weight: 600; }
/* Правило рядка `.contract-subject` прибрано разом із самим рядком: предмет
   показувався окремим рядком і лише в обраного договору, тобто порівняти два
   договори було неможливо. Тепер це звичайна колонка. */
/* Лічильник договорів у переліку контрагентів */
.folder .tag { margin-left: 4px; }

/* ГРОШІ ЛАНЦЮЖКА В КАРТЦІ ДОГОВОРУ: ліміт, законтрактовано, виконано, залишок.
 *
 * Доти цей `<dl>` не мав ЖОДНОГО правила — і браузер малював його як уміє:
 * підпис окремим рядком, значення наступним із відступом 40px. Три показники
 * з'їдали шість рядків екрана, а очима їх усе одно доводилося зшивати попарно.
 *
 * Той самий перелік «показник — значення», що `.act-facts` нижче, і зроблений
 * так само: перша колонка по вмісту, друга бере решту.
 *
 * `max-content 1fr`, а НЕ дві `max-content`: у рядку ліміту поруч із числом
 * живе довідка «(договір … + ДУ: N на …, без ПДВ)», і колонка по вмісту
 * випхала б її за край панелі.
 *
 * Клас власний, а не `.doc-meta`: той поділений із заголовком редактора
 * документа (`documents/editor.html`), де стоїть на `<p>` — сітка зламала б
 * його. Відступи панелі стиснуті тут, а не в `.form-panel`: цю панель читають,
 * а не заповнюють. */
.contract-limits { padding: 8px 14px; margin-bottom: 10px; }
.chain-facts {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 2px 12px;
  margin: 0;
  font-size: 12px;
}
.chain-facts dt { color: var(--ink-muted); }
.chain-facts dd { margin: 0; }

/* ---------- Форми записів (заміна InputBox) ---------- */
.form-panel {
  border: 1px solid var(--accent);
  border-left-width: 4px;
  background: #f7fbff;
  padding: 12px 14px;
  margin-bottom: 14px;
  max-width: 640px;
}
.form-panel h3 { margin: 0 0 10px; font-size: 14px; }
/* Відступ між полями стислий: у картці контрагента їх десять, у договорі
   одинадцять, і 8px між кожним розтягували форму на зайвий екран. */
.form-panel label {
  display: block;
  margin-bottom: 5px;
  font-size: 12px;
  color: var(--ink-muted);
}
.form-panel label.check { color: var(--ink); }
.form-panel input[type="text"],
.form-panel input[type="date"],
.form-panel input[type="password"],
.form-panel input:not([type]) {
  display: block;
  width: 100%;
  max-width: 420px;
  margin-top: 3px;
  padding: 4px 7px;
  font: inherit;
  border: 1px solid var(--line);
  border-radius: 3px;
}
.form-panel input[type="checkbox"] { margin-right: 6px; }
.form-panel .hint { font-size: 12px; color: var(--ink-muted); margin: 4px 0 10px; }
/* Форма на кілька коротких полів: логін, ім'я, пароль, роль. У 640px вони
 * розтягувалися на пів екрана, хоча в жодне не вводять більше двох слів.
 * Ширину задає панель — усередині і `input`, і `select` тягнуться на 100%, тож
 * поля виходять РІВНО однакові, зокрема вибір ролі. */
.form-panel-narrow { max-width: 320px; }
.form-panel-narrow h3 { margin-top: 0; }
/* Кнопка після поля, а не впритул до нього */
.form-panel-narrow button { margin-top: 6px; }
/* Кілька форм в одній панелі (картка користувача: ім'я, роль, пароль) — між
 * ними межа, інакше три «Зберегти» поспіль читаються як одна форма з трьома
 * кнопками. */
.form-panel form + form {
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}
/* КАРТКА ДОКУМЕНТА — ВИНЯТОК. Правило вище розділяє САМОСТІЙНІ форми панелі
 * (ім'я, роль, пароль у картці користувача), щоб три «Зберегти» поспіль не
 * читалися як одна форма. У картці документа форми дрібні й стоять усередині
 * стовпців — межа впоперек читалася б як розрив самої картки. */
.form-panel-doc .doc-req-row form + form,
.form-panel-doc .doc-req-row .form-col form {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

.form-panel input[type="file"] {
  display: block;
  width: 100%;
  margin-top: 3px;
  font: inherit;
  font-size: 12px;
}

/* Картка контрагента й договору мають по десятку полів — у 640px вони
 * перетворюються на довгий стовпчик, який не охопити поглядом. Звідси окремий
 * клас на три колонки.
 *
 * Доти стояло `max-width: 1040px`, а колонки були `1fr` — тобто вони
 * розтягувалися рівно настільки, скільки їм дадуть, і три стовпці розповзалися
 * по пів екрана, хоча найдовше поле в них — назва контрагента. Ширина підписів
 * (зокрема галочки ПДВ) на це не впливала ніяк.
 *
 * Тепер ширина колонки фіксована (230px), а панель — рівно під три такі колонки:
 * 3×230 + 2×18 проміжки + 28 внутрішніх відступів = 764px.
 *
 * `width: fit-content` тут НЕ годиться, хоч і напрошується: ширина панелі
 * залежала б від вмісту, а вміст (`auto-fit`) — від ширини панелі. Замкнене
 * коло, і браузер розв'язує його на користь ОДНІЄЇ колонки — перевірено. */
.form-panel-wide { max-width: 764px; }

/* ТРИ ЯВНІ КОЛОНКИ, а не автопотік. Раніше поля просто лилися зліва направо
 * (`grid-auto-flow: row`), і згрупувати їх за змістом було неможливо: порядок
 * визначала кількість колонок, яка залежала від ширини екрана. Тепер кожна
 * колонка — свій `.form-col`, а всередині неї поля йдуть згори вниз. */
.form-panel-wide form {
  display: grid;
  grid-template-columns: repeat(auto-fit, 230px);
  gap: 0 18px;
  align-items: start;
}
/* МІНІФОРМИ ВСЕРЕДИНІ ПАНЕЛІ — НЕ СІТКА. Правило вище писалося під єдину
 * тодішню форму панелі, а тепер у картці документа їх кілька: статус, копія,
 * «У теку». Кожна з них — обгортка навколо одного елемента, і двоколонкова
 * сітка розсаджувала б його по чужих комірках (курсам це вже коштувало
 * окремого правила — див. `.doc-rate-cell form`). */
.form-panel-wide form.inline-form {
  display: inline;
  grid-template-columns: none;
  gap: 0;
  margin: 0;
}
.form-col { min-width: 0; }
/* Ряд дій — на всю ширину сітки, а не в колонку полів */
.form-panel-wide .form-actions { grid-column: 1 / -1; }
/* Довга підказка — так само на всю ширину. У колонці 230px три речення про те,
 * звідки система дізнається, яка сторона наша, давали сім сірих рядків і
 * розтягували картку вдвічі, хоча праворуч від них було порожньо.
 *
 * Працює лише доти, доки абзац лишається ПРЯМИМ нащадком `<form>`: сіткою є
 * форма, і всередині `.form-col` `grid-column` мовчки не робить нічого. */
.form-panel-wide .hint-wide { grid-column: 1 / -1; margin: 0 0 8px; }

/* ---------- Реквізити документа ---------- */
/* ДВІ КОЛОНКИ РІЗНОЇ ШИРИНИ, і це єдина форма в системі, де вони не рівні.
 *
 * Ліворуч три КОРОТКІ поля (тип, дата, номер) — їм вистачає стандартних 230px.
 * Праворуч два ДОВГІ: у примітку пишуть речення, а підпис договору це сторони,
 * номер і дата в один рядок; 230px різали обидва. Півтора рази = 345px.
 *
 * Панель рівно під ці дві колонки: 230 + 345 + 18 проміжок + 28 внутрішніх
 * відступів = 621px замість 764px. Тобто блок став і нижчим, і вужчим — а
 * редактор відкривають заради таблиці під ним, не заради реквізитів. */
/* ТРЕТІЙ БЛОК ДОПИСАВ СОБІ ШИРИНИ: 621 + 18 проміжок + ~210 на курси. Висота
 * при цьому не зросла — курси стали поруч, а не під полями.
 *
 * ДВА КЛАСИ, БО СУСІД ВАЖЧИЙ. `.form-panel.form-panel-wide` вище ставить
 * `max-width: 1120px`, і одним класом його не перебити: заміряно — панель
 * лишалася 1120px завширшки з порожнечею праворуч від курсів. */
.form-panel.form-panel-doc { max-width: 849px; }
/* ЛІВА ЧАСТИНА КАРТКИ — СІТКА НА ОБГОРТЦІ, А НЕ НА ФОРМІ. Форм тут дві:
 * реквізити й статус, і вкласти другу в першу не можна — ані розміткою, ані по
 * суті (роззатвердження ходить іншим маршрутом і з іншим допуском).
 *
 * `display: contents` віддає колонки форми реквізитів зовнішній сітці, тож
 * форма статусу сама лягає в другий ряд першого стовпця — рівно під «Номер».
 * Поля при цьому надсилаються як і доти: розкладка не міняє того, чия форма. */
.form-panel-doc .doc-req-main {
  display: grid;
  grid-template-columns: 230px 345px;
  /* ДРУГИЙ РЯД ГНУЧКИЙ, І ЦЕ ТРИМАЄ СТАТУС ПІД «НОМЕРОМ». Доти обидва ряди були
   * `auto`, а висоту першого задавала ПРАВА колонка — примітка, договір,
   * підказка й ряд дій разом. Статус лежить у другому ряду, тож між ним і
   * «Номером» зяяло пів картки порожнечі.
   *
   * Тепер права колонка перетинає обидва ряди, а серед них є гнучкий (`1fr`) —
   * і сітка за своїм же правилом не міряє нею перший ряд. Перший ряд виходить
   * рівно заввишки з три коротких поля, а вся зайва висота дістається другому. */
  grid-template-rows: max-content 1fr;
  gap: 6px 12px;
  align-content: start;
}
.form-panel-doc .doc-req-form { display: contents; }
/* `:nth-of-type`, А НЕ `:nth-child`: між `<form>` і її колонками стоять
 * приховані поля (`csrf()` і стан редактора), і скільки їх — залежить від того,
 * що зараз розгорнуто. Рахунок серед `<div>`-ів від цього не залежить. */
.form-panel-doc .doc-req-form > .form-col:nth-of-type(1) { grid-column: 1; grid-row: 1; }
.form-panel-doc .doc-req-form > .form-col:nth-of-type(2) { grid-column: 2; grid-row: 1 / -1; }
/* Упритул до «Номера», а не по центру відданої висоти. */
.doc-status-col { grid-column: 1; grid-row: 2; align-self: start; }
/* Права частина: курси, а під ними — вивантаження, історія, копія. */
.doc-side { display: flex; flex-direction: column; gap: 8px; }
/* Кнопки стовпчиком і однакової ширини: вони читаються як перелік «що можна
 * винести назовні», а не як ряд, у якому шукають потрібну очима. */
.doc-side-actions { display: flex; flex-direction: column; gap: 4px; }
.doc-side-actions .btn { justify-content: center; }
/* «ІСТОРІЯ» Й «КОПІЯ» — ПОПОЛАМ В ОДНОМУ РЯДКУ. Обидві коротші за сусідів
 * («Вивантажити з розцінками», «Зберегти у теку»), і на всю ширину стовпця
 * читалися як ще два пункти того самого переліку «що можна винести назовні» —
 * хоча одна показує історію, а друга робить НОВИЙ документ.
 *
 * `auto-fit`, а не два жорсткі стовпці: «Копія» є лише в того, хто може
 * створювати документи, і без неї «Історія» має зайняти рядок цілком, а не
 * лишити половину порожньою. */
.doc-side-pair {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
  gap: 4px;
}
/* Форма «Копії» — обгортка навколо однієї кнопки; загальне правило панелі
 * робить її `display: inline`, і в сітці кнопка лишилася б завширшки з підпис. */
.doc-side-pair form.inline-form { display: block; }
.doc-side-pair .btn { width: 100%; }
/* `.form-panel-doc form { grid-template-columns: … }` прибрано. Сітка переїхала
 * на обгортку `.doc-req-main`, а форм у картці тепер кілька — статус, копія,
 * «У теку», курси, — і двоколонкова сітка рвала б кожну з них. Той самий
 * підступ уже коштував окремого правила курсам (див. `.doc-rate-cell form`). */
/* Довгі поля тягнуться на всю колонку. `max-width: 420px` із загального правила
 * тут завузьке для договору й зайве взагалі: ширину вже тримає сама колонка. */
.form-panel-doc .form-col:nth-of-type(2) input,
.form-panel-doc .form-col:nth-of-type(2) select { max-width: none; }

/* ВЕРТИКАЛЬ УЩІЛЬНЕНА. Реквізити дивляться секунду й закривають, а займали вони
 * третину екрана: 5px між полями, 3px над кожним полем, 10px над рядом дій і
 * 10px під підказкою складалися в зайвий екран прокрутки до самої таблиці.
 * Скрізь приблизно вдвічі менше — підписи й поля лишилися того ж кегля, стиснуті
 * тільки проміжки між ними. */
.form-panel-doc h3 { margin-bottom: 6px; }
.form-panel-doc label { margin-bottom: 2px; }
.form-panel-doc input, .form-panel-doc select { margin-top: 1px; }
.form-panel-doc .hint { margin: 3px 0 0; }
.form-panel-doc .form-actions { margin-top: 8px; }

/* Кнопки, відступ в одну кнопку, галочка ПДВ — в один рядок.
 * Галочка не реквізит, а перемикач розрахунку, тож стоїть біля дії, яку
 * змінює, а не серед полів. */
.form-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  margin-top: 10px;
}
/* Відступ в одну кнопку. Точної CSS-міри тут немає — кнопки різної ширини
   («Створити» 80px / «Скасувати» 85px), тож значення підібране виміром у
   браузері: 72px + два проміжки flex по 6px = 84px, тобто рівно кнопка. */
.actions-gap { width: 72px; }
/* «Видалити» відсунуте від «Зберегти» й «Скасувати»: дія незворотна, і сусідство
 * впритул із тими, які тиснуть щодня, — найкоротший шлях промахнутися. */
.form-actions .form-danger { margin-left: 24px; }
.form-actions .check { margin: 0; color: var(--ink); }
.form-actions .check input { margin-right: 6px; }
.field-hint { display: block; font-size: 11px; color: var(--ink-muted); }

/* Поле, на яке лається сервер. Форма при відмові НЕ закривається: введене
 * лишається на місці, а винне поле видно з першого погляду.
 * `:invalid` тут не годиться — правила на кшталт «замовник і підрядник мусять
 * різнитися» браузеру невідомі, їх знає лише сервер. */
.form-panel label.is-invalid { color: #c00000; font-weight: 600; }
.form-panel label.is-invalid input,
.form-panel label.is-invalid select {
  border-color: #c00000;
  border-width: 2px;
  background: #fff6f6;
}
/* Порожнє обов'язкове поле браузер ловить сам — підсвічуємо так само, щоб
   червоне означало одне й те саме незалежно від того, хто його знайшов */
.form-panel input:required:invalid,
.form-panel select:required:invalid { border-color: #e08080; }

.form-error { color: #c00000; font-weight: 600; margin: 0 0 8px; }
.blockers { margin: 0 0 10px; padding-left: 22px; color: #c00000; }

.btn-primary { background: var(--accent); color: #fff; border-color: var(--accent); }
.btn-primary:hover { background: #0060d0; }
/* ВИДАЛЕННЯ — ЗВИЧАЙНА КНОПКА З ЖИРНИМ ЧЕРВОНИМ ПІДПИСОМ, а не суцільна червона
   заливка. Заливка кричала: на «Розцінках» таких кнопок три поспіль, і вони
   перетягували увагу з того, заради чого екран відкривали. Червоний колір
   лишається — він і є попередженням, — але тепер це колір СЛОВА, а не плями.

   Правило одне на всю систему: клас `btn-danger` носять усі 25 кнопок у 15
   шаблонах, від «Видалити папку» до «Видалити документ». */
.btn-danger { background: #ebebeb; color: var(--danger); border-color: #969696;
              font-weight: 700; }
.btn-danger:hover { background: #e0e0e0; }
.btn.is-disabled { opacity: .45; pointer-events: none; }

.inline-form { display: inline; }

/* Повідомлення після запису (заміна MsgBox) */
.flash {
  padding: 7px 12px;
  margin: 0 0 12px;
  border-left: 4px solid;
  font-size: 13px;
  max-width: 900px;
}
.flash-error { background: #fdecea; border-color: #c00000; color: #7a0000; }
.flash-ok { background: #eaf7ea; border-color: #2e7d32; color: #1b5e20; }

/* Пробна версія: смужка під шапкою (див. `base.html`, `app/plan.py`). Спокійна,
 * доки строку вистачає; жовтіє за два тижні до кінця; червоніє, коли лишився
 * тільки перегляд. */
.trial-strip {
  padding: 5px 16px;
  font-size: var(--fs-ui);
  border-bottom: 1px solid;
  background: #eef4fb; border-color: #bdd7ee; color: #1f3b5a;
}
.trial-soon { background: #fff4d6; border-color: #e0b84c; color: #6b4a00; }
.trial-expired { background: #fdecea; border-color: #c00000; color: #7a0000; }

/* Тост: повідомлення про успіх виринає ПОВЕРХ сторінки й саме зникає за 3 с.
 * Воно нічого не вимагає від людини, тож не має ні займати місце в потоці, ні
 * чекати, доки його закриють. Помилки так НЕ поводяться — їх треба прочитати.
 *
 * Без JS: `forwards` лишає елемент у кінцевому стані анімації.
 *
 * `pointer-events: none` — не косметика, а виправлення справжнього дефекту.
 * Спершу тут покладалися на `visibility: hidden` у кінцевому кадрі, але браузер
 * її звідти не застосовує: після зникнення `opacity` дорівнювала 0, а
 * `visibility` лишалася `visible`. Прозорий блок і далі висів над правим
 * верхнім кутом і ЇВ КЛІКИ — саме там, де кнопка «Вийти» (перевірено
 * `elementFromPoint`). Повідомленню про успіх кліки не потрібні взагалі, тож
 * найнадійніше просто прибрати його з-під курсора назавжди. */
.toast {
  position: fixed;
  top: 14px;
  right: 18px;
  z-index: 50;
  max-width: 420px;
  margin: 0;
  pointer-events: none;
  border: 1px solid #2e7d32;
  border-left-width: 4px;
  border-radius: 4px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, .18);
  animation: toast-in .18s ease-out, toast-out .45s ease-in 3s forwards;
}
@keyframes toast-in {
  from { opacity: 0; transform: translateY(-12px); }
  to { opacity: 1; transform: none; }
}
@keyframes toast-out {
  to { opacity: 0; transform: translateY(-12px); }
}
/* Той, хто вимкнув анімації, не має отримати повідомлення-привида: лишаємо
 * його видимим назавжди замість миттєвого зникнення. */
@media (prefers-reduced-motion: reduce) {
  .toast { animation: none; }
}

.tree-link { color: inherit; text-decoration: none; }
.tree-link:hover { text-decoration: underline; }

/* Inline-редагування норми складу розцінки */
.norm-input {
  width: 84px;
  padding: 2px 5px;
  font: inherit;
  text-align: right;
  border: 1px solid var(--line);
  background: var(--editable-bg);
  border-radius: 2px;
}
.norm-input:focus { outline: 2px solid var(--accent); outline-offset: -1px; }

/* `width: 100%` тут обов'язковий, а не для симетрії з `input`. Без нього
 * `<select>` бере ширину від НАЙДОВШОГО пункту переліку, а найдовший пункт —
 * підпис договору («№12 від 01.03.2026 — Замовник → Виконавець»). У колонці
 * 230px такий select вилазив за межі панелі на 170px. */
.form-panel select {
  display: block;
  width: 100%;
  max-width: 420px;
  margin-top: 3px;
  padding: 4px 7px;
  font: inherit;
  border: 1px solid var(--line);
  border-radius: 3px;
}
.form-panel .cascade { margin-bottom: 10px; }

/* Inline-редагування комірок специфікації.
 *
 * `min-width` НЕМАЄ, і це не дрібниця. Доти стояло `min-width: 60px`, а колонка
 * «Норма корист.» має 84px, з яких на вміст лишається 70. Поле не могло стиснутись
 * нижче 60px, поруч стояла кнопка «↺» — і разом вони не вміщалися: довга
 * арифметична норма виходила за межі комірки й накривала сусідні.
 * Ширину задає КОЛОНКА (`table-layout: fixed`), а поле стискається разом із нею. */
.cell-input {
  width: 100%;
  min-width: 0;
  padding: 0 4px;
  font: inherit;
  border: 1px solid transparent;
  background: var(--editable-bg);
  border-radius: 2px;
}
.cell-input.num { text-align: right; }
/* Шаблонна підказка («Нова позиція») — саме `placeholder`, а не текст у базі.
 * Тому вона сіра, зникає з першим уведеним символом і повертається, щойно поле
 * спорожніло, — без жодного скрипта. */
.cell-input::placeholder { color: var(--ink-muted); font-style: italic; }

/* КОМІРКА-КОНТЕЙНЕР — СПІЛЬНА ОБГОРТКА ДЛЯ БУДЬ-ЯКОЇ РОЗКЛАДКИ В КОМІРЦІ.
 * Три комірки специфікації тримають кілька елементів в один рядок: найменування
 * (маркер + поле), обсяг (номер джерела + поле), норма й коефіцієнт (поле +
 * «↺»); тим самим живуть сума в журналі документів, обсяг у відомості обмірів і
 * кнопки рядка в оплатах — там значок стоїть при лівому краї, число при правому.
 *
 * ЧОМУ ОБГОРТКА, А НЕ `display: flex` НА `td`. Так було доти — і `td` через це
 * ПЕРЕСТАВАВ БУТИ КОМІРКОЮ ТАБЛИЦІ. Два наслідки, обидва помітні:
 *   1) комірка більше не тягнеться на висоту рядка. У «Норма корист.» і
 *      «Коеф. корист.» вміст порожній для будь-якої роботи верхнього рівня, тож
 *      підсвітка виділеного рядка вироджувалася там у вузьку смужку, а решта
 *      рядка була залита;
 *   2) `table-layout: fixed` до такої комірки не застосовується — ширина колонки
 *      переставала бути обмеженням, і вміст витікав на сусідів;
 *   3) при `border-collapse: collapse` рамка такої комірки більше не склеюється
 *      із сусідніми, а малюється власна ПОВЕРХ спільної — уся колонка стоїть у
 *      видимо жирнішій лінії, ніж решта таблиці. Саме так стояли колонка сум у
 *      журналі, обсяг обміру й кнопки оплат, поки не переїхали сюди.
 * Тепер `td` лишається коміркою, а flex живе на `div` усередині неї. Сторожа
 * тримає `test_findings.py`. */
.cell-box { display: flex; align-items: center; gap: 2px; min-width: 0; }
.cell-box .inline-form { flex: 1 1 auto; min-width: 0; }
/* ПІДПИС ПРИВИДА ЗАЙМАЄ МІСЦЕ ПОЛЯ. У живому рядку найменування — поле вводу, яке
 * розтягується на всю комірку, і все, що стоїть за ним (замок, «З», пакет),
 * притискається до правого краю. У привида поля немає, лише текст, і без цього
 * правила замок сідав одразу за останньою літерою — у кожному рядку на іншій
 * висоті колонки, тож очима його було не знайти. */
.cell-box > .cell-ghost.wide { flex: 1 1 auto; min-width: 0; }
/* Комірка найменування: маркер «+»/«−» і поле в ОДИН рядок.
 * `min-width: 220px` на полі звідси прибрано свідомо — саме він розпирав
 * комірку, і назва зривалася на другий рядок під маркер. */
.grid td.cell-name,
.grid td.cell-qty,
.grid td.cell-with-revert { vertical-align: middle; }
.cell-qty .cell-box { gap: 3px; }
.cell-box .revert-form { flex: 0 0 auto; }
/* ПОЗНАЧКА ПАКЕТА — праворуч від найменування, у тій самій комірці.
 * Окрема колонка коштувала б ширини ВСІМ рядкам заради ознаки, яка є в меншості
 * з них. `flex: 0 0 auto` не дає їй стискатися, коли назва довга: обрізатися має
 * назва, а не позначка, заради якої позначку й ставили. */
.pkg-tag {
  flex: 0 0 auto;
  font-size: 10px;
  line-height: 1.3;
  padding: 0 4px;
  border: 1px solid var(--accent);
  border-radius: 8px;
  background: #e3ecf7;
  color: var(--accent);
  white-space: nowrap;
  max-width: 120px;
  overflow: hidden;
  text-overflow: ellipsis;
}

.cell-input:hover { border-color: var(--line); }
.cell-input:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
/* ПОЛЕ В РЯДКУ СКЛАДУ — ГЛИБШИЙ ЖОВТИЙ. Сам рядок кремовий і майже білий, тож
 * звичайний `--editable-bg` на ньому зникав: не було видно, де в рядку взагалі
 * можна писати. У рядку роботи фон сіро-теплий, і там жовтий читається сам. */
.row-child .cell-input { color: var(--ink-muted); background: #fff5c4; }
.cell-input.qty-custom { color: var(--custom-fg); }
/* Похідне значення: показуємо, але воно нічим не керує. Норма під ручним
   обсягом саме така — обсяг стоїть у каскаді вище, тож правити тут нема чого.
   Сірий і курсив — те саме, чим у таблицях позначено «недоступно». */
.derived { color: var(--ink-muted); font-style: italic; }
/* Результат виразу НЕ СТИСКАЄТЬСЯ: у flex-комірці поле вводу росте й тисне на
 * сусіда, і стиснене «= 146,00» перетворювалося б на «= 1…». Стискається саме
 * поле — воно й так показує початок виразу, а результат мусить бути цілий. */
.cell-box .derived { flex: 0 0 auto; }

/* ЗВІДКИ БЕРЕТЬСЯ ОБСЯГ: номер рядка-джерела ЛІВОРУЧ від самого обсягу.
 *
 * Доти стрілка стояла ПІСЛЯ поля обсягу, притиснута до правого краю комірки, —
 * і читалася як підпис до сусідньої колонки цін, а не до обсягу, яким керує.
 *
 * Червона й жирна навмисно: це не оформлення, а попередження, що число в полі
 * поруч не самостійне — його перерахує зміна зовсім іншого рядка. */

/* «↺» — У ТІЙ САМІЙ КОМІРЦІ, ЩО Й ЧИСЛО, яке скасовує (обсяг, норма,
 * коефіцієнт). Доти кнопка була одна на весь рядок, в окремому стовпці, і мовчки
 * стосувалася «найголовнішого» перевизначення — тобто повернути саме коефіцієнт,
 * маючи ще й перевизначену норму, було неможливо.
 *
 * Кнопка дрібна й без рамки: у комірці головне — число, а вона підказка при
 * ньому. Показується лише там, де є що скасовувати, тож поява значка сама по
 * собі означає «тут значення введене руками». */
.revert-mini {
  border: none;
  background: none;
  padding: 0 1px;
  font-size: 11px;
  line-height: 1;
  color: var(--ink-muted);
  cursor: pointer;
}
.revert-mini:hover { color: var(--accent); }
/* «₴» — повернути ДОГОВІРНУ ціну. Стоїть поруч із «↺», тож мусить від нього
   відрізнятися з першого погляду: інша літера й інший колір. Дії різні —
   «↺» віддає рядок каскадові, «₴» ставить ціну з рядка ДЦ. */
.revert-source { color: var(--accent); font-weight: 700; }
.revert-source:hover { color: var(--danger, #b00020); }
/* «З» — ресурс постачає замовник. На відміну від «↺» і «₴», це ПЕРЕМИКАЧ, а не
   разова дія: він стоїть у комірці ЗАВЖДИ (там, де взагалі можливий) і мусить
   з першого погляду казати, увімкнений він чи ні.
   Вимкнений — блідий контур, щоб не змагатися очима з шифром поруч; увімкнений
   — залитий, бо саме він міняє розкладку грошей у підсумку. */
.supply-mini {
  flex: 0 0 auto;
  min-width: 15px;
  border: 1px solid var(--ink-muted);
  border-radius: 3px;
  font-weight: 700;
  opacity: .45;
}
.supply-mini:hover { opacity: 1; }
/* ЗАМОК ПОЗИЦІЇ — КОЛОДКА, ЧОРНА В ОБОХ СТАНАХ. Стан читається ФОРМОЮ, а не
 * блідістю: дужка замкнена або відкинута. Бліда відкрита колодка губилася
 * поруч із назвою, і перемикач здавався вимкненим елементом, а не кнопкою.
 *
 * Чорна, а не синя: синім у рядку вже говорить поставка замовника («З»), і
 * замок сперечався б із нею за значення, якого не має. */
.close-mini {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  border: 0;
  background: none;
  padding: 0 1px;
  color: var(--ink);
  cursor: pointer;
}
.close-mini svg { width: 14px; height: 14px; display: block; }
.close-mini:hover { color: var(--accent); }

/* ЗАМКНЕНА ПОЗИЦІЯ — СІРИЙ ТЕКСТ, ЗАЛИВКИ ЛИШАЮТЬСЯ. Колір тла тут уже каже
 * своє — рівень, виділення, поле вводу, — і стан «більше не чекаємо» мусить
 * читатися ПОРУЧ із ним, а не замість нього. Тому лише `color`, і в полях вводу
 * так само: більшість тексту рядка стоїть саме в них. Сама колодка лишається
 * чорною — це кнопка, а не вміст. */
.row-closed td,
.row-closed .cell-input,
.row-closed .cell-ghost { color: var(--ink-muted); }
.row-closed .close-mini { color: var(--ink); }
.supply-mini.is-on {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  opacity: 1;
}
/* Рядок «в т.ч. поставки Замовника» — усередині «Разом», а не поруч із ним.
   Приглушений, щоб не читався як ще один самостійний підсумок. */
.total-supplied td { color: var(--ink-muted); font-weight: 400; }
/* На пункт дрібніше за решту комірки: це ПОЗНАЧКА біля числа, а не саме число.
 * Разом зі знятою стрілкою (див. `_structured.html`) звільняє в найвужчій
 * колонці таблиці місце, якого бракувало обсягу, — доти довге число виходило
 * за межі комірки. */
.link-ref {
  flex: 0 0 auto;
  font-size: 10px;
  font-weight: 700;
  color: #c00000;
  white-space: nowrap;
  cursor: help;
}
/* Той самий номер, але клікабельний: він відповідає на питання «звідки обсяг»,
 * тож клік по ньому означає «змінити звідки». Підкреслення лише під курсором —
 * інакше позначка на 10px перетворилася б на смугу. */
a.link-ref { cursor: pointer; text-decoration: none; }
a.link-ref:hover { text-decoration: underline; }

/* ---------- Вибір джерела обсягу ---------- */
/* СМУГА РЕЖИМУ ЛИПКА, і це не косметика: джерело шукають прокруткою через увесь
 * документ, а множник зі «Скасувати» мусять лишатися на очах. Єдине `sticky` у
 * проєкті — тому правило стоїть при самій смузі, а не в спільному місці. */
.link-bar {
  position: sticky;
  top: 0;
  z-index: 5;
  margin-bottom: 8px;
  padding: 8px 12px;
  border: 1px solid var(--accent);
  background: var(--header-bg);
}
.link-bar form {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 12px;
}
.link-bar-what { margin: 0; flex: 1 1 320px; }
.link-bar-target { font-weight: 700; }
.link-bar-factor { display: inline-flex; align-items: baseline; gap: 6px; }
.link-bar-factor input { width: 80px; }
.link-bar-actions { display: flex; gap: 6px; }
.link-bar-hint { margin: 6px 0 0; color: var(--ink-muted); font-size: 12px; }

/* КОМІРКА ОБСЯГУ — КНОПКА. Виглядає як число, а не як кнопка: у режимі вибору
 * такою стає кожна комірка колонки, і тисяча сірих прямокутників зробила б
 * таблицю нечитабельною. Про можливість клацнути каже підсвічена комірка
 * (`.cell-pickable`) і курсор. */
/* `flex`, А НЕ `width: 100%`: комірка обсягу — flex-контейнер (`.cell-box`), і
 * поруч із кнопкою в ній буває червоний номер джерела. Ширина у відсотках від
 * контейнера тоді виходить за його межі — число просто зникає за краєм. */
.qty-pick {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: right;
  cursor: pointer;
}
.cell-pickable { background: var(--pick-bg, #eef6ff); }
.cell-pickable:hover { background: var(--accent); }
.cell-pickable:hover .qty-pick { color: #fff; }
/* Рядки, звідки брати не можна: число лишається на місці, але приглушене — і
 * підказка називає причину. Прибрати їх зовсім означало б діру в таблиці. */
/* Курсор звичайний — з тієї ж причини, що в `.btn[disabled]`: підказка вже
 * пояснює, чому звідси не можна, а знак заборони лише кричить про це. */
.qty-blocked { color: var(--ink-muted); cursor: default; }

/* ---------- Зміна ширини стовпця за межу в шапці ---------- */
/* Ручку малює `columns.js` у кожному заголовку таблиці з `data-cols`. Смуга
   вузька (6px) і прозора: видимою межею лишається сама рамка комірки, а курсор
   каже, що за неї можна взятися. Вилазить на пару пікселів за край комірки —
   так у неї попадаєш, цілячись у саму лінію, а не поруч із нею. */
.grid thead th { position: relative; }
.col-grip {
  position: absolute;
  top: 0;
  bottom: 0;
  right: -3px;
  width: 6px;
  cursor: col-resize;
  /* Поверх вмісту сусідньої комірки, інакше ручку перекриває її текст */
  z-index: 2;
}
.col-grip:hover { background: var(--accent); }
/* ОСТАННЯ РУЧКА — УСЕРЕДИНІ ТАБЛИЦІ, а не верхи на її межі.

   Решта ручок сидять на межі між стовпцями (`right: -3px`), і три пікселі
   назовні там нікому не заважають — з того боку ще один стовпець. В останнього
   стовпця з того боку край таблиці, а таблиця займає всю ширину контейнера:
   ручка вилазила на три пікселі за неї, і контейнер чесно показував унизу
   повзунок на ті самі кілька міліметрів. Помітно це ставало саме після
   перемикання режиму — бо перемикання повертало ширини стовпців до
   CSS-замовчувань, тобто до таблиці «рівно по контейнеру». */
thead th:last-child > .col-grip { right: 0; }

/* ЗАГОЛОВОК-ПЕРЕМИКАЧ («Обсяг» розгортає норми з коефіцієнтами).
 *
 * ВИГЛЯДАЄ ЯК ЗВИЧАЙНИЙ ЗАГОЛОВОК, і це навмисно: підпис лишається підписом, а
 * про можливість клацнути каже маркер поруч — той самий «+», що в гілках
 * специфікації й у блоці ціноутворення. Синє посилання посеред шапки читалося б
 * як щось стороннє, вставлене в таблицю.
 *
 * `inline-flex` із переносом — бо підпис у вузькій колонці переноситься, і
 * маркер має лишатися при ньому, а не з'їжджати окремим рядком. */
.col-toggle {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 2px;
  color: inherit;
  text-decoration: none;
}
.col-toggle:hover { text-decoration: underline; }

/* ---------- Перетягування рядків специфікації ---------- */
/* Заступило кнопки «Вгору/Вниз». Курсор і напівпрозорість — єдине, що каже
   людині «це можна тягнути»; лінія показує, куди саме рядок стане. */
.spec tr[draggable="true"] { cursor: grab; }
.spec tr.is-dragging { opacity: .4; }
.spec tr.drop-before td { box-shadow: inset 0 2px 0 var(--accent); }
.spec tr.drop-after td { box-shadow: inset 0 -2px 0 var(--accent); }

/* `width: 100%` замість `max-width`: доти список був ширший за свою колонку
 * (80px проти 62px) і накривав сусідню комірку. Тепер він рівно по комірці,
 * якою б вузькою вона не стала. */
.unit-select {
  font: inherit;
  padding: 0 2px;
  border: 1px solid transparent;
  background: transparent;
  width: 100%;
  min-width: 0;
  box-sizing: border-box;
}
.unit-select:hover { border-color: var(--line); background: var(--editable-bg); }

.toolbar select {
  font: inherit;
  min-height: var(--ctl-h);
  padding: 0 6px;
  border: 1px solid #969696;
  border-radius: 4px;
}

/* ---------- Бібліотечний пікер редактора ---------- */
/* Три колонки поруч: дерево папок | позиції | розцінки. Вигляд узятий з
 * «Розцінок» (`.folder-tree`, `.folder`, `.panel-header`) — нових правил під
 * дерево не заводимо, інакше два майже однакові набори почали б розходитися.
 * Тут лише те, чого в тому екрані немає: розкладка панелі та перелік позицій
 * із подвійним кліком. */
.library-panel { max-width: 1100px; position: relative; }
/* ЗАГОЛОВОК І ТИПИ — ОДНИМ РЯДКОМ. Типів чотири, підписи однослівні, і власний
   рядок під них з'їдав висоту самого переліку, заради якого пікер відкривають. */
.lib-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; }
.lib-types { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px; }
.lib-step { color: var(--ink-muted); font-size: 12px; margin-right: 4px; font-weight: 400; }
.lib-layout { display: flex; gap: 14px; align-items: flex-start; }
/* ЩІЛЬНІСТЬ — ЯК У САМОМУ ДОКУМЕНТІ (12px, відступ 1px). Пікер стоїть просто над
   специфікацією, і два різні кеглі поруч читалися як два різні екрани; до того ж
   кожен зайвий піксель у рядку — це мінус позиція у видимій частині переліку. */
.library-panel .folder, .library-panel .lib-item, .library-panel .panel-header {
  padding: 1px 6px;
  font-size: 12px;
  line-height: 1.2;
}
.library-panel .panel-header { margin: 0 0 2px; }
.library-panel .empty, .library-panel .hint { font-size: 12px; margin: 2px 0; }
/* Папки вужчі за позиції: у назві папки два-три слова, у назві роботи — рядок */
.lib-folders { flex: 0 0 300px; min-width: 0; }
.lib-items { flex: 1 1 auto; min-width: 0; }
.lib-recipes { flex: 0 0 220px; min-width: 0; }
/* Дерево в панелі вужче за головну таблицю — крок відступу відповідно менший. */
.lib-folders .folder-tree { --tree-step: 16px; }

/* ДЕРЕВО З ПЛЮСИКАМИ: маркер стоїть ПЕРЕД посиланням, тож рядок — flex. Одне
   правило на обидва каталоги — і в пікері редактора, і в «Розцінках»: це те
   саме дерево папок, показане з двох екранів. */
.lib-folders .folder-tree li,
.folder-tree-foldable li { display: flex; align-items: center; }
.lib-folders .folder,
.folder-tree-foldable .folder { flex: 1 1 auto; min-width: 0; }
/* Папку переставляють перетягуванням, і курсор — єдине, що про це каже: рядок
   водночас лишається посиланням, і без `grab` він виглядав би як звичайний
   перехід. Те саме правило, що в дереві «Планування». */
.folder-tree-foldable[data-sort-links] .folder { cursor: grab; }
.folder-tree-foldable[data-sort-links] li.is-dragging .folder { cursor: grabbing; }

/* ПЕРЕЛІКИ ВИЩІ В ПІВТОРА РАЗА (320px -> 480px). Пікер відкривають, щоб знайти
   позицію в бібліотеці на кількасот записів; при 320px у вікно потрапляло
   близько дюжини, тобто пошук перетворювався на прокручування. Дерево папок
   тримає ту саму висоту — колонки поруч і мусять закінчуватись однаково. */
.lib-list, .lib-folders .folder-tree {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 480px;
  overflow-y: auto;
}
.lib-list li { display: flex; align-items: center; gap: 4px; }
.lib-item {
  flex: 1 1 auto;
  min-width: 0;
  display: block;
  padding: 2px 6px;
  border: 1px solid var(--line);
  border-top: none;
  text-decoration: none;
  color: var(--ink);
  font-size: 13px;
  text-align: left;
  background: #fff;
  cursor: pointer;
}
.lib-list li:first-child .lib-item { border-top: 1px solid var(--line); }
.lib-item:hover { background: #f4f8fd; }
.lib-item.is-selected { background: var(--selected-bg); font-weight: 600; }
/* «взяти» СХОВАНА, а не видалена: позицію бере подвійний клік, і саме цю кнопку
   він і натискає (`ondblclick`), тобто дія йде тим самим POST, що й раніше.
   Видима вона була біля КОЖНОГО рядка — сотня кнопок, які половинили ширину
   назв заради жесту, що вже є. */
.lib-take { display: none; }
.lib-recipe { border: 1px solid var(--line); font-weight: 600; }

/* ХРЕСТИК ЗАКРИТТЯ — у правому верхньому кутку панелі. Кнопка «Закрити» стояла
   ПІД переліком: щоб закрити пікер, доводилося спершу прокрутити його до кінця.
   Червоний і жирний — це вихід, а не ще одна дія над бібліотекою. */
.panel-close {
  position: absolute;
  top: 4px;
  right: 8px;
  font-size: 20px;
  font-weight: 700;
  line-height: 1;
  color: #c00000;
  text-decoration: none;
  padding: 0 4px;
}
.panel-close:hover { color: #ff0000; }

/* ---------- Актування ---------- */
/* Окремий розділ «Актування» звідси пішов разом зі своїми трьома кроками й
   власною відомістю — усе це вміє сам РЕДАКТОР ДОКУМЕНТА. Заміна ДЦ переїхала
   в нього ж і стилі свої зберегла: вони внизу цього блоку. */
/* Перелік документів — плитками в рядок: їх одиниці, і стовпчик на весь екран
   заради трьох ДЦ був би марнотратством висоти. */
/* ПЕРЕЛІК АКТІВ — ЗВИЧАЙНА ТАБЛИЦЯ СИСТЕМИ (`.grid.narrow`), тож власних правил
   під нього майже не треба. Тут лише те, чого в спільних немає: рядок обирає
   акт, тобто клікається цілком, а затверджений — ні. */
/* ПАНЕЛЬ — ЗА ШИРИНОЮ ТАБЛИЦІ. Стовпці тягнуть мишею (`data-cols`), і панель
   мусить рости й меншати разом із таблицею, а не обрізати її своїм `max-width`
   чи тягнутися сірою смугою на весь екран. На вузькому екрані прокручується
   сама панель, а не сторінка. */
.act-pick-panel {
  padding: 8px 10px;
  width: fit-content;
  max-width: 100%;
  box-sizing: border-box;
  overflow-x: auto;
}
/* ФІКСОВАНІ СТОВПЦІ, А НЕ ЗА ВМІСТОМ. Доти таблиця була `.grid.narrow`: довгий
   номер акта розпирав свій стовпець, примітка стискала решту, і ряд виходив
   нерівним. Тепер ширини задані колонками, а текст переноситься всередині них.
   Разом — ширина таблиці; `columns.js` перепише обидва, щойно потягнуть межу. */
.act-pick-grid { margin-bottom: 8px; table-layout: fixed; width: 780px; }
.act-pick-grid .apc-number { width: 220px; }
.act-pick-grid .apc-date   { width: 90px; }
.act-pick-grid .apc-status { width: 110px; }
.act-pick-grid .apc-sum    { width: 110px; }
.act-pick-grid .apc-notes  { width: 250px; }
.act-pick-grid td { overflow-wrap: anywhere; }
/* «до ДЦ №…» під номером акта основної ДЦ у переліку додаткової. */
.act-pick-grid .act-sub { display: block; }
.act-pick-grid tbody tr { cursor: pointer; }
.act-pick-grid tbody tr:hover td { background: var(--bld-4); }
/* Затверджений акт у переліку є, але не клікається: у нього нічого не допишеш.
   Показати його треба — це історія; обіцяти дію, яка відмовить, не треба.
   Курсор звичайний — з тієї ж причини, що в `.btn[disabled]`. */
.act-pick-grid tbody tr.is-disabled { opacity: .55; cursor: default; }
.act-pick-grid tbody tr.is-disabled:hover td { background: none; }
.act-sub { color: var(--ink-muted); font-size: 11px; font-weight: 400; }

/* ПЕРЕБІР НАД ДОГОВІРНИМ ОБСЯГОМ — червоним і жирним: перевиконання буває, але
   пройти непоміченим не має. */
.act-over { color: var(--danger, #b00020); font-weight: 700; }
/* ЗАЛИШОК — У ДУЖКАХ ЛІВОРУЧ ВІД ЗААКТОВАНОГО, дрібним і приглушеним. Це два
   боки одного числа: скільки вже пішло в акти й скільки ще можна. Власна
   колонка під другий бік коштувала ширини, якої в таблиці на 15 колонок немає,
   а перебір читається тим самим числом зі знаком мінус. */
.act-left { color: var(--ink-muted); font-size: 10px; margin-right: 4px; }
.act-over .act-left { color: inherit; font-weight: 400; }
/* Рядок специфікації відкривається подвійним кліком — курсор має про це казати. */
.spec tbody tr[ondblclick] { cursor: pointer; }
/* Позиція, дописана в самому акті: у договорі її немає, і рядок про це каже —
   лівий край підсвічений, як позначка «це не з договору». */
.row-from-act td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
/* ПРИВИД — позиція договору, якої в обраному акті ще немає. Вона бліда, бо в
   цьому акті її і справді ще немає: договірний обсяг видно, грошей немає,
   правити нічого. Не сіра «вимкнена», а саме бліда: вимкнене обіцяє, що колись
   оживе на місці, а привид оживає від зовсім іншої дії — картки з обсягом. */
.row-ghost td { background: #fafafa; color: var(--ink-muted); }
.row-ghost .cell-ghost,
.row-ghost .row-pick.is-ghost { color: var(--ink-muted); }
.cell-ghost { display: inline-block; padding: 1px 3px; }
/* Підказка замість кнопок редагування, поки акт не обрано. */
/* РОЗБІЖНІСТЬ ІЗ ДОГОВОРОМ — синій курсив. Курсив, а не тільки колір: у
   специфікації синім уже позначено «введено руками» (`.qty-custom`), і другий
   синій без різниці в накресленні читався б як те саме. Позначаються і
   змінена комірка, і ті, що через неї порахувалися інакше, — включно з полем
   вводу всередині, інакше саме число лишилося б чорним. */
.val-off,
.val-off .cell-input,
.val-off .derived {
  color: var(--accent);
  font-style: italic;
}
/* «Закрити» картки — у її правому верхньому куті, як хрестик пікера: місце для
   «прибрати це з екрана» в системі одне. */
.act-detail { position: relative; }
.act-close {
  position: absolute;
  top: 0;
  right: 0;
  color: var(--ink-muted);
  font-size: 12px;
  text-decoration: none;
}
.act-close:hover { color: var(--danger, #b00020); }
/* Ряд вибору акта стоїть під тулбаром, а не збоку: розгортає його кнопка з
   того самого ряду. */
/* Створення акта — ОДНИМ РЯДКОМ під таблицею: дата, номер, кнопка. Поля тут
   вузькі, і це не дрібниця: спільне правило панелі тягне кожне на всю її
   ширину, тобто три коротких поля з'їдали б три рядки замість одного. */
.act-new {
  /* `display` ЗАДАЄМО САМІ: `.inline-form` робить форму рядковим елементом, і
     її діти-мітки лишалися б блоками на всю ширину панелі — три коротких поля
     ставали б трьома рядками. */
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}
.act-new label { display: flex; align-items: center; gap: 4px; margin: 0;
                 font-size: 12px; }
.form-panel .act-new input[type="date"] { width: 140px; margin-top: 0; }
.form-panel .act-new input:not([type]) { width: 110px; margin-top: 0; }

/* Картка позиції розгортається ПІД своїм рядком — не збоку й не вгорі сторінки:
   те, що розгорнули, має лишатися там, куди дивилися. */
.act-detail-row td { background: #f7fbff; border-top: 2px solid var(--accent); }
.act-detail h4 { margin: 0 0 6px; font-size: 13px; }
/* Факти двома колонками: підпис і значення. `dl` замість таблиці, бо це не
   таблиця даних, а перелік «показник — значення». */
.act-facts { display: grid; grid-template-columns: max-content 1fr;
             gap: 2px 12px; margin: 0 0 8px; font-size: 12px; }
.act-facts dt { color: var(--ink-muted); }
.act-facts dd { margin: 0; }
.act-breakdown { list-style: none; margin: 0; padding: 0; }
.act-breakdown li { padding: 1px 0; }
.act-detail-actions { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.act-qty-input { width: 110px; font: inherit; font-size: 12px; padding: 2px 6px;
                 border: 1px solid var(--accent); border-radius: 3px;
                 text-align: right; }

/* Заміна ДЦ — небезпечна дія (переписує посилання в підписаних актах), тож блок
 * і виглядає інакше: тепла рамка, яка не дає прийняти його за решту кроків. */
.act-replace { border: 1px solid #d9a441; background: #fffdf6; padding: 8px 10px;
               margin-bottom: 10px; max-width: 900px; }
.act-replace-form { display: flex; flex-wrap: wrap; gap: 8px; align-items: flex-end;
                    margin-bottom: 8px; }
.act-replace-form label { font-size: 12px; color: var(--ink-muted); margin: 0; }
.act-replace-form select { display: block; margin-top: 2px; font: inherit;
                           font-size: 12px; padding: 2px 6px; max-width: 420px; }
.act-plan { border-top: 1px solid #e2cfa5; padding-top: 8px; }
.act-plan-table { table-layout: fixed; width: 100%; max-width: 720px;
                  margin: 6px 0; }
.act-plan-table td, .act-plan-table th { padding: 1px 6px; }
/* Застереження — не помилка: інший колір заголовка, той самий вигляд переліку. */
.act-warn-head { color: #8a5a00; font-size: 12px; margin: 8px 0 2px; font-weight: 600; }
.act-warn li { color: #8a5a00; }

/* ---------- ТЕП: приміщення та площі ---------- */
/* ДВІ ПАНЕЛІ, І ШИРИНА МІЖ НИМИ ПОДІЛЕНА ЗА ВМІСТОМ, а не порівну.
 *
 * Ліворуч тепер не лише дерево, а й самі приміщення: назва вузла з відступом на
 * кожен рівень, номер приміщення й три колонки площ. Праворуч — бібліотека зі
 * статистикою: назва плюс шість вузьких чисел. Доти панелей було три (між ними
 * стояли «приміщення вузла»), і дереву діставалося менше половини екрана —
 * назви поверхів у ньому обривалися на середині.
 *
 * Частки, а не пікселі: розкладка спільна з «Розцінками» (`.pricing-layout`), і
 * на вузькому екрані обидві панелі мусять стискатися разом. */
.tei-layout .panel-tei-tree { flex: 1.35 1 0; min-width: 0; }
.tei-layout .panel-tei-lib { flex: 1 1 0; min-width: 0; }

.tei-tree { table-layout: fixed; width: 100%; }
.tei-tree .tc-name { width: auto; }
/* Номер приміщення: у нього пишуть «12» чи «12а», а не речення. */
.tei-tree .tc-room { width: 74px; }
.tei-tree .tc-area { width: 88px; }
.tei-tree td { padding: 1px 6px; }
/* ПРИМІЩЕННЯ В ДЕРЕВІ — рядок світліший за вузол, у якому воно лежить: те саме
   правило, що зі складом розцінки в специфікації (`--child-bg`). Око має одразу
   бачити, де закінчується структура й починається її вміст. */
.tei-room td { background: var(--child-bg); }
.tei-room:hover td { background: #fff8d8; }
/* ПОЛЯ ВВОДУ — ТЕМНІШИМ ЖОВТИМ ЗА САМИЙ РЯДОК. Рядок приміщення й так
 * світло-жовтий, і поле на ньому кольору `--editable-bg` зливалося з тлом: було
 * не видно, де в рядку взагалі можна писати. Тут потрібен контраст ІЗ СУСІДОМ, а
 * не «жовтий загалом», тож відтінок свій і темніший. */
.tei-room .cell-input {
  background: #ffeeb0;
  border: 1px solid #d9bf5e;
  border-radius: 2px;
}
.tei-room .cell-input:hover { background: #ffe68a; }
.tei-room .cell-input:focus { background: #fff; border-color: var(--accent); }
/* Корінь-об'єкт не вибирається: приміщення живуть у листках, а він показує суму
   всього. Тому й не виглядає як посилання. */
.tei-node-root { font-weight: 600; }
.tei-node { color: var(--ink); text-decoration: none; }
.tei-node:hover { text-decoration: underline; }
.tei-total { font-weight: 600; }
/* Скільки приміщень гілки ще без обмірів. Дрібно й тривожним кольором: це не
   друге число поруч із площею, а попередження, що площа ще неповна. */
.tei-pending {
  margin-left: 4px;
  font-size: 10px;
  color: var(--danger, #b00020);
}

/* ЧЕРВОНА ЗАЛИВКА, ДОКИ ПЛОЩА НУЛЬОВА. Це не помилка вводу, а стан «додали,
 * обміри ще не внесли», і саме він має впадати в око, коли переглядаєш поверх
 * перед тим, як брати підсумок у розрахунок. Рамка, а не самий фон: поле
 * лишається полем, у яке видно, що треба писати. */
.tei-area-empty {
  background: #ffe0e0;
  border-color: var(--danger, #b00020);
}
/* `.tei-sale-tag` (значок ₴ біля продажного приміщення) прибрано: тепер про це
   каже сам ПОДІЛ бібліотеки на групи «Продаж» і «МЗК», а в дереві — колонка, у
   якій стоїть площа. Значок казав те саме, але його треба було ще й знайти
   очима в кінці назви. */

/* БІБЛІОТЕКА — ТАБЛИЦЯ ЗІ СТАТИСТИКОЮ. Доти це був перелік назв, і колонка була
   одна; тепер поруч стоять шість чисел про цей будинок, і вирівняти їх можна
   лише сіткою. */
.tei-lib { table-layout: fixed; width: 100%; }
.tei-lib .lc-name { width: auto; }
.tei-lib .lc-code { width: 58px; }
.tei-lib .lc-num { width: 62px; }
.tei-lib .lc-area { width: 74px; }
.tei-lib .lc-cnt { width: 42px; }
.tei-lib .lc-share { width: 58px; }
.tei-lib td { padding: 1px 4px; }
/* Заголовок групи — той самий сірий, що в підзаголовках переліків: це не рядок
   бібліотеки, а підпис до наступних. */
.tei-lib-group td {
  background: var(--group-3);
  font-weight: 600;
  text-transform: uppercase;
}
.tei-lib-item:hover td { background: #e6eef8; }
/* Обраний фільтр — так само, як обраний вузол дерева поруч: одне явище має
   виглядати однаково. */
.tei-lib-item.is-active td { background: var(--selected-bg); font-weight: 600; }
/* Перетягуваний рядок бере курсор-хапалку — інакше про перетягування не
   здогадатися. Коли перелік показано за шифром, `data-sortable` немає, і
   курсора теж: порядок там тимчасовий і не зберігається. */
/* Рядок бібліотеки тягнуть у дерево — курсор-хапалка на кожному, а не лише на
   тих, які можна ще й переставити місцями. */
.tei-lib .tei-lib-item { cursor: grab; }
/* Рядок дерева як зона кидання: рамка по всьому рядку, а не смужка збоку — це
   «покласти сюди», а не «поставити поруч». Той самий вигляд, що в перетягуванні
   папок планування. Приймають кидок і вузли, і самі приміщення: кинуте на
   приміщення лягає в його ж вузол. */
.tei-tree tr.drop-target td { box-shadow: inset 0 -2px 0 var(--accent),
                                          inset 0 2px 0 var(--accent); }
.tei-room { cursor: grab; }
/* Позначки перетягування — НА КОМІРКАХ, а не на рядку: `box-shadow` на `tr` у
   таблиці зі злитими межами не малюється. Той самий прийом, що в специфікації
   документа. */
.tei-lib tr.is-dragging td { opacity: .5; }
.tei-lib tr.drop-before td { box-shadow: inset 0 2px 0 var(--accent); }
.tei-lib tr.drop-after td { box-shadow: inset 0 -2px 0 var(--accent); }
.tei-lib-name { color: var(--ink); text-decoration: none; }
.tei-lib-name:hover { color: var(--accent); text-decoration: underline; }
.tei-lib-code { color: var(--ink-muted); font-size: 11px; }
/* Олівець стоїть ОДРАЗУ ЗА НАЗВОЮ, якої стосується, а не в кінці рядка за
   шістьма колонками чисел. Дрібний і приглушений: правлять назву зрідка, а
   читають перелік постійно. */
.tei-lib-edit { margin-left: 4px; color: var(--ink-muted); text-decoration: none;
                font-size: 11px; }
.tei-lib-edit:hover { color: var(--accent); }
/* Підсумок групи — у тих самих колонках, що й числа під ним. */
.tei-lib-group td.num { font-variant-numeric: tabular-nums; }
/* Рядок створення найменування: назва тягнеться, шифр і галочка — під себе. */
.tei-new { display: flex; flex-wrap: wrap; align-items: center; gap: 4px;
           margin: 4px 0; }
.tei-new input[name="name"] { flex: 1 1 120px; min-width: 0; font: inherit;
                              font-size: 12px; padding: 1px 5px; }
.tei-code-input { flex: 0 0 70px; font: inherit; font-size: 12px; padding: 1px 5px; }
.tei-new .check { font-size: 11px; color: var(--ink-muted); display: flex;
                  align-items: center; gap: 3px; margin: 0; }

/* ЖУРНАЛ: ЗНАЧОК ОНОВЛЕННЯ ПРИ ЛІВОМУ КРАЇ, СУМА ПРИ ПРАВОМУ. Та сама будова, що
 * в комірці обсягу відомості: дія ліворуч, число праворуч, і око не блукає між
 * ними. Кнопка «прозора» — вона службова, і рамка робила б із неї другий підсумок. */
 * Розкладку тримає `.cell-box`, а не сам `<td>` — чому саме так, сказано
 * при тій обгортці. */
.doc-total > .cell-box {
  justify-content: space-between;
  /* По базовій лінії, а не по центру: поруч стоять значок і число, і рівняти
   * їх треба за текстом, а не за висотою коробок. */
  align-items: baseline;
  gap: 6px;
}
.doc-total .inline-form { display: inline; }
.total-recalc {
  border: 0;
  background: none;
  cursor: pointer;
  color: var(--ink-muted);
  font: inherit;
  line-height: 1;
  padding: 0 2px;
}
.total-recalc:hover { color: var(--accent); }

/* ---------- Обмір: відомість і пікер площ ---------- */
/* ХРЕСТИК ЗАКРИТТЯ стоїть абсолютно, тож панелі потрібна власна система
 * координат — те саме, що в `.library-panel` із її пікером. */
.measure-panel { position: relative; }

/* ФОРМИ РЯДКІВ ВИНЕСЕНІ ЗА ТАБЛИЦЮ й порожні: усе, що вони надсилають, стоїть у
 * комірках і посилається на них через `form=`. Показувати там нічого.
 *
 * СЕЛЕКТОР НАВМИСНО ВАЖЧИЙ. Просте `.measure-form` (0,1,0) програвало сусідньому
 * `.form-panel-wide form` (0,1,1) — форми лишалися сітками й тримали під
 * таблицею смугу порожнечі на п'ять рядків. Порядок у файлі тут не рятує:
 * специфічність важить більше. */
.form-panel .measure-form { display: none; }

/* ПОЛЕ ЗАЙМАЄ СВОЮ КОМІРКУ ЦІЛКОМ. Доти рядок був однією коміркою на три
 * колонки, а поля в ній — на відсотках; шапка таблиці й значення під нею
 * розходилися тим більше, чим вужчий екран. Тепер ширину задає `<colgroup>`, а
 * поле просто заповнює те, що йому дали. */
.measure-grid td { padding: 1px 3px; }
.measure-grid td > input {
  width: 100%;
  font: inherit;
  padding: 0 3px;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: var(--editable-bg);
}
/* Пояснення зламаного виразу — рядком під своїм рядком, без відступу згори:
 * воно належить рядку над ним, а не стоїть саме по собі. */
.measure-error-row td { padding-top: 0; }
.measure-error-row .field-hint { margin: 0; }

/* КОМІРКА ОБСЯГУ ТРИМАЄ ДВІ РЕЧІ: хрестик при лівому краї, число при правому.
 * Власного стовпця під хрестик більше немає — він важив у рядку стільки ж,
 * скільки сам обсяг, хоча видаляють обмір разів у сто рідше, ніж читають. */
.measure-grid td.measure-value > .cell-box {
  justify-content: space-between;
  align-items: baseline;
  gap: 6px;
}

.measure-del {
  border: 0;
  background: none;
  cursor: pointer;
  color: var(--danger);
  font: inherit;
  line-height: 1;
  padding: 0 2px;
}
.measure-del:hover { color: #fff; background: var(--danger); border-radius: 2px; }

/* ПІКЕР — ТОЙ САМИЙ БЛОК, ЩО У ФІЛЬТРІ ЗВІТУ (`.struct-block`), тож більшість
 * вигляду вже задана там. Тут лише те, чого в звіті немає: власні частки
 * стовпців, рядок «поточний вузол» і числа праворуч у рядках.
 *
 * `max-width` знімається: блок стоїть усередині панелі, яка й так має свою
 * межу, а успадкована від звіту стеля робила б із трьох стовпців два з
 * половиною. */
.measure-pick { max-width: none; margin: 10px 0 4px; }
.measure-col-types { flex-grow: .6; }
.measure-col-consts { flex-grow: 1.3; }
.pick-types, .pick-consts, .pick-folders, .pick-tree { max-height: 220px; overflow-y: auto; }
.pick-folders { margin-bottom: 6px; }

/* Кнопки в переліках виглядають як рядки-посилання поруч: для людини це той
 * самий жест, і різниця в тегу не має ставати різницею на екрані. */
.pick-type, .pick-folder, .pick-node, .pick-own-btn, .const-pick {
  width: 100%;
  text-align: left;
  cursor: pointer;
  font-family: inherit;
}

/* ПОТОЧНИЙ ВУЗОЛ — ПЕРЕМИКАЧ, А НЕ ПІДПИС. Увімкнений означає «беремо площі
 * там, де стоїть робота»; вимкнений відкриває під собою структуру. */
.pick-own-btn { display: flex; align-items: baseline; gap: 8px; }
.pick-own-label { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; }
.pick-own-area, .pick-node-area { flex: 0 0 auto; font-variant-numeric: tabular-nums; }
.pick-missing { color: #a04000; margin: 4px 0 0; }
.pick-other { margin: 8px 0 2px; color: var(--ink-muted); font-size: var(--fs-dense); }

/* РЯДОК ДЕРЕВА — плюсик і сам вузол в один ряд. `.node` поруч блоковий, тож
 * без цього маркер стояв би окремим рядком над назвою. */
.pick-tree li { display: flex; align-items: stretch; }
.pick-tree li[hidden] { display: none; }
.pick-tree .marker { flex: 0 0 14px; align-self: center; text-decoration: none; }
.pick-tree .pick-node { flex: 1 1 auto; }
.pick-node { display: flex; align-items: baseline; gap: 6px; }
.pick-node-name { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; }
/* Відступ за рівнем — власний, бо спільний `.tree-indent` розрахований на
 * посилання (`a.tree-indent`), а тут рядки є кнопками. */
.pick-tree .pick-node, .pick-folders .pick-folder {
  padding-left: calc(6px + var(--depth, 0) * var(--tree-step));
}
/* ПОГЛИНЕНИЙ ВУЗОЛ. Обраний батько вже включає його площу, тож у суму він не
 * йде — і мусить це показувати, а не мовчати. */
.pick-node.is-covered { opacity: .5; }

/* КНОПКА ВСТАВКИ — при правому краї стовпця «Де»: вибір закінчується вузлом,
 * і дія стоїть там, де він закінчується. */
.pick-actions { display: flex; justify-content: flex-end; margin-top: 6px; }

/* Підсумок вибору — той самий рядок, що «фільтр застосовано» у звіті. */
.pick-summary { flex-wrap: wrap; }
.pick-total { font-weight: 600; }

/* ---------- Журнал цін ресурсу ---------- */
/* Блок під переліком позицій, а не окрема сторінка: «під виділеним ресурсом» і є
 * те місце, де його шукають. Рамка й відступ відділяють його від переліку — без
 * них таблиця цін читалася б як продовження таблиці позицій. */
.price-journal {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 2px solid var(--accent);
}
.price-journal .panel-header { margin-top: 0; }
/* «Ця ціна діє на N рядків…» — тим самим кеглем, що й заголовок блока. Речення
   довге й читається один раз перед зміною ціни; набране основним 14px, воно
   важило на екрані більше за сам перелік цін, заради якого блок і відкритий. */
.price-journal > .hint {
  font-size: 12px;
  color: var(--ink-muted);
  margin: 0 0 6px;
}
/* 720px ВИСТАЧАЛО, ПОКИ В ДОГОВОРІ СТОЯВ САМИЙ НОМЕР. Відколи там повні
 * реквізити (№, дата, замовник, виконавець), на решту лишалося ~175px, і підпис
 * розсипався на п'ять рядків — рядок таблиці ставав 86px заввишки. Заміряно на
 * стенді, а не на око. */
.price-list { table-layout: fixed; width: 100%; max-width: 1080px; }
/* Ширша за саму дату: перед нею в комірці стоять хрестик і олівець, а після
   неї — позначка «чинна». Разом це 165px разом із відступами комірки —
   заміряно, а не на око. */
.price-list .pc-date { width: 175px; }
.price-list .pc-unit { width: 110px; }
.price-list .pc-money { width: 130px; }
.price-list .pc-contract { width: auto; }
/* Ціна без ПДВ — приглушена: у системі всі числа з ПДВ, і ця колонка тут як
   довідка про те, ЩО САМЕ лежить у базі, а не як друге рівноправне число. */
.price-net { color: var(--ink-muted); }
/* Рядка-заголовка групи одиниці тут більше немає: він казав те саме, що колонка
   «Одиниця виміру» в кожному рядку, тільки окремим рядком екрана. Розділяє ряди
   історії тепер сам порядок (`list_prices` сортує за одиницею) плюс та колонка. */
/* Чинна ціна для документа з сьогоднішньою датою: саме
   це число піде в документ, і побачити його треба з першого погляду. */
.price-now {
  margin-left: 6px;
  padding: 0 4px;
  border-radius: 8px;
  background: #e3ecf7;
  color: var(--accent);
  font-size: 10px;
}
/* Три поля форми в один ряд: дата, ціна, одиниця — разом вони один запис, а
   стовпчиком з'їдали б висоту блока, під яким стоїть сам перелік. */
.price-fields { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end; }
.price-fields label { margin-bottom: 0; }
.form-panel-price { max-width: 720px; }
.form-panel-price input, .form-panel-price select { max-width: 200px; }
/* ДОГОВІР ШИРШИЙ ЗА РЕШТУ ПОЛІВ: у підписі номер, дата й обидві сторони, і в
   200px від нього лишався б самий номер. Він же й переноситься на другий рядок
   першим — дата, ціна й одиниця це один запис і мусять стояти разом. */
.form-panel-price .price-contract select { max-width: 100%; }
.price-contract { flex: 1 1 320px; min-width: 240px; }

/* ---------- Звіти ---------- */
/* Відомість звітів — ряд кнопок однакової ширини, як у решті рядів керування:
   сітка з колонок `1fr` у контейнері `max-content`. */
.report-list {
  display: inline-grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 6px;
  width: max-content;
  max-width: 100%;
  margin-bottom: 10px;
}
.report-list .btn { justify-content: center; }

/* ---------- План-Факт ---------- */
.pf-list, .pf-summary { table-layout: fixed; width: 100%; max-width: 1100px;
                        margin-bottom: 14px; }
.pf-list .pf-code { width: 90px; }
.pf-list .pf-name { width: auto; }
.pf-list .pf-money { width: 130px; }
.pf-list .pf-delta { width: 150px; }
.pf-summary { max-width: 640px; }
.pf-title { font-weight: 600; }
/* Довідка про рядок — дрібним і приглушеним: скільки позицій, у скількох
   документах, який статус. Це контекст числа, а не друге число поруч. */
.pf-sub { color: var(--ink-muted); font-size: 11px; margin-left: 6px; }

/* ЗНАК ВІДХИЛЕННЯ НЕСЕ КОЛІР, а не підпис. Перевитрата червона, економія зелена
 * — читається швидше за будь-яке слово, і саме це питання ставлять до звіту
 * першим. Колонка з відсотком не окрема: два погляди на одне число не варті
 * подвоєння ширини таблиці. */
.pf-delta-cell.is-over { color: var(--danger, #b00020); }
.pf-delta-cell.is-under { color: #1f6f3f; }
/* Нуль — ЗВИЧАЙНИЙ колір. «Виконали рівно на домовлену суму» це не економія:
   зелений тут читався б як добра новина там, де новини немає взагалі. */
.pf-delta-cell.is-same { color: var(--ink-muted); }
.pf-pct { font-size: 11px; opacity: .85; margin-left: 4px; }

/* Деталізація успадковує вигляд специфікації (`.spec`), тож ширини колонок тут
   лише перевизначають ті шість, які в неї є. `min-width` з `.spec` знімаємо:
   він розрахований на п'ятнадцять колонок, а тут їх шість. */
/* ШИРИНА — ЧИСЛОМ, А НЕ `100%`. `.grid` і `.spec` вище дають `width: 100%`, і
   при `table-layout: fixed` уся вільна ширина монітора діставалася колонці
   найменування: на широкому екрані таблиця розтікалася через увесь екран, а
   разом із нею й заголовок блока. Селектор саме `table.pf-detail` — інакше
   `.spec` тієї ж ваги, але нижче у файлі, переміг би (див. `table.doc-list`).
   `max-width` лишає запобіжник для вузького екрана. */
table.pf-detail {
  table-layout: fixed;
  width: 1032px;      /* 560 + 82 + 110 + 140 + 140 — сума самих колонок */
  max-width: 100%;
  /* `min-width` із `.spec` розрахований на п'ятнадцять колонок, а тут їх п'ять. */
  min-width: 0;
}
/* ЗАГОЛОВОК РОЗШИФРОВКИ — «Закрити» ЛІВОРУЧ, одразу перед підписом. Блоків
   буває кілька один під одним, і кнопка мусить стояти в того блока, якого
   закриває; притиснута ж до правого краю (`space-between`), вона від'їжджала на
   всю ширину заголовка й на широкому екрані лишалася непоміченою. */
.pf-cell-title {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 12px;
}
.pf-detail .pf-dname { width: 560px; }
.pf-detail .pf-unit { width: 82px; }
.pf-detail .pf-qty { width: 110px; }
.pf-detail .pf-money { width: 140px; }

/* ---------- Планування ---------- */
/* Дерево й панелі — ті самі, що в «Розцінках» (`.pricing-layout`, `.panel`,
   `.folder-tree`): нових правил під них не заводимо, інакше два майже однакові
   набори почали б розходитися. Тут лише те, чого в тих екранах немає. */

/* Замовник не вибирається (завдання пишуться під договір), тож він і не
   виглядає як посилання: без наведення, без курсора-руки. */
.plan-customer {
  background: var(--group-3);
  font-weight: 600;
  cursor: default;
}
/* Номер договору — головна ручка дерева, тож помітніший за папки під ним */
.plan-contract { font-weight: 600; }

/* Договір без об'єкта доступний з БУДЬ-ЯКОГО об'єкта. Позначка навмисно
   стримана: це пояснення, а не попередження. */
.tag-shared { background: #e3ecf7; }

/* Порожній лічильник не малюємо взагалі: нуль біля кожної папки — це шум, у
   якому губиться те, де завдання справді є. Праворуч його тримає не `float`, а
   `margin-left: auto` нижче — рядок став flex-ом, і в ньому `float` не діє. */
.plan-count:empty { display: none; }

.plan-subject { color: var(--ink-muted); font-weight: 400; }
.plan-note { color: var(--ink-muted); font-size: 12px; margin: 6px 0; }

/* Ряд керування завданнями — ЧОТИРИ КНОПКИ ОДНАКОВОЇ ШИРИНИ в один рядок.
 * Сітка з колонок `1fr` у контейнері `max-content`: рівні частки, а сам ряд не
 * розтягується на панель. Той самий прийом, що в кнопках вигляду структури, —
 * і тримається він сам, без числа, яке довелося б підганяти після кожного
 * перейменування. */
.plan-actions {
  display: inline-grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  width: max-content;
  max-width: 100%;
}
.plan-actions .btn { justify-content: center; }

/* ВИСОТИ СПИСКУ ЗАВДАНЬ. Завдань на екрані десятки, і кожен зайвий піксель — це
 * мінус рядок. Відступи зменшені приблизно на чверть проти попередніх (4px -> 3px
 * у рядку, 3px -> 2px у полях), а самі рядки залиті сірим: список читається як
 * суцільний перелік, а не як набір окремих рамок. Біле лишається за станами —
 * наведення й вибір, — і саме тому вони тепер помітні з першого погляду. */
.plan-tasks { list-style: none; margin: 0; padding: 0; }
.plan-task {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 1px 6px;
  border: 1px solid var(--line);
  border-top: none;
  background: #f2f2f2;
  font-size: 13px;
  line-height: 1.2;
  cursor: pointer;
}
/* Галочка — 13px замість браузерних 16px: саме вона тримала висоту рядка */
.plan-task-box { width: 13px; height: 13px; }
.plan-tasks li:first-child { border-top: 1px solid var(--line); }
.plan-task:hover { background: #e6eef8; }
.plan-task.is-selected { background: var(--selected-bg); }
/* Назва забирає всю вільну ширину, значки тиснуться праворуч — так дати різних
   завдань стоять в одну колонку. */
.plan-task-title { flex: 1 1 auto; min-width: 0; }
.plan-task.is-done .plan-task-title { text-decoration: line-through; opacity: .6; }
.plan-task-box { flex: 0 0 auto; margin: 0; }

/* Рядок створення: поле розтягується, кнопка лишається під свій підпис.
   Відступи такі самі стиснуті, як у рядках завдань, — три поля поспіль
   (пошук, нове завдання, перелік) мусять читатися одним блоком. */
.plan-new { display: flex; gap: 4px; margin: 4px 0; }
.plan-new input {
  flex: 1 1 auto;
  min-width: 0;
  font: inherit;
  font-size: 13px;
  line-height: 1.2;
  padding: 1px 6px;
}
/* Ховається до старту скрипта — див. `is-js-only` в planning.js. Показувати
   поле, яке нікуди не веде, гірше, ніж не показувати нічого. */
.is-js-only { display: none; }

/* ---------- Картка завдання ---------- */
/* ШИРИНА ТЯГНЕТЬСЯ МИШЕЮ. Опис завдання буває на абзац, а буває на слово, і одне
 * число однаково не підходить обом. Значення приходить змінною, яку ставить
 * скрипт; `340px` лишається початковим — тим, що було доти. */
/* КАРТКА ЗАЙМАЄ ВСЮ ВИСОТУ, ЯКА Є ДО НИЗУ ВІКНА, а не стільки, скільки вийшло з
 * полів. Висоту (`--plan-card-h`) міряє скрипт: вона залежить від того, скільки
 * над карткою займають шапка сторінки, рядок об'єкта й ряд кнопок, а це різні
 * числа на різних екранах і в різних розділах — у CSS його довелося б угадувати.
 *
 * Усередині — стовпчик, у якому ТЯГНЕТЬСЯ ЛИШЕ ОПИС (`.plan-field-desc`). Решта
 * полів має природну висоту, чек-лист резервує п'ять рядків і росте далі сам —
 * а росте він за рахунок опису, бо вільну висоту забирає тільки той. */
.plan-panel {
  flex: 0 0 var(--plan-card-w, 340px);
  position: relative;
  padding-left: 8px;
  display: flex;
  flex-direction: column;
  height: var(--plan-card-h, auto);
}
/* Смуга захоплення — по лівому краю панелі, тобто рівно на межі, яку тягнуть.
 * Вузька (6px), але з власним курсором: інакше її не знайти, а ширша з'їдала б
 * поле картки. */
.plan-resizer {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 6px;
  cursor: col-resize;
  background: transparent;
}
.plan-resizer:hover, .plan-resizer.is-dragging { background: var(--accent); }
/* Поки тягнуть, текст не має виділятися: інакше замість зміни ширини виходить
   синє виділення половини екрана. Те саме при перетягуванні рядка — тільки
   курсор інший, тож і клас окремий. */
.is-resizing { user-select: none; cursor: col-resize; }
.is-dragging-row { user-select: none; cursor: grabbing; }
/* ШАПКА КАРТКИ — ЦЕ Й Є ПОЛЕ НАЗВИ. Заввишки як шапка з номером договору в
   сусідній панелі: обидві стоять в одному рядку екрана, і різна висота читалася
   б як недороблена верстка. Поле в ній без власної рамки — рамкою йому служить
   сама смуга; рамка з'являється при фокусі, коли в поле справді пишуть. */
.plan-card-head {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 0 0 auto;
  text-align: left;
}
/* БЕЗ РАМКИ Й БЕЗ ВЛАСНИХ ВІДСТУПІВ ПО ВЕРТИКАЛІ — інакше шапка картки стає
   вищою за сусідню шапку з номером договору, а вони стоять в одному рядку
   екрана. Рамкою полю служить сама смуга; при фокусі її малює `box-shadow`,
   який, на відміну від `border`, розмірів не міняє. */
.plan-title-input {
  flex: 1 1 auto;
  min-width: 0;
  font: inherit;
  font-weight: 600;
  line-height: inherit;
  color: var(--ink);
  background: transparent;
  border: none;
  border-radius: 3px;
  padding: 0 4px;
}
.plan-title-input:hover { background: rgba(255, 255, 255, .55); }
.plan-title-input:focus {
  background: #fff;
  box-shadow: 0 0 0 1px var(--accent);
  outline: none;
}
.plan-title-input::placeholder { font-weight: 400; color: var(--ink-muted); }

.plan-field { display: block; margin-bottom: 6px; }
.plan-field > span { display: block; font-size: 11px; color: var(--ink-muted); }
.plan-field input, .plan-field textarea, .plan-field select {
  width: 100%;
  font: inherit;
  padding: 3px 6px;
  border: 1px solid #969696;
  border-radius: 3px;
  box-sizing: border-box;
}
.plan-field textarea { resize: vertical; }
/* ОПИС — ЄДИНЕ ПОЛЕ, ЯКЕ ТЯГНЕТЬСЯ. Йому дістається вся висота, що лишилась
   після решти полів і чек-листа; коли пунктів у чек-листі стає більше п'яти,
   вільної висоти меншає — і опис зменшується рівно на стільки ж.
   `min-height` — межа, нижче якої поле перестає бути полем (три рядки). */
/* ДВА СЕЛЕКТОРИ, А НЕ ОДИН — інакше правило мовчки не діє. Картка це `.panel`, а
   вище стоїть `.panel label { display: block }` вагою (0,1,1) проти (0,1,0) у
   простого класу — тобто `display: flex` програвав би незалежно від порядку у
   файлі, і поле опису лишалося б висотою в три рядки посеред порожньої картки.
   Та сама пастка, що з `.plan-count` і з шапками колонок специфікації. */
.plan-panel .plan-field-desc {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-height: 74px;
}
.plan-panel .plan-field-desc textarea { flex: 1 1 auto; resize: none; }
/* Дата й час — в один ряд: разом вони одне значення, а не два. */
.plan-field-row { display: flex; gap: 6px; align-items: flex-end; flex: 0 0 auto; }
.plan-field-row .plan-field { flex: 1 1 auto; }
.plan-field-time { flex: 0 0 96px; }

.plan-subheader { font-size: 12px; margin: 10px 0 4px; flex: 0 0 auto; }
.plan-status { font-weight: 400; font-size: 11px; color: #1f6f3f; }
.plan-status.is-error { color: var(--danger, #b00020); }

/* ЧЕК-ЛИСТ РЕЗЕРВУЄ П'ЯТЬ РЯДКІВ (5 × 21px) навіть порожній: інакше картка
   стрибала б від кожної доданої галочки, а місце під неї однаково потрібне.
   Далі перелік росте сам — за рахунок опису, який над ним. Коли й опис уперся у
   свій мінімум, гортається вже сам перелік (`overflow-y`), а не картка. */
.plan-chk {
  list-style: none;
  margin: 0;
  padding: 0;
  flex: 0 1 auto;
  min-height: 105px;
  overflow-y: auto;
}
.plan-chk-item { display: flex; align-items: baseline; gap: 6px; padding: 2px 0; }
.plan-chk-text { flex: 1 1 auto; min-width: 0; font-size: 13px; }
/* Правка на місці має бути помітною до кліку, інакше її не знайдуть */
.plan-chk-text[contenteditable="true"]:hover { background: var(--editable-bg); }
.plan-chk-text[contenteditable="true"]:focus { outline: 1px solid var(--accent); }
.plan-chk-item.is-done .plan-chk-text { text-decoration: line-through; opacity: .6; }
.plan-chk-drop {
  flex: 0 0 auto;
  border: none;
  background: none;
  color: var(--ink-muted);
  cursor: pointer;
  font-size: 15px;
  line-height: 1;
  padding: 0 4px;
}
.plan-chk-drop:hover { color: var(--danger, #b00020); }

/* Нагадування — той самий ряд, що чек-лист, плюс два свої елементи: коли настане
   і кнопка «прибрати». Форма додавання стоїть в один рядок: число, одиниця,
   кнопка — це один жест, і розкладати його на три рядки нема за чим. */
.plan-rem-item .plan-rem-when { flex: 0 0 auto; font-size: 12px; color: var(--ink-muted); }
/* «без дати» — попередження, а не довідка: таке нагадування не настане ніколи,
   і читатися воно має інакше, ніж дата поруч. */
.plan-rem-item .plan-rem-when.is-warn { color: var(--danger, #b00020); }
.plan-rem-item .plan-rem-off { flex: 0 0 auto; padding: 0 8px; font-size: 11px; }
.plan-chk-item.is-empty { color: var(--ink-muted); font-size: 12px; }
.plan-rem-new { display: flex; align-items: center; gap: 6px; }
.plan-rem-new .plan-rem-label { font-size: 12px; color: var(--ink-muted); }
.plan-rem-new input { width: 56px; }
.plan-rem-new select { flex: 0 0 auto; }

/* Кнопки картки — «Зробити папкою» й «Видалити». `.plan-confirm` (ряд «Точно
   видалити» / «Скасувати») звідси пішов разом із двоступеневим видаленням.
   `flex: 0 0 auto` — щоб ряд лишався внизу картки, а не стискався, коли
   чек-лист виріс. */
.plan-card-actions { margin-top: 10px; flex: 0 0 auto; }
/* Форма додавання пункту теж не стискається: інакше довгий чек-лист з'їдав би
   поле, яким його поповнюють. */
#pf-check-new { flex: 0 0 auto; }

/* РЯД КЕРУВАННЯ: кнопки плюс пошук в одну лінію. Пошук про те саме, що
   «Активні»/«Виконані», — що лишити на екрані, — тож і стоїть у тому ж ряду.
   Окремою смугою під кнопками він з'їдав рядок списку, заради якого сторінку й
   відкривають. */
.plan-bar { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.plan-bar .plan-actions { margin-bottom: 0; }
/* Сірий, як кнопки поруч, і тієї самої висоти: це один орган керування, а не
   поле вводу даних. Біліє при фокусі — тоді в нього справді пишуть. */
.plan-search {
  flex: 1 1 auto;
  min-width: 80px;
  font: inherit;
  font-size: 11px;
  line-height: 1.2;
  min-height: 18px;
  padding: 0 7px;
  background: #ebebeb;
  color: #282828;
  border: 1px solid #969696;
  border-radius: 4px;
  box-sizing: border-box;
}
.plan-search:focus { background: #fff; border-color: var(--accent); outline: none; }
.plan-search::placeholder { color: #6b6b6b; }

/* ---------- Календар ---------- */
/* Своя сітка, без бібліотек: потрібен один вигляд — місяць, — і це 42 клітинки.
   Календарна бібліотека принесла б власні стилі, локалізацію й формат подій,
   тобто другий набір понять поруч із наявним. */
.cal-head { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; }
.cal-title { flex: 1 1 auto; margin: 0; font-size: 14px; text-align: center; }

.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 1px;
            background: var(--line); border: 1px solid var(--line); }
.cal-dow { background: var(--group-3); text-align: center; font-size: 11px;
           padding: 2px 0; font-weight: 600; }
/* Висота розрахована під МАКСИМУМ вмісту: число дня + три позначки + рядок
   «+N ще» — це рівно 92px. Менше значення виглядає безневинно, але тоді
   найповніший день розсуває ВЕСЬ свій тиждень, і сітка стрибає від гортання
   місяця. Більше трьох позначок у клітинці не буває — про це дбає `MAX_CHIPS`. */
.cal-cell { background: #fff; min-height: 92px; padding: 2px 3px;
            display: flex; flex-direction: column; gap: 1px; overflow: hidden; }
/* Дні сусідніх місяців приглушені, але видимі — щоб тиждень читався цілим */
.cal-cell.is-outside { background: #fafafa; }
.cal-cell.is-outside .cal-day { color: var(--ink-muted); }
.cal-cell.is-today { box-shadow: inset 0 0 0 2px var(--accent); }
.cal-day { font-size: 11px; color: var(--ink-muted); }

.cal-task {
  font-size: 11px;
  padding: 1px 3px;
  border-radius: 2px;
  background: #e3ecf7;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Клікабельні лише завдання ВІДКРИТОГО договору — картка живе в його межах.
   Чужі показуємо приглушено: побачити строк треба, а відкривати нема куди. */
.cal-task.is-own { cursor: pointer; }
.cal-task.is-own:hover { background: var(--selected-bg); }
.cal-task:not(.is-own) { opacity: .6; }
.cal-more { font-size: 10px; color: var(--ink-muted); }

/* ---------- Рядок дерева планування ---------- */
/* РЯДОК — FLEX, а не текст із плаваючими вставками. Доти назва була голим
 * текстом, кнопки й лічильник — `float: right`, і саме через це плюсик стояв
 * ЗА ПІВСАНТИМЕТРА від назви, якої стосується, ще й вилітав над рядком: плаваючий
 * елемент виводиться з нормального потоку, тож ні по базовій лінії, ні по центру
 * він не вирівнюється взагалі.
 *
 * Тепер порядок явний: назва — дії — згортання — лічильник. Дії стоять одразу
 * за назвою, лічильник притиснутий до правого краю `margin-left: auto`. */
.plan-tree .folder { display: flex; align-items: center; gap: 4px; }
.plan-caption { min-width: 0; }
/* Виконавець і предмет — другим рядком, дрібно й приглушено: це те, чим договори
   одного замовника відрізняються, але не те, що читають першим. */
.plan-contract-meta {
  display: block;
  font-size: 11px;
  font-weight: 400;
  color: var(--ink-muted);
  line-height: 1.25;
}

/* ---------- Дії над папками в дереві ---------- */
/* З'являються при наведенні: у колонці 320px три значки біля кожного рядка
   перетворили б перелік на панель інструментів, у якій не видно самих назв. */
.plan-acts { display: none; }
.folder:hover .plan-acts { display: inline-flex; align-items: center; gap: 2px; }
/* ДВА СЕЛЕКТОРИ, А НЕ ОДИН — інакше правило мовчки не діє. Лічильник це ще й
   `.tag`, а вище (`.folder .tag`) у тега стоїть `margin-left: 4px` — і воно
   переважає простіший `.plan-count` за вагою селектора, хоч і стоїть раніше.
   Через це числа тулилися до назви замість правого краю: рядок став flex-ом, а
   старий `float: right` у flex-елемента не діє взагалі, тож нічого їх туди вже
   не відсувало. */
.plan-tree .plan-count { margin-left: auto; }
/* Поки кнопки видно, лічильник ховаємо: разом вони не вміщаються, і назва
   починає переноситись — рядок стрибає під курсором. */
.folder:hover .plan-count { display: none; }
.plan-act {
  border: none;
  background: none;
  cursor: pointer;
  font-size: 12px;
  line-height: 1;
  padding: 0 2px;
  color: var(--ink-muted);
}
.plan-act:hover { color: var(--accent); }
/* ПЛЮСИК СТВОРЕННЯ ПАПКИ — ПОМІТНІШИЙ ЗА РЕШТУ ЗНАЧКІВ. Це найчастіша дія в
 * дереві (папки заводять постійно, перейменовують і видаляють зрідка), а виглядав
 * він як два інші — дрібною сірою рискою. Півтора кегля й жирність роблять його
 * ціллю, у яку влучаєш не цілячись. */
.plan-act-add {
  font-size: 18px;
  font-weight: 700;
  line-height: 1;
  color: var(--accent);
}

/* Плюсик ЗГОРТАННЯ гілки — інша дія й інше місце: він про весь договір, а не про
   цей рядок, тож стоїть праворуч, поруч із лічильником. */
.plan-fold {
  margin-left: auto;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: #fff;
  color: var(--ink-muted);
  cursor: pointer;
  font-size: 12px;
  font-weight: 700;
  line-height: 1;
  padding: 0 4px;
}
.plan-fold:hover { color: var(--accent); border-color: var(--accent); }
/* Лічильник іде одразу за плюсиком, тож `auto` лишається тільки в одного з них */
.plan-fold ~ .plan-count { margin-left: 4px; }

/* Вбудоване поле замість `prompt()`/`confirm()` — браузерних діалогів у системі
   немає ніде, і один такий читався б як чужий застосунок. */
.plan-inline {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  padding: 4px 6px;
  background: #f4f8fd;
  border: 1px solid var(--line);
  border-top: none;
}
.plan-inline input { flex: 1 1 100%; font: inherit; padding: 2px 4px; min-width: 0; }
.plan-inline-ask span { flex: 1 1 100%; font-size: 11px; color: var(--ink-muted); }

/* Перейменування — У СВОЄМУ Ж РЯДКУ. Поле стає на місце назви, не зсуваючи
 * нічого навколо: доти під папкою розгорталася окрема смуга, рядок їхав униз, а
 * правити доводилося копію тексту в іншому місці екрана.
 * Спадкує шрифт і розмір назви, щоб текст не «стрибав» у мить кліку. */
.plan-rename {
  flex: 1 1 auto;
  min-width: 0;
  font: inherit;
  padding: 0 4px;
  border: 1px solid var(--accent);
  border-radius: 2px;
}

/* ---------- Перетягування ---------- */
/* Назви ті самі, що в структурі документа (`_structured.html`), — одне явище
   має виглядати однаково в усій системі. */
.is-dragging { opacity: .5; }
/* Папку можна перетягнути в іншу папку, між будь-якими рядками або на договір.
   Курсор — єдине, що про це каже: рядок водночас лишається посиланням, і без
   `grab` він виглядав би як звичайний перехід. */
.plan-tree li[data-kind="folder"] .folder { cursor: grab; }
.plan-tree li[data-kind="folder"].is-dragging .folder { cursor: grabbing; }
.drop-before { box-shadow: inset 0 2px 0 var(--accent); }
.drop-after { box-shadow: inset 0 -2px 0 var(--accent); }
/* Зона кидання — рамка по всьому вузлу, а не смужка: це «покласти всередину»,
   а не «поставити поруч», і виглядати воно має інакше. */
.drop-target { box-shadow: inset 0 0 0 2px var(--accent); }

@media (max-width: 1100px) {
  .pricing-layout { flex-direction: column; }
  .panel-folders, .panel-recipes, .plan-panel { flex: 1 1 auto; width: 100%; }
}

/* ---------- Звіт «План-факт по об'єкту» ---------- */
/* Таблиця ширша за звіт по фрагменту: сім колонок проти п'яти, і в кожній
   гроші. Стеля `.pf-list` (1100px) тут душила б числа переносами. */
.pf-object { max-width: 1500px; }

/* Підсумковий рядок — жирний і відокремлений лінією зверху. Нуль у ньому
   ДРУКУЄТЬСЯ, на відміну від рядків груп: там порожньо означає «цієї ланки в
   групі немає», а тут питання поставлене прямо. */
.pf-total td {
  font-weight: 600;
  border-top: 2px solid var(--ink);
  background: var(--group-3);
}

/* Довідка під назвою документа в переліку — сторони договору. Дрібним і
   приглушеним: це контекст рядка, а не друге значення поруч. */
.ledger-sub {
  flex: 1 1 100%;
  order: 10;
  color: var(--ink-muted);
  font-size: 11px;
}

/* Колонка документів ширша за дерева структур: у ній номер, сторони й сума, а
   не короткий підпис вузла. */
.struct-col-docs { flex: 1 1 40%; min-width: 320px; }

/* ЗВІРКА, ЯКА НЕ ЗІЙШЛАСЬ, — не «підказка», а тривога. Сума груп зобов'язана
   дорівнювати сумі підсумків документів за побудовою запиту, тож розбіжність
   означає зламаний розрахунок, а не властивість даних. */
.pf-alarm {
  color: var(--danger, #b00020);
  font-weight: 600;
}

/* Клікабельна сума в звіті по об'єкту — посилання без підкреслення, щоб
   таблиця не рябіла: підказує наведення й курсор, а не постійна лінія. */
.cell-link { color: inherit; text-decoration: none; }
.cell-link:hover { text-decoration: underline; color: var(--accent); }
/* Відкрита клітинка тримає рамку, поки видно її розшифровку: інакше, дійшовши
   до таблиці внизу, неможливо згадати, яке саме число розкладено. */
.is-open-cell { outline: 2px solid var(--accent); outline-offset: -2px; }

/* Відомість генпідрядних договорів. Ширина під шість стовпців, останній з яких
   — предмет договору: йому й дістається все, що лишилось. */
.pf-contracts { max-width: 1200px; }
.pf-contracts .pf-c-num { width: 110px; }
.pf-contracts .pf-c-date { width: 100px; }
.pf-contracts .pf-c-party { width: 220px; }
.pf-contracts .pf-c-money { width: 130px; }
.pf-contracts .pf-c-subject { width: auto; }
/* СТОРОНИ Й ПРЕДМЕТ ПЕРЕНОСЯТЬСЯ, а не обрізаються трьома крапками. Доти назву
   замовника різало по 260px — і два договори з тим самим початком назви
   («ТОВ Будівельна компанія…») ставали в переліку нерозрізненними, хоча
   розрізняє їх саме назва. Те саме правило вже діє в переліку договорів
   партнера (`.contract-list td.party`). */
.pf-contracts td.party, .pf-contracts td.subject {
  white-space: normal;
  overflow-wrap: anywhere;
}
/* У ВІДОМОСТІ ОБИРАЮТЬ — увесь рядок клікається, як у журналі документів, тож
   курсор мусить це казати ще до кліку. Підсвітку обраного дає спільне правило
   `.row-selected td`; свого кольору тут не треба — обраний рядок скрізь у
   системі виглядає однаково. */
.pf-contracts tbody tr { cursor: pointer; }
/* СХОДИНКА «СУБПІДРЯДУ»: номер стоїть із відступом за рівнем, тож стовпцю
   треба місця більше, ніж у пласкій відомості. Генпідряд — жирним, щоб поверхи
   читалися без підрахунку відступів. */
.pf-contracts .pf-c-tree { width: 170px; }
.pf-contracts tr.is-general td { font-weight: 600; }

/* Перемикач вигляду журналу — над таблицею, ліворуч, без відриву від неї. */
.doc-view-switch { margin-bottom: 8px; }

/* Договори стоять деревом (ДУ й субпідряди під своїм договором), тож номер несе
   сходинку. Крок дрібний із тієї ж причини, що в журналі документів: глибина
   тут дво-трирівнева, і широкий крок платив би шириною колонки за вкладеність,
   якої майже немає. */
.contract-list { --tree-step: 14px; }

/* Прапорець у довіднику-переліку: галочка, яка перемикається кліком. Виглядає
   як значення, а не як кнопка, — бо це і є значення рядка; кнопкою вона є лише
   технічно (запис мусить іти POST-ом). */
.flag-toggle {
  border: none; background: none; cursor: pointer;
  font-size: 15px; line-height: 1; padding: 2px 6px;
  color: var(--ink-muted);
}
.flag-toggle.is-on { color: #1f6f3f; font-weight: 700; }
.flag-toggle:hover { background: var(--selected-bg); border-radius: 3px; }
.grid.narrow.with-flag { max-width: 620px; }

/* Позабудівельний підсумок — окремим рядком під загальним, приглушено: ці гроші
   реальні, але в порівнянні з договірною ціною їм не місце. */
.pf-offsite td {
  color: var(--ink-muted);
  font-style: italic;
  border-top: 1px dashed var(--line);
}

/* ---------- Блок «Ціноутворення» ---------------------------------------
 * Розклад ціни кожного ресурсу: вхідна ціна у валюті -> закупівельна -> ціна
 * на замовника. Стоїть під тулбаром, над специфікацією.
 *
 * Тепла рамка — та сама, що в «Заміні ДЦ»: обидва блоки розгортаються з
 * тулбара й мають одразу читатися як «це не тулбар». */
/* РАМКИ НЕМАЄ. Помаранчева обводка кричала про блок, який людина щойно сама
   розгорнула кнопкою, — і на екрані з жовтими комірками введення читалася як
   попередження, хоч попереджати нема про що. Блок тримає власне тло. */
.pricing-block { background: #fffdf6; padding: 8px 10px; margin-bottom: 10px; }
.pricing-block .hint { margin: 0 0 6px; }

/* КОЛОНОК ОДИНАДЦЯТЬ, і на вузькому екрані вони не влазять. Прокручується САМЕ
 * БЛОК, а не сторінка: горизонтальна смуга в усього документа зсувала б і
 * специфікацію, яку ніхто не просив рухати. */
.pricing-scroll { overflow-x: auto; }
/* КЕГЕЛЬ І ЩІЛЬНІСТЬ — ЯК У СПЕЦИФІКАЦІЇ, під якою цей блок і стоїть. Комірки
 * збігалися й доти, а от шапка й міжрядковий інтервал правил не мали зовсім і
 * діставалися від `body` (14px / 1.45). Виходило, що блок ніби той самий
 * інструмент, але з більшою шапкою й вищими рядками — і на око читався як
 * «щось інше», хоч показує ті самі числа. */
.pricing { table-layout: fixed; width: 100%; min-width: 1360px; }

.pricing td { padding: 1px 6px; }
.pricing .inline-form { display: flex; }

/* НАЙМЕНУВАННЯ — СХОДИНКОЮ ВІД ПІДПИСУ ТИПУ. Підпис групи починається за
 * маркером (6px комірки + 14px `.marker` = 20px), і рядки під ним стояли з ним
 * урівень: перелік читався як один рівень, а не як вміст групи. 40px — це два
 * кроки тієї самої шкали, що в `.indent-*` (24/40/56/72), тобто відступ той
 * самий, що в специфікації.
 *
 * Тільки `padding-left`: вертикальні відступи рядка тримає `.pricing td`.
 * Тільки в блоці: той самий макрос малює картку під олівцем
 * (`_price_card.html`), а там групи немає й відступати нема від чого. */
.pricing-block .pricing td.cell-name { padding-left: 40px; }

.pc-c-name   { width: auto; }
/* 68px, а не 60: у 60 підпис «Од.вим.» ламався ПОСЕРЕД СЛОВА («Од.ви / м.»).
 * Ширина колонки тут визначається не даними (в одиниці два-три символи), а
 * саме заголовком. */
.pc-c-unit   { width: 68px; }
.pc-c-qty    { width: 96px; }
.pc-c-cur    { width: 76px; }
.pc-c-rate   { width: 68px; }
/* ДВІ КОЛОНКИ ШИРШІ ЗА ЧИСЛО, бо в них буває пара «вираз + результат»: у полі
 * стоїть «80*1,2+50», а поруч сіре «= 146,00». Обидві ціни в цьому блоці
 * вводяться виразом, і саме сюди він і переїхав зі специфікації. */
.pc-c-in     { width: 124px; }
.pc-c-disc   { width: 92px; }
.pc-c-buy    { width: 110px; }
.pc-c-markup { width: 96px; }
.pc-c-out    { width: 132px; }
/* Пара не переноситься: перенос дав би рядкам різну висоту, а блок читають
 * стовпчиком. Те саме правило тримає рядки картки (`.pc-row > td.num`). */
.pricing td.num { white-space: nowrap; }
.pc-c-sum    { width: 130px; }

.pricing .pc-unit { text-align: center; }
/* Закупівельна ціна ПОХІДНА — її не вводять, її отримують. Приглушена заливка
 * каже це без окремого підпису й ділить рядок надвоє: ліворуч бібліотека,
 * праворуч документ. */
.pricing .pc-buy { background: var(--child-bg); font-weight: 600; }

/* ЧЕРВОНІЄ ЦІНА, А НЕ НАЦІНКА. Питання до цього екрана звучить «за скільки
 * продаємо», і відповідь стоїть у колонці ціни; націнка ж проміжна цифра
 * розкладу. При мінусовій націнці ціна лишається додатною й нічим не
 * відрізняється від нормальної — без кольору її не помітити. */
/* `.cell-input.pc-neg` — БЕЗ ПРЕФІКСА `.pricing`, бо ця сама комірка є і в
 * специфікації: ціна там теж перераховується націнкою й теж буває мінусовою.
 * Ваги стачає рівно щоб перебити `.cell-input.qty-custom` (синє «задано
 * руками»), і саме такий порядок правильний: мінус — про гроші, синє — про те,
 * хто це число поставив. */
.pc-neg, .cell-input.pc-neg { color: var(--danger); font-weight: 600; }

/* Комірка, якої ця людина правити не може: ТЕКСТ, а не сіре поле. Сіре поле
 * обіцяє, що колись оживе; воно не оживе ніколи — бракує допуску. */
.pricing .pc-ro { color: var(--ink-muted); }
/* Значення, яке в рядках документа різне. Позначки ручної ціни («₴») тут більше
 * немає: стану «ціна перекриває розклад» система не створює — див.
 * `_pricing_row.html`. */
.pc-mixed { color: var(--ink-muted); font-size: 11px; flex: 0 0 auto; }

/* ---------- Олівець у комірці ціни специфікації -------------------------
 * Комірка ціни вузька (96px), і поле вводу займає її всю. Елемент У ПОТОЦІ
 * з'їв би в числа десяту частину ширини, тому олівець абсолютний, а комірка
 * дістає ліве поле рівно під нього. */
.spec td.cell-price { position: relative; padding-left: 14px; }
.price-pencil { position: absolute; left: 2px; top: 0;
                line-height: 1; font-size: 10px;
                color: var(--ink-muted); text-decoration: none; }
.price-pencil:hover { color: var(--accent); }
/* ДОВІДНИК КОНСТАНТ І ЙОГО ПІКЕР — та сама пара колонок, що в ресурсів: дерево
   папок ліворуч, вміст праворуч. Ширина дерева фіксована, решта — вмісту:
   назви папок короткі, а таблиця значень довга. */
.const-layout,
.const-picker { display: grid; grid-template-columns: 260px 1fr; gap: 16px; }
.const-folders .lp-list { margin-bottom: 8px; }

/* РЯДОК КОНСТАНТИ — В ОДИН РЯДОК: шифр, ЗНАЧЕННЯ, пояснення.
 *
 * Доти значення стояло `.lp-where` — тобто ДРУГИМ РЯДКОМ під назвою, дрібно й
 * приглушено, як «звідки знахідка» в ресурсів. Але в константи значення це не
 * довідка про походження, а сама суть: обирають саме його («мені треба той, що
 * 1,05»), і воно ж стоятиме у формулі. Через це кожна константа з'їдала два
 * рядки, а відповідь на головне питання лежала в другому.
 *
 * ПОРЯДОК ЧИТАННЯ: шифр -> значення -> пояснення. Пояснення останнє, бо його
 * читають лише тоді, коли шифр і число не сказали достатньо. */
.const-pick { display: flex; align-items: baseline; gap: 6px; white-space: nowrap; }
/* ШИФРИ РІЗНОЇ ДОВЖИНИ, А КОЛОНКА ЗНАЧЕНЬ ОДНА. Без цієї межі «=» стояли б
 * сходинкою — на трьох константах дрібниця, на трьох десятках саме те, через що
 * перелік і перестають читати. Межа МІНІМАЛЬНА: довший шифр її розсуне, а не
 * обріжеться. */
.const-pick strong { min-width: 4.5em; }
.const-val { font-weight: 600; font-variant-numeric: tabular-nums; }
/* Пояснення стискається першим і обрізається трикрапкою: воно довге, і в
 * колонці на 360px саме воно виштовхнуло б число за край. */
.const-note { color: var(--ink-muted); min-width: 0; overflow: hidden;
              text-overflow: ellipsis; }
.const-folder-add { margin-top: 8px; gap: 4px; }
.const-folder-add input { min-width: 0; flex: 1; }
.const-items { min-width: 0; }
.const-folder-cell select { max-width: 200px; }

/* ЗАГОЛОВОК БЛОКА З «ЗАКРИТИ» — підпис ліворуч, кнопка при правому краї.

   Клас носять пікер ресурсу й картка формування ціни. Доти правила не було
   зовсім: кнопка просто стояла слідом за текстом, тобто «закрити» опинялося
   посеред рядка й тим ближче до заголовка, чим коротший підпис. */
.lp-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
/* ВХОДЖЕННЯ ЗВЕДЕНОГО РЯДКА — з відступом і приглушеним підписом позиції, як
   склад розцінки під своєю роботою: та сама вкладеність, той самий прийом. */
.pc-variant > td { background: var(--child-bg, #fffdf0); }
.pc-variant .cell-name { padding-left: 22px; }
.pc-where { color: var(--ink-muted); margin-right: 4px; }

/* КАРТКА ФОРМУВАННЯ ЦІНИ — сім ЗВИЧАЙНИХ рядків таблиці, а не вставлений у неї
 * блок. Доти це була друга таблиця на одинадцять колонок усередині комірки на
 * всю ширину: схожа на першу й не вирівняна з нею. Тепер підпис ланки стоїть
 * ліворуч, а число — у тому самому стовпці, що й ціна самого рядка, тож око
 * веде їх однією колонкою згори вниз.
 *
 * ФОН ТОЙ САМИЙ, ЩО В КАРТОК АКТУВАННЯ (`.act-detail-row`): це вставка, а не
 * позиції кошторису, і читатися вона має так само. */
/* РЯДКИ ОДНАКОВОЇ ВИСОТИ Й ТРОХИ ВИЩІ ЗА РЯДКИ КОШТОРИСУ: сім ланок читають
 * згори вниз одним рухом, і різна висота збивала б цей рух. Однакова вона
 * виходить не сама — значення загорнуте в `.cell-box`, тож вираз із результатом
 * стоїть в один рядок (див. `_price_card.html`). */
.pc-row > td { background: #f7fbff; padding-top: 6px; padding-bottom: 6px; }
/* Число з виразом не переносимо: у картці ширини вдосталь, а перенос повернув
 * би саме ту різнобічну висоту, від якої тут ідеться. */
.pc-row > td.num { white-space: nowrap; }
/* БЛОК ВІДОКРЕМЛЮЄ ЛІНІЯ, А НЕ ПОРОЖНЄ МІСЦЕ.
 *
 * Доти тут стояли поля по три міліметри — і перший та останній рядки виходили
 * вищими за п'ять середніх. Замість «блок відокремлений» читалося «таблиця
 * з'їхала»: око бачить різну висоту рядків раніше, ніж будь-яку межу.
 *
 * `double` при ширині 3px малює саме ДВІ лінії з просвітом — початок і кінець
 * вставки видно, а висота рядків лишається спільною на всі сім. Класами, а не
 * `:first-of-type` — див. `_price_card.html`. */
.pc-row-first > td { border-top: 3px double var(--accent); }
.pc-row-last > td { border-bottom: 3px double var(--accent); }
/* Підпис ланки — праворуч, до самого числа, як підпис підсумку. Приглушений:
 * головне тут число, а не слово при ньому. */
.pc-row-label {
  text-align: right;
  color: var(--ink-muted);
  white-space: nowrap;
}

/* ---------- Підпис договору ----------
 * Один макрос на всю систему (`_contract_caption.html`), тож і стиль один.
 * Три частини різної ваги: номер — те, за чим договір шукають очима; дата й
 * сторони — те, чим його РОЗРІЗНЯЮТЬ, бо номери повторюються від року до року.
 * Тому номер темний, а решта приглушена: разом вони читаються як один підпис,
 * а не як три значення в комірці. */
.contract-caption { display: inline-flex; flex-wrap: wrap; gap: 0 5px;
                    align-items: baseline; }
.contract-date, .contract-parties { color: var(--ink-muted); }
/* Ціна без договору — «загальна», і це окремий зміст, а не порожнє місце.
 * Курсивом: це підпис стану, а не назва чогось. */
.contract-none { color: var(--ink-muted); font-style: italic; }

/* ---------- Курси валют ----------
 * Дата вужча за числа: у ній рівно десять символів, а курс буває й «1 234,5678».
 * Ширини можна тягнути (`data-cols="rates"`), тож це лише відправна точка. */
.rates-list { table-layout: fixed; width: 100%; max-width: 640px; }
.rates-list .rate-date { width: 120px; }
.rates-list .rate-value { width: auto; }
/* Назва валюти біля коду — приглушена й дрібна: код це те, що читають, назва
 * лише розшифровує його для тих, хто бачить таблицю вперше. */
.rate-name { color: var(--ink-muted); font-weight: 400; font-size: var(--fs-note); }
/* КУРС НЕ З ЦІЄЇ ДАТИ. Помаранчевий, а не червоний: це не помилка й не
 * небезпека — число правильне, просто взяте з іншого дня. Червоний у цій
 * системі означає «гроші пішли не туди» (від'ємна націнка, розбіжність із
 * договором), і витрачати його на «курс тижневої давнини» означало б знецінити
 * там, де він справді потрібен. Підказка називає справжню дату. */
.rate-stale, .pc-rate-stale { color: #b5651d; }
.rate-stale { font-style: italic; }
/* ВАГА СЕЛЕКТОРА ТУТ ЗНАЧУЩА, а не про охайність. Комірка курсу в блоці
 * ціноутворення має клас `.pc-ro` («правити не можна»), і правило
 * `.pricing .pc-ro` важче за самий `.pc-rate-stale` — тож попередження ставало
 * звичайним сірим і зникало з очей. Заміряно на стенді: було
 * rgb(120,120,120) замість помаранчевого. */
.pricing .pc-rate-stale { color: #b5651d; }

/* УПРАВЛІНСЬКИЙ КУРС — СИНІЙ, А НЕ ПОМАРАНЧЕВИЙ, і це не про красу. Помаранчеве
 * поруч означає ВИМУШЕНУ заміну: на потрібну дату курсу не було, узяли
 * найближчий. Тут навпаки — число поставила людина, і воно рівно таке, як
 * задумано. Одним кольором ці два стани читалися б як один, і щоразу
 * доводилося б наводити мишу, щоб дізнатися, який саме перед тобою.
 * Вага селектора — з тієї ж причини, що в сусіда: `.pricing .pc-ro` важчий. */
.pc-rate-own, .pricing .pc-rate-own { color: var(--accent); font-weight: 600; }

/* ---------- Курси валют: ТРЕТІЙ БЛОК реквізитів ----------
 *
 * СТОЇТЬ ПОРУЧ ІЗ ПРИМІТКОЮ Й ДОГОВОРОМ, а не під ними. Праворуч від правої
 * колонки лишалося порожньо, а таблиця з двох рядків унизу робила панель на
 * сотню пікселів вищою — тобто відсувала саму специфікацію, заради якої
 * редактор і відкривають.
 *
 * Обгортка потрібна тому, що курси НЕ МОЖУТЬ жити всередині форми реквізитів:
 * кожен із них надсилається сам, щойно його ввели, а форми не вкладаються. */
/* Перенос — на вузькому екрані курси падають під поля, а не вилазять за панель. */
.doc-req-row { display: flex; flex-wrap: wrap; gap: 18px; align-items: flex-start; }
.doc-rates h4 {
  margin: 0 0 4px;
  font-size: var(--fs-note);
  font-weight: 600;
}
/* БЕЗ КЛАСУ `.grid`: там свої рамки, заливка шапки й відступи під таблицю на
 * пів екрана. Тут — три вузькі колонки збоку від полів. */
.doc-rates-grid { border-collapse: collapse; font-size: var(--fs-note); }
.doc-rates-grid th {
  font-weight: 400;
  color: var(--ink-muted);
  padding: 0 6px 2px 0;
  text-align: center;
}
.doc-rates-grid td { padding: 1px 6px 1px 0; white-space: nowrap; }
/* ВАГА СЕЛЕКТОРА ТУТ ЗНАЧУЩА, а не про охайність. Поле без атрибута `type`
 * підпадає під `.form-panel input:not([type])` з `display:block; width:100%` —
 * і в комірці таблиці воно розпирало її на всю сторінку разом із панеллю.
 * Заміряно: 230px замість 92 і панель 1120px замість 849. Три класи важчі за
 * той селектор, тож ширина нарешті слухається. */
.form-panel-doc .doc-rates-grid .doc-rate-input {
  display: inline-block;
  width: 92px;
  max-width: none;
  margin-top: 0;
  padding: 2px 5px;
  text-align: right;
}
/* СТРІЛОЧКА В ОДНОМУ РЯДКУ З ПОЛЕМ. Комірка тримає дві сусідні форми, тож без
 * `inline-flex` вони стали б одна під одною — саме те, чого тут не треба. */
.doc-rate-cell { display: flex; align-items: center; gap: 4px; }
/* МІНІФОРМИ КУРСУ — НЕ СІТКА. `.form-panel-doc form` і `.form-panel-wide form`
 * роблять КОЖНУ вкладену форму двоколонковим гридом на 230px+345px — правило
 * писалося під єдину тодішню форму панелі. Через це поле й стрілочка сідали в
 * різні комірки такої сітки: заміряно 230px завширшки на кнопку й різні рядки.
 * Тут форма — лише обгортка навколо одного поля, тож повертаємо їй рядок. */
.form-panel-doc .doc-rate-cell form {
  display: inline-flex;
  align-items: center;
  grid-template-columns: none;
  gap: 0;
  margin: 0;
  /* ВІДСТУПИ ЗАГАЛЬНОГО ПРАВИЛА ФОРМ — ГЕТЬ. Саме вони й зсували стрілочку:
   * заміряно 13px зверху, через які кнопка сідала нижче за поле, хоч обидві
   * центровані у своїх коробках. */
  padding: 0;
  /* ВИСОТА ФОРМИ = ВИСОТА КОМІРКИ, інакше кнопка центрується у власній
   * коробці заввишки з себе й сідає на сім пікселів нижче за поле. */
  align-self: stretch;
}
.form-panel-doc .doc-rates-grid .doc-rate-reset {
  width: auto;
  /* ЯВНЕ ЦЕНТРУВАННЯ: без нього кнопка сідала на сім пікселів нижче за поле —
   * заміряно. Стрілочка мусить стояти В ОДНОМУ РЯДКУ зі своїм курсом. */
  align-self: center;
  margin: 0;
  border: 0;
  background: none;
  color: var(--ink-muted);
  cursor: pointer;
  font-size: 15px;
  line-height: 1;
  padding: 0 2px;
}
.form-panel-doc .doc-rates-grid .doc-rate-reset:hover { color: var(--accent); }

/* Валюта в журналі цін — приглушена: головне число рядка гривневе, а це
 * довідка про те, з чого воно вийшло. */
.price-list .pc-currency { width: 130px; }
.price-currency { color: var(--ink-muted); }

/* ---------- Журнал оплат договору ---------- */
/* ФОРМИ ТУТ НЕ КАРТКА, А РЯДКИ. Панель успадковує `.form-panel-wide form`, яка
 * робить сіткою з колонок по 230px КОЖНУ форму всередині, — і `form + form`
 * дописує кожній наступній рамку зверху й 24px відступу. Для картки договору
 * це правильно, тут — ні: форм стільки, скільки оплат, і всі вони порожні.
 *
 * `display: none` замість `hidden`: атрибут програв би авторському
 * `display: grid` вище, і кожна оплата коштувала б смуги порожнечі. */
.contract-payments .row-form { display: none; }

/* Рядок додавання — потік, а не сітка: п'ять полів різної ширини (дата, номер,
 * напрям, сума, примітка) у колонках по 230px розповзалися б на три ряди. */
.contract-payments .pay-add {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: 4px 10px;
  margin-top: 6px;
}
.contract-payments .pay-add label {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.contract-payments .pay-add input { max-width: 150px; }

/* Поля в самій таблиці — по ширині колонки, без загального `max-width`. */
.payments-list input,
.payments-list select {
  width: 100%;
  max-width: none;
  box-sizing: border-box;
}
/* Дві кнопки рядка стоять поруч і не тягнуться. */
.payments-list .row-actions { white-space: nowrap; }
.payments-list .row-actions > .cell-box { gap: 4px; }
/* Позначка «за додатковою угодою» належить рядку над нею, тож без власної
 * лінійки зверху — інакше читалася б як окрема оплата. */
.payments-list .pay-note td { border-top: 0; padding-top: 0; }

/* ---------- Розклад вузла звіту по виконавцях ---------- */
/* ПІДРЯДОК — ЦЕ РОЗКЛАД, А НЕ ЩЕ ОДНА ГРУПА. Тому він без кольору рівня
 * (`group-b-N` / `group-e-N`): відтінок у цьому звіті означає ГЛИБИНУ
 * структури, а підрядок стоїть не глибше — він стоїть збоку, розкладаючи той
 * самий вузол на виконавців. Пофарбувавши його як наступний рівень, ми сказали
 * б, що в структурі з'явився ще один поверх, якого там немає. */
.pf-contract > td { background: #fbfbfb; }
.pf-contract .tree-indent {
  font-size: 11px;
  color: var(--ink-muted);
  /* Курсив відрізняє назву КОМПАНІЇ від назви вузла структури: вони стоять в
   * одній колонці одна під одною, і без цього око читає їх як одну гілку. */
  font-style: italic;
}
.pf-contract .num { color: var(--ink-muted); }

/* ---------- Матеріали: вигрузка з 1С ---------- */
/* РЯДОК ЗАВАНТАЖЕННЯ — ОДИН РЯД, а не стовпчик. Полів усього два (файл і
 * кнопка), і блоковий `<form>` розганяв би їх на три рядки з підказкою. */
.mat-upload,
.mat-search {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
  margin-bottom: 8px;
}
.mat-search label { display: flex; align-items: center; gap: 6px; }

/* ПОЛЕ ВИБОРУ ФАЙЛА СХОВАНЕ ПІД СВОЇМ ПІДПИСОМ, а не прибране з дерева: клік по
 * підпису відкриває той самий діалог, і клавіатура з читачем екрана дістають
 * поле, як звичайно. `display: none` забрав би і те, і те. */
.mat-upload label.btn { cursor: pointer; }
.mat-upload label.btn input[type="file"] {
  position: absolute;
  width: 1px; height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* Перемикач періодів — ряд дрібних кнопок, як вкладки. */
.mat-periods { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 6px; }

/* ПЕРЕШКОДИ — СПИСКОМ, і панель віддає їм усю ширину: у скарзі про склади
 * перелічено дев'ять назв, і в колонку 640px вона стала б стіною. */
.mat-problems ul { margin: 4px 0 0; padding-left: 18px; }
.mat-problems li { margin-bottom: 3px; }

/* ВІДМОВА ВИДІЛЯЄТЬСЯ, ПОПЕРЕДЖЕННЯ НІ. Той самий перелік означає різне до
 * натискання й після нього, і колір — друга половина цієї різниці після
 * заголовка: людина мусить побачити, що сторінка ЗМІНИЛАСЯ. */
.mat-problems.is-refused {
  border-left: 3px solid var(--danger);
}
.mat-problems.is-refused h4 { color: var(--danger); }

/* Галочка про стартове сальдо — над кнопками, з відступом: рішення рідкісне й
 * важливе, тож не мусить зливатися з рядком дій. */
.mat-accept { display: block; margin-top: 10px; }

/* ---------- Матеріали: чи повна вигрузка ---------- */
/* Панель відповідає на питання «чи можна вірити цій оборотці», тож стоїть
 * першою на екрані звірки й тримає власну вузьку таблицю. */
.mat-supply { margin-bottom: 12px; }
.mat-supply h3 { margin: 0 0 4px; }
table.mat-supply-grid { table-layout: fixed; width: 100%; max-width: 820px; }
.mat-supply-grid { margin: 8px 0; }
/* СЕЛЕКТОР ЗУМИСНЕ ВАЖЧИЙ ЗА `.mat-deviation`: панель — це `form-panel-wide`, а
 * там форма вже оголошена сіткою (`.form-panel-wide form`), і за меншою вагою
 * поле, кнопка й підказка ставали трьома рядками стовпця. Та сама пастка, що
 * описана вище в `table.doc-list`. */
.mat-supply form.mat-deviation {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
  margin-top: 8px;
}
.mat-deviation label { display: flex; align-items: center; gap: 6px; }
/* Та сама вага, що й вище: `.form-panel input:not([type])` розтягує поля на всю
 * ширину, а тут у полі двозначне число. */
.mat-supply .mat-deviation input { width: 90px; }
.mat-deviation button { flex: none; }

/* Назва складу в переліку залишків повторюється сотні разів поспіль, тож
 * приглушена: читають номенклатуру, а склад лише зрідка перевіряють оком. */
.mat-list .mat-wh { color: var(--ink-muted); }

/* ШИРИНА СТОВПЦІВ — ФІКСОВАНА, І ЦЕ УМОВА ТОГО, ЩОБ ЇЇ МОЖНА БУЛО ТЯГНУТИ.
 * За `auto` ширини в `<col>` лише порада: стовпець, звужений менше за свій
 * текст, розсуває себе назад, і тягнути його виглядає як несправність.
 * Префікс `table.` навмисно — без нього пізніший `.grid { width: 100% }`
 * перемагає за однаковою вагою (та сама причина, що в `table.doc-list`). */
table.mat-warehouses,
table.mat-list,
table.mat-preview,
table.mat-reconcile { table-layout: fixed; width: 100%; }

/* Назви складів і номенклатури довгі, а рвати їх посеред слова не можна:
 * читають їх очима, а не по буквах. */
.mat-warehouses td, .mat-list td, .mat-preview td, .mat-reconcile td {
  overflow-wrap: break-word;
}

/* ---------- Матеріали: вкладки, групи, звірка ---------- */
.mat-tabs { display: flex; gap: 4px; margin-bottom: 8px; }

/* УЧАСНИК ГРУПИ — «ФІШКА» З КНОПКОЮ ВИХОДУ. Назва й хрестик мусять читатися як
 * одне ціле: у групі їх десяток, і хрестик, що поїхав до сусіда, прибрав би не
 * того учасника. */
.mat-members { display: flex; flex-wrap: wrap; gap: 4px 6px; }
.mat-chip {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 1px 4px;
  border: 1px solid var(--line);
  background: #fff;
  white-space: nowrap;
}
/* Форма всередині «фішки» не має бути блоком: інакше кожен учасник займав би
 * власний рядок, а їх у групі десяток. */
.mat-chip form { display: inline; }
.mat-chip .btn { padding: 0 4px; line-height: 1.1; }

/* Перелік кандидатів на додавання — ряд кнопок, які переносяться. */
.mat-picks { display: flex; flex-wrap: wrap; gap: 4px; margin: 4px 0 8px; }
.mat-picks form { display: inline; }

.mat-group h4 { display: flex; align-items: baseline; gap: 8px; }
.mat-unmatched { margin-top: 6px; }

/* Поле націнки в ряду дій редактора — вузьке: у ньому двозначне число, а
 * стандартна ширина поля відсунула б саму кнопку за край панелі. */
/* Підпис і поле — В ОДИН РЯДОК: у верхньому ряду стека стовпець вужчий за
   попередній ряд, і без цього «Націнка, %» ламалося після коми. */
.rebill-markup {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  white-space: nowrap;
}
.rebill-markup input { width: 56px; }

/* ---------- Звіт «Накопичувальний» ---------- */
/* ПРОКРУЧУЄТЬСЯ ТАБЛИЦЯ, А НЕ СТОРІНКА: блок на кожен акт, і за договором їх
 * бувають десятки. Разом зі сторінкою вбік їхали б і фільтри, і назва звіту. */
.cum-scroll { overflow-x: auto; }

/* ---------- Імпорт документа з файла ---------- */
/* ПРОКРУЧУЄТЬСЯ ТАБЛИЦЯ ФАЙЛА, А НЕ СТОРІНКА: колонок у чужому кошторисі буває
 * два десятки, і разом зі сторінкою вбік їхали б реквізити й кнопки. */
.imp-scroll { overflow-x: auto; margin-bottom: 12px; }
.imp-map td { white-space: nowrap; max-width: 320px; overflow: hidden; text-overflow: ellipsis; }
.imp-map th select { width: 100%; min-width: 120px; }
/* Підпис колонки у файлі — під вибором, приглушено: це підказка, а не відповідь. */
.imp-map th.imp-label { font-weight: normal; color: var(--ink-muted); }
.imp-sections { max-width: 1000px; }
/* КОЕФІЦІЄНТ ОДИНИЦІ — вузьке числове поле, а розшифровка «1 1000м3 = 1000 м3» —
 * під ним і приглушено: це перевірка відповіді, а не ще одне питання. */
.imp-units .imp-factor { width: 90px; }
.imp-factor-note { display: block; color: var(--ink-muted); font-size: var(--fs-note); }
/* ШИРИНИ ЗАДАЮТЬ СТОВПЦІ, А НЕ ВМІСТ. `table-layout: fixed` слухається `<col>`,
 * а `max-content` дає таблиці рівно суму їхніх ширин: розтягнуті на весь екран
 * два блоки читалися б гірше, ніж щільні п'ять. Типові ширини — за видом
 * стовпця, тими самими групами, якими їх тягнуть (`columns.js`). */
table.cum-table { width: max-content; table-layout: fixed; }
.cum-table col.cum-c-name { width: 320px; }
.cum-table col.cum-c-unit { width: 60px; }
.cum-table col.cum-c-qty { width: 100px; }
.cum-table col.cum-c-markup { width: 90px; }
.cum-table col.cum-c-purchase,
.cum-table col.cum-c-price,
.cum-table col.cum-c-cost { width: 120px; }
/* ТЕКСТ ПЕРЕНОСИТЬСЯ, і числа теж — по пробілу між тисячами. Ширину стовпця
 * задала людина, і вміст мусить рости вниз, а не вилазити в сусіда. Селектор
 * важчий за загальний `.grid td.num { white-space: nowrap }` — інакше числа
 * лишились би в один рядок. */
.cum-table td.num,
.cum-table td,
.cum-table thead th { white-space: normal; overflow-wrap: anywhere; }
/* МЕЖА БЛОКА — ТОВЩА ЗА МЕЖУ СТОВПЦЯ. Інакше «Обсяг» акта зливався б із «Вартістю»
 * попереднього, і око не знаходило б, де закінчився один документ. */
.cum-table .cum-first { border-left: 2px solid var(--ink-muted); }
/* Нулі залишку: червоний — перебрано, синій — відмовились. Жирні, бо нуль
 * звичайний (закрито рівно) стоїть поруч і мусить від них відрізнятися. */
.cum-table .cum-over { color: var(--danger); font-weight: 700; }
.cum-table .cum-refused { color: var(--accent); font-weight: 700; }
/* Ціна акта, що розійшлася з договором. */
.cum-table .cum-diff { color: var(--danger); }


/* ---------- Варіанти документа й тендер ---------- */
/* ТАБЛИЦЯ — ТА САМА, ЩО НАКОПИЧУВАЛЬНИЙ ЗВІТ (`cum-table`): блоки поруч, ширини
 * групами, прокрутка вбік усередині себе. Своє тут лише номер рядка й кольори. */
.cum-table col.cum-c-num { width: 60px; }
/* ЖОВТЕ — «ІНАКШЕ, НІЖ В ОРИГІНАЛІ». Колір тла, а не тексту: текстом уже
 * говорять найдешевше й найдорожче, і два значення одного кольору злилися б. */
.offer-board .offer-diff { background: #fff3c4; }
.offer-board .offer-diff-text { color: #8a6d00; }
.offer-board .offer-best { color: #1e7b34; font-weight: 700; }
.offer-board .offer-worst { color: var(--danger); font-weight: 700; }
.offer-board .offer-removed {
  color: var(--ink-muted);
  text-decoration: line-through;
  text-align: center;
}
.offer-board tr.offer-new td { background: #eef8ec; }
/* Завантаження — одним рядом над переліком: учасник, файл, кнопка й підказка. */
.offer-upload {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 8px 0 12px;
}
.offer-upload input[name="bidder"] { min-width: 260px; }
.offer-compare-row { display: flex; align-items: center; gap: 8px; }
.offer-changes { margin: 0; padding-left: 18px; max-height: 320px; overflow-y: auto; }

/* ---------- Самореєстрація — окремий файл `signup.css` ---------- */
