/* App-wide stylesheet, linked from templates/base.html through {% static %}
   with a ?v=<content hash> cache-buster (core.context_processors).
   KB-267 moved it here verbatim from base.html's <style> block, then set the
   redesign tokens in :root. Served in production by the nginx alias on
   /static/ after deploy.sh's collectstatic (D18). Page-specific rules still
   live in each template's own <style>. */

:root {
    /* KB-267: owner's decisions 26.09.2026 (Claude Design mock-ups) — colour
       direction C, density direction B, values from the «Таблиця значень»
       page and the mock-ups' own .dC variables (--p/--pt/--ps). */
    --primary: #4F46E5;
    --primary-hover: #4338CA;
    --primary-dark: #3730A3;
    --primary-soft: #EEF0FF;
    --primary-ring: rgba(79, 70, 229, 0.15);
    --bg: #FFFFFF;
    --surface: #FFFFFF;
    --surface-subtle: #FAFAFA;
    --border: #E3E5EA;
    --border-strong: #D9DBE1;
    --text: #111318;
    --text-muted: #5F6672;
    --text-subtle: #A1A6B0;
    --danger: #DC2626;
    --danger-border: #FECACA;
    --danger-soft: #FEF2F2;
    /* KB-267: used by balances/payroll, balances/recurring_list and the
       warehouse contour badges, but never declared until now. */
    --warn-strong: #93370D;
    --warn-soft: #FEF4E6;
    /* KB-276: used with inline fallbacks by the users / employees «Активний»
       label and the employee «C-level» label, never declared. Declared with
       exactly those fallback values, so nothing changes on screen; restyling
       the labels (--primary-light is still a Bootstrap blue tint) is the
       migration's call. */
    --success-bg: #d4edda;
    --success-text: #155724;
    --primary-light: #cfe2ff;
    --r-card: 8px;
    --r-input: 6px;
    --r-badge: 5px;
    --r-pill: 999px;
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04);
    --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.05);
    --dt-contract: #5e6ad2;
    --dt-specification: #eab308;
    --dt-invoice: #16a34a;
    --dt-delivery-note: #dc2626;
    /* KB-338: the darker text tones the doc-type badges and the document
       form's active type tab already drew as literals, and the ТТН tab's
       colour — declared so the form's tabs take them from :root. */
    --dt-specification-strong: #A16207;
    --dt-delivery-note-strong: #991B1B;
    --dt-waybill: #0891b2;

    /* Typography, density B. Body and H1 are applied below; the rest are
       declared for the tables / forms / cards PRs of the redesign.
       Line heights are unitless ratios of the mock-up's px values: a px
       line-height is inherited as an absolute length, so any larger text
       below the body would overlap itself. */
    --fs-body: 13px;
    --lh-body: 1.5;          /* 19.5 / 13 — KB-305: was 1.385 (18 / 13) */
    --fs-sub: 12px;          /* second, grey line of a table row */
    --lh-sub: 1.4;           /* 16.8 / 12 — KB-305: was 1.333 (16 / 12) */
    --fs-h1: 20px;
    --lh-h1: 1.4;            /* 28 / 20 */
    --fs-amount: 28px;       /* KB-282: the amount that IS the title of a money card (3C) */
    --lh-amount: 1.286;      /* 36 / 28 */
    --fs-card-title: 14px;
    --fs-label: 12.5px;      /* form field label */
    --fs-th: 12px;           /* column headers: 12/500, normal case */
    --fw-th: 500;

    /* Declared only — applied to components in the next PRs. */
    --control-h: 32px;       /* field and button height */
    --control-px: 10px;
    --cell-py: 6px;
    --cell-px: 10px;
    --table-head-h: 30px;
    --table-head-bg: #F6F7F9;
    --divider: #ECEDF1;      /* between table rows */
    --row-hover: #F6F7FB;
    --row-selected: #EEF0FF;
    --card-pad: 20px;        /* KB-305: was 16px */
    --card-pad-lg: 24px;     /* KB-305: was 20px */
    --gap-block: 20px;       /* KB-305: was 16px */
    /* KB-275: form frame widths — narrow form, wide form (2C) and its side
       panel, as fixed by the owner for the redesign. */
    --form-narrow: 560px;
    --form-wide: 760px;
    --form-side: 328px;
    /* KB-276: sidebar width, density B («Ширини · Сайдбар»). */
    --sidebar-w: 232px;
    /* KB-278: the content's fixed gap from the sidebar (and its right edge). */
    --page-px: 32px;

    /* Bootstrap's own primary and link colours, so its components stop
       drawing the default #0d6efd next to ours ("the second blue"). */
    --bs-primary: #4F46E5;
    --bs-primary-rgb: 79, 70, 229;
    --bs-link-color: #4F46E5;
    --bs-link-color-rgb: 79, 70, 229;
    --bs-link-hover-color: #3730A3;
    --bs-link-hover-color-rgb: 55, 48, 163;
    --bs-focus-ring-color: rgba(79, 70, 229, 0.25);
}
* { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
body {
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, system-ui, 'Segoe UI', sans-serif;
    font-size: var(--fs-body); line-height: var(--lh-body); letter-spacing: -0.01em;
    color: var(--text); background: var(--bg);
}
h1, h2, h3, h4, h5, h6 { color: var(--text); margin-top: 0; letter-spacing: -0.015em; font-weight: 600; }
h1, h2, .page-title { font-size: var(--fs-h1); font-weight: 600; }
h3, .section-title { font-size: 14px; font-weight: 600; }
a { color: var(--primary); text-decoration: none; }
a:hover { color: var(--primary-dark); text-decoration: underline; }
.label { font-size: 11px; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: var(--text-muted); }
.mono { font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace; font-variant-ligatures: none; }
.text-muted, small.text-muted { color: var(--text-muted) !important; font-size: 12px; }
.help-text { color: var(--text-muted); font-size: 12px; }
/* KB-278: the content starts right after the sidebar and takes the width
   there is — no page-wide max-width, no centring. Width limits live in the
   components (--form-narrow / --form-wide, .cat-card). */
main.container { max-width: none; margin: 0; padding: 32px var(--page-px); }

/* === NAVBAR === */
.app-navbar { background: var(--surface); border-bottom: 1px solid var(--border); padding: 14px 0; }
.app-navbar .container { max-width: 1200px; padding: 0 24px; display: flex; align-items: center; gap: 24px; }
.app-brand { display: flex; align-items: center; gap: 10px; font-weight: 600; font-size: 14px; color: var(--text); text-decoration: none; letter-spacing: -0.015em; }
.app-brand:hover { color: var(--text); text-decoration: none; }
.app-brand-mark { width: 24px; height: 24px; border-radius: 6px; background: var(--primary-soft); color: var(--primary-dark); display: inline-flex; align-items: center; justify-content: center; font-weight: 700; font-size: 12px; line-height: 1; }
.app-nav { display: flex; align-items: center; gap: 18px; flex: 1; flex-wrap: wrap; }
.app-nav-link { color: var(--text-muted); font-weight: 500; font-size: 13.5px; text-decoration: none; white-space: nowrap; padding: 6px 10px; border-radius: 7px; transition: color 0.1s, background 0.1s; }
.app-nav-link:hover { color: var(--text); text-decoration: none; }
.app-nav-link.active { background: var(--primary-soft); color: var(--primary-dark); }
.app-user { display: flex; align-items: center; gap: 14px; }
.user-chip { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--text); text-decoration: none; padding: 4px 6px; border-radius: 7px; transition: background 0.1s; }
.user-chip:hover { background: var(--surface); text-decoration: none; }
.user-avatar { width: 24px; height: 24px; border-radius: 50%; background: var(--surface-subtle); color: var(--text-muted); display: inline-flex; align-items: center; justify-content: center; font-weight: 600; font-size: 11px; line-height: 1; }
.user-name { color: var(--text-muted); font-size: 13px; font-weight: 500; }
.nav-logout { background: transparent; border: none; color: var(--text-muted); font-size: 13px; font-weight: 500; cursor: pointer; padding: 4px 6px; font-family: inherit; transition: color 0.1s; }
.nav-logout:hover { color: var(--text); }

/* === SIDEBAR (registry-driven, KB-3) === */
/* KB-412: a page wider than the window (a table that cannot be narrower)
   scrolls as a whole — the shell and the content column are never narrower
   than their content (``min-width: min-content``), so the document grows and
   the body scrolls, never a frame inside it. The sidebar is sticky on both
   axes and stays put while the page scrolls sideways. */
.app-shell { display: flex; align-items: stretch; min-height: 100vh; min-width: min-content; }
.app-sidebar { flex: 0 0 var(--sidebar-w); width: var(--sidebar-w); background: var(--surface-subtle); border-right: 1px solid var(--border); padding: 16px 12px; display: flex; flex-direction: column; gap: 14px; position: sticky; top: 0; left: 0; z-index: 2; height: 100vh; overflow-y: auto; }
.app-sidebar .app-brand { padding: 0 2px; gap: 8px; }
/* KB-276: brand «КБ КИЇВ» + the legal name in small grey (mock-up, variant A). */
.app-brand-text { display: flex; flex-direction: column; min-width: 0; }
.app-brand-name { font-weight: 600; font-size: 14px; line-height: 18px; }
.app-brand-legal { font-weight: 400; font-size: 11.5px; line-height: 16px; letter-spacing: 0; color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.app-main { flex: 1 1 auto; min-width: min-content; }
/* KB-276: accordion (variant A) on native <details>. A group header is a
   34px row in normal case with a chevron; the open group turns it. */
.app-nav-groups { display: flex; flex-direction: column; gap: 2px; flex: 1; }
.app-nav-group { display: block; }
.app-nav-group-label { display: flex; align-items: center; justify-content: space-between; gap: 8px; height: 34px; padding: 0 10px; border-radius: var(--r-input); font-size: var(--fs-body); font-weight: 500; color: var(--text); cursor: pointer; list-style: none; user-select: none; }
.app-nav-group-label::-webkit-details-marker { display: none; }
.app-nav-group-label::marker { content: ''; }
.app-nav-group-label:hover { background: var(--surface); }
.app-nav-group-label:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--primary-ring); }
.app-nav-group-label .bi { font-size: 12px; color: var(--text-muted); transition: transform 0.15s; }
.app-nav-group[open] > .app-nav-group-label .bi { transform: rotate(180deg); }
.app-nav-group-items { display: flex; flex-direction: column; gap: 1px; padding: 1px 0 6px; }
.app-nav-link { display: flex; align-items: center; gap: 9px; color: var(--text-muted); font-weight: 500; font-size: var(--fs-body); text-decoration: none; padding: 6px 10px; border-radius: var(--r-input); transition: color 0.1s, background 0.1s; }
.app-nav-link .bi { font-size: 15px; width: 18px; flex: 0 0 18px; text-align: center; }
.app-nav-link:hover { color: var(--text); text-decoration: none; background: var(--surface); }
.app-nav-link.active { background: var(--primary-soft); color: var(--primary-dark); }
.app-sidebar-footer { display: flex; align-items: center; justify-content: space-between; gap: 10px; border-top: 1px solid var(--border); padding-top: 14px; }

@media (max-width: 768px) {
    .app-shell { flex-direction: column; }
    .app-sidebar { flex-basis: auto; width: 100%; height: auto; position: static; flex-direction: column; border-right: none; border-bottom: 1px solid var(--border); }
}

/* === CARDS === */
.card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-card); box-shadow: none; padding: 0; }
.card-body { padding: 20px; }
.card-header { background: transparent; border-bottom: 1px solid var(--border); padding: 14px 20px; font-weight: 600; font-size: 13px; }

/* === FORMS === */
/* KB-275: density B — label 12.5/500, field --control-h (32) at 13px. The
   field border is --border-strong (#D9DBE1, «рамка поля» of the mock-ups). */
.form-label { font-weight: 500; font-size: var(--fs-label); line-height: var(--lh-sub); color: var(--text); margin-bottom: 6px; }
.form-label.small, label.small { font-size: 12px; color: var(--text-muted); font-weight: 500; }
.form-control, .form-select { height: var(--control-h); border: 1px solid var(--border-strong); border-radius: var(--r-input); padding: 0 var(--control-px); font-size: var(--fs-body); color: var(--text); background-color: var(--surface); line-height: var(--lh-body); transition: border-color 0.1s, box-shadow 0.1s; }
textarea.form-control { height: auto; padding-top: 6px; padding-bottom: 6px; }
/* KB-275: number fields — no spinner arrows (the keyboard arrows still
   step), figures of equal width so amounts line up while typing. */
input[type="number"] { font-variant-numeric: tabular-nums; -moz-appearance: textfield; appearance: textfield; }
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
.form-control:focus, .form-select:focus { border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-ring); outline: none; }
.form-control::placeholder { color: var(--text-subtle); }
.form-control.is-invalid, .form-select.is-invalid { border-color: #DC2626; box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.15); }

/* === BUTTONS (override Bootstrap to indigo) === */
/* KB-275: one height with the fields. min-height, not height: a long label
   that wraps grows the button instead of being clipped. */
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-height: var(--control-h); font-size: var(--fs-body); line-height: var(--lh-body); font-weight: 500; border-radius: var(--r-input); padding: 0 14px; transition: background 0.1s, border-color 0.1s, color 0.1s; }
.btn-sm { min-height: 28px; padding: 0 10px; font-size: 12.5px; }
.btn-primary { background: var(--primary); border-color: var(--primary); color: #fff; }
.btn-primary:hover { background: var(--primary-hover); border-color: var(--primary-hover); color: #fff; }
/* KB-267: Bootstrap paints the pressed / focused / disabled states from its
   --bs-btn-* variables, which still carried #0d6efd. */
.btn-primary {
    --bs-btn-active-bg: var(--primary-dark); --bs-btn-active-border-color: var(--primary-dark); --bs-btn-active-color: #fff;
    --bs-btn-disabled-bg: var(--primary); --bs-btn-disabled-border-color: var(--primary);
    --bs-btn-focus-shadow-rgb: 79, 70, 229;
}
.btn-outline-primary {
    --bs-btn-color: var(--primary); --bs-btn-border-color: var(--primary);
    --bs-btn-hover-color: #fff; --bs-btn-hover-bg: var(--primary); --bs-btn-hover-border-color: var(--primary);
    --bs-btn-active-color: #fff; --bs-btn-active-bg: var(--primary-hover); --bs-btn-active-border-color: var(--primary-hover);
    --bs-btn-disabled-color: var(--primary); --bs-btn-disabled-border-color: var(--primary);
    --bs-btn-focus-shadow-rgb: 79, 70, 229;
}

/* === TABS (KB-267: Bootstrap .nav-tabs in the primary colour) === */
.nav { --bs-nav-link-color: var(--primary); --bs-nav-link-hover-color: var(--primary-dark); }
.nav-tabs {
    --bs-nav-tabs-border-color: var(--border);
    --bs-nav-tabs-link-hover-border-color: var(--border) var(--border) var(--border);
    --bs-nav-tabs-link-active-color: var(--text);
    --bs-nav-tabs-link-active-border-color: var(--border) var(--border) var(--surface);
}
.nav-link:focus-visible { box-shadow: 0 0 0 3px var(--primary-ring); }

/* === CHECKBOXES (KB-267: checked state was Bootstrap blue) === */
.form-check-input:checked { background-color: var(--primary); border-color: var(--primary); }
.form-check-input:focus { border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-ring); }
.btn-outline-secondary { border-color: var(--border-strong); color: var(--text-muted); background: var(--surface); }
.btn-outline-secondary:hover { background: var(--surface-subtle); color: var(--text); border-color: var(--border-strong); }

/* === ALERTS === */
.alert { padding: 12px 16px; border-radius: var(--r-card); border: 1px solid; font-size: 13px; margin-bottom: 14px; }
.alert-info, .alert-primary { background: var(--primary-soft); color: var(--primary-dark); border-color: #DBD7FF; }
.alert-success { background: #D1FAE5; color: #065F46; border-color: #A7F3D0; }
.alert-warning { background: #FEF3C7; color: #92400E; border-color: #FDE68A; }
.alert-danger { background: var(--danger-soft); color: #991B1B; border-color: var(--danger-border); }

/* === DOC-TYPE BADGES === */
.doc-type-badge { display: inline-block; padding: 2px 8px; border-radius: var(--r-badge); font-size: 11px; font-weight: 600; }
.doc-type-contract { background: #EEEFFB; color: var(--dt-contract); }
.doc-type-specification { background: #FEF9E7; color: var(--dt-specification-strong); }
.doc-type-invoice { background: #DCFCE7; color: var(--dt-invoice); }
.doc-type-delivery_note { background: #FEE2E2; color: var(--dt-delivery-note-strong); }
/* KB-337: ТТН бейджа кольору не мала (у --dt-* її немає) — нейтральний сірий. */
.doc-type-waybill { background: var(--surface-subtle); color: var(--text-muted); }
/* KB-96: напрям листа в реєстрі кореспонденції. Класи навішуються
   ПОРУЧ з .doc-type-badge — розмір, радіус і вага там, тут лише колір. */
.dir-badge-incoming { background: #DCFCE7; color: #166534; }
.dir-badge-outgoing { background: #EEEFFB; color: #5e6ad2; }
/* KB-107: бейдж готового виробу в довіднику товарів. Той самий принцип,
   що й вище — клас навішується ПОРУЧ з .doc-type-badge, тут лише колір.
   У коментарі свідомо НЕ повторено текст самого бейджа: base.html
   віддається на кожній сторінці, тож будь-яка перевірка «бейдж є /
   бейджа немає» інакше знаходила б цей рядок. */
.fg-badge { background: #FEF9E7; color: #A16207; }
/* KB-110 → KB-314: розмір складу виробу в списку «Вироби» — тепер спільний
   .cat-badge; власний кольоровий клас прибрано. */
/* KB-121 → KB-310: тип відправлення (НП / авто) тепер спільний .cat-badge
   (--primary для НП) у партіалі sales/_delivery_type_badge.html; власні
   кольорові класи прибрано. */

/* === CATALOG: shared list + form pages === */
/* KB-345: the old page headers (cat-header / cat-form-header) are gone — every
   page is on .cat-page-head; tests/test_ui_patterns_kb276.py keeps them out. */
.cat-breadcrumb { font-size: 12px; color: var(--text-muted); text-decoration: none; display: inline-block; margin-bottom: 10px; }
.cat-breadcrumb:hover { color: var(--text); text-decoration: none; }
.cat-filter { border: 1px solid var(--border); border-radius: var(--r-card); padding: 12px; margin-bottom: 16px; display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.cat-search { display: flex; gap: 8px; flex: 1; min-width: 240px; max-width: 420px; }
.cat-search .form-control { flex: 1; }
/* KB-90: фільтр з додатковими селектами (реєстри). Інпуту потрібно більше
   місця, ніж дозволяє .cat-search — той писався під input + кнопку. */
.cat-search--wide { max-width: none; flex-wrap: wrap; }
.cat-search--wide .form-control { flex: 1 1 260px; min-width: 220px; }
.cat-search--wide .form-select { flex: 0 0 auto; width: auto; min-width: 140px; }
/* KB-94: селекти фільтр-бару реєстрів. Ширина фіксована і не залежить ні
   від вибраного значення, ні від того, чи відкритий TomSelect (він
   додає в контрол пошуковий інпут, і ``width: auto`` вище стрибав би).
   Правила йдуть ПІСЛЯ ``.cat-search--wide .form-select``: TomSelect
   копіює класи селекта на свій wrapper, тож специфічність однакова і
   виграє останнє. */
.cat-search--wide .ts-wrapper { flex: 0 0 auto; width: 170px; }
.cat-search--wide .ts-wrapper .ts-control { width: 100%; }
/* KB-103: страховка висоти. Стрибок лікує ``controlInput: null`` в
   ініціалізаторі (без пошукового інпута контрол однорядковий у будь-якому
   стані), а це — другий рубіж на випадок, що якийсь фільтр лишиться зі
   своїм інпутом: відкриття селекта не має рухати сусідів по бару. */
.cat-search--wide .ts-wrapper .ts-control { min-height: var(--control-h); height: var(--control-h); }
.cat-search--wide .ts-dropdown { width: 100%; min-width: 170px; }
/* Довге значення обрізається, а не розтягує контрол чи випадайку. */
.cat-search--wide .ts-control > .item,
.cat-search--wide .ts-dropdown .option { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* KB-103: вкладки окремим рядком над фільтр-баром — там, де сам бар
   широкий (договори: пошук + три селекти) і місця поруч не лишає. */
.cat-seg-row { margin-bottom: 12px; }
.cat-seg { display: inline-flex; gap: 4px; padding: 4px; background: var(--surface-subtle); border-radius: var(--r-pill); border: 1px solid var(--border); }
.cat-seg-btn { padding: 6px 14px; font-size: 13px; font-weight: 500; border-radius: var(--r-pill); border: none; background: transparent; color: var(--text-muted); cursor: pointer; text-decoration: none; line-height: 1.3; transition: color 0.15s, background 0.15s, box-shadow 0.15s; }
.cat-seg-btn:hover { color: var(--text); text-decoration: none; }
.cat-seg-btn.active { background: #FFFFFF; color: var(--text); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); }
/* KB-303: a count inside a segment («Активні 12») — was a local ``.cnt`` in
   the finance ledgers and «Касові розриви». */
.cat-seg-count { font-size: 11px; font-weight: 700; padding: 1px 7px; margin-left: 6px; border-radius: var(--r-pill); background: var(--surface); color: var(--text-muted); }
.cat-seg-btn.active .cat-seg-count { background: var(--surface-subtle); color: var(--text); }
/* KB-340: кілька груп перемикачів на одній сторінці («Планування закупівель»:
   «Комплектуючі» і «Закупівлі») — групи в один рядок, над кожною підпис
   звичайним регістром. Активна вкладка — основним кольором (``--primary``),
   однаково в усіх групах: видно, яка з груп зараз показана. */
.cat-seg-groups { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px 24px; margin-bottom: 12px; }
.cat-seg-group { display: inline-flex; flex-direction: column; gap: 4px; }
.cat-seg-label { font-size: var(--fs-sub); line-height: var(--lh-sub); color: var(--text-muted); }
.cat-seg--primary .cat-seg-btn.active { background: var(--primary); color: #FFFFFF; box-shadow: none; }
.cat-seg--primary .cat-seg-btn.active .cat-seg-count { background: rgba(255, 255, 255, 0.22); color: #FFFFFF; }
/* KB-412 (replaces the inner scroll of KB-273): a table NEVER scrolls inside
   its frame. The frame is never narrower than the table
   (``min-width: min-content``) — so ``overflow: hidden`` only rounds the
   corners and has nothing to cut; the cards around it grow the same way
   (.cat-block, .cat-card, .cat-form-card), and a table wider than the window
   scrolls the page (.app-shell). Never put ``overflow-x: auto|scroll`` on a
   table or its wrapper — tests/core/test_table_no_inner_scroll_kb412.py. */
.cat-table { border: 1px solid var(--border); border-radius: var(--r-card); overflow: hidden; min-width: min-content; background: var(--surface); }
.cat-table .table { margin: 0; }
.cat-table .table > tbody > tr:last-child > td { border-bottom: none; }
/* KB-273: density B (mock-up 1A′) — column headers 12/500 in normal case on
   one line; a header wider than a fixed column ends in an ellipsis instead of
   spilling into its neighbour. */
.cat-table .table > thead > tr > th { font-size: var(--fs-th); font-weight: var(--fw-th); line-height: var(--lh-sub); text-transform: none; letter-spacing: 0; color: var(--text-muted); padding: calc((var(--table-head-h) - 16px) / 2) var(--cell-px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; border-bottom: 1px solid var(--border); background: var(--table-head-bg); }
.cat-table .table > tbody > tr > td { vertical-align: middle; padding: var(--cell-py) var(--cell-px); border-bottom: 1px solid var(--divider); }
/* Bootstrap paints every cell from --bs-table-bg-state; setting it on hover
   keeps row-level colours (.table-warning …) working when not hovered. */
.cat-table .table > tbody > tr:hover > * { --bs-table-bg-state: var(--row-hover); }
/* KB-96: ширини колонок списку листів не мають залежати від вибірки.
   ``table-layout: auto`` міряє колонки за вмістом, тож після пошуку,
   що лишив один короткий рядок, пропорції інші, ніж у повному списку.
   Модифікатор — тільки під листи; решта .cat-table лишається auto. */
.cat-table--letters .table { table-layout: fixed; width: 100%; }
/* KB-273: text wraps between words; ``overflow-wrap: break-word`` breaks a
   word only when it cannot fit the column on a line of its own (a long
   number, an IBAN). The per-column ``word-break: break-word`` of KB-96/97 is
   gone — the owner's rule is word wrapping first, a hard break last. */
.cat-table--letters .table td { overflow-wrap: break-word; }
/* KB-97: той самий прийом для договорів. Колонок більше, тож ширини
   свої, а «Короткий зміст» знову лишається без width і забирає залишок.
   Модифікатор носять і список, і таблиця специфікацій на картці. */
.cat-table--contracts .table { table-layout: fixed; width: 100%; }
.cat-table--contracts .table td { overflow-wrap: break-word; }
/* KB-362: a formset table inside a form (the item rows of a sale, a client
   specification, a supply invoice …). Under ``table-layout: auto`` the
   <colgroup> widths were a hint only: focusing a TomSelect (its search input
   takes 4rem, KB-294) or choosing a longer name re-measured the column, and
   the column jumped. Fixed, the colgroup is the law — every table with the
   modifier carries one; the column without a width takes the rest. A field
   fills its cell, a long chosen name ends in an ellipsis (KB-294). */
.cat-table--form .table { table-layout: fixed; width: 100%; }
.cat-table--form .table > tbody > tr > td > .ts-wrapper,
.cat-table--form .table > tbody > tr > td > .form-control,
.cat-table--form .table > tbody > tr > td > .form-select { width: 100%; }
/* A formset table whose fixed columns leave the flexible one too little room
   in the 760px card: the table keeps this minimum width, and the card grows to
   it (KB-412, ``min-width: min-content`` on the card) — no inner scroll. */
.cat-table--form-wide .table { min-width: 980px; }
/* KB-273: in the fixed-width tables a header wraps between words instead of
   ending in an ellipsis — «Місце / зберігання» on two lines reads better than
   «Місце зберіга…». No break inside a word: a single word wider than its
   column is the one case left to the ellipsis. Auto-layout tables keep the
   one-line header above (the column simply grows). */
.cat-table--letters .table > thead > tr > th,
.cat-table--contracts .table > thead > tr > th { white-space: normal; overflow-wrap: normal; word-break: normal; vertical-align: bottom; }
.cat-muted { color: var(--text-muted); }
.cat-strong { font-weight: 500; }
/* KB-273: second, grey line of a cell — density B, 12/16. */
.cat-sub { font-size: var(--fs-sub); line-height: var(--lh-sub); color: var(--text-muted); }
/* KB-273: cell utilities for the migration (mock-up 1A′ / 1D). A clamped
   cell shows the full text in its ``title`` — that is the template's job. */
.cat-line-1 { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cat-line-2 { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden; white-space: normal; }
/* KB-412: in a table cell a one-line clamp lets its column shrink. A
   ``nowrap`` line is as wide as its whole text even as the column's minimum,
   so under ``table-layout: auto`` the ellipsis never showed — the column took
   the full name and the table outgrew its block. As a 1-line box clamp the
   text still shows on one line with an ellipsis, but the column's minimum is
   the longest word: the table fits the block and the name is cut instead. */
.cat-table td .cat-line-1 { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 1; line-clamp: 1; white-space: normal; }
.cat-nil { color: var(--text-subtle); }   /* empty value, «—» */
/* KB-279: a two-line list (1A′) — every cell starts at the top, so the grey
   second lines of neighbouring cells stay on one baseline. */
.cat-table--two-line .table > tbody > tr > td { vertical-align: top; }
/* KB-306: a compact table — a deliberate modifier for a table of up to ~4
   columns without long text (a directory, totals, balances), never automatic:
   every other .cat-table stays 100% wide. The frame hugs the table, left
   aligned; the name column (the first) is at least 280px, a numeric column at
   least 120px, so a short list does not collapse into a strip; content wider
   than the page widens the frame and the page (KB-412). In a flush
   block the card itself hugs its content — .cat-block--compact, but only a
   standalone block of a list page (like «Посади»): in the stack of a card
   (3A / 3C) every block is the full column width and only the table inside
   is compact (KB-334). */
.cat-table--compact { width: fit-content; max-width: 100%; }
.cat-table--compact .table { width: auto; }
.cat-table--compact .table > * > tr > :first-child { width: 280px; }
.cat-table--compact .table > * > tr > .cat-num,
.cat-table--compact .table > * > tr > .cat-amount { width: 120px; }
.cat-block--compact { width: fit-content; max-width: 100%; align-self: flex-start; }
.cat-block--compact > .cat-table--compact { width: auto; }
.cat-block--compact > .cat-table--compact:not(.cat-table--fixed-cols) .table { min-width: 100%; }
/* KB-306: several tables with the same set of columns on one page (the groups
   of «Категорії витрат», the blocks of «Аналітика витрат») share one set of
   fixed widths, so a column stands on one vertical from the first table to
   the last. Every table carries the same <colgroup> of .cat-col-* classes;
   ``width: 1px`` under the fixed layout makes the table exactly the sum of its
   columns (CSS 2.1 §17.5.2.1), whatever the data. A cell text longer than its
   column is cut — ``.cat-line-1`` with the full text in ``title``. Under the
   fixed layout a <col> width wins over a cell width, so the compact minimums
   above do not fight the colgroup. */
.cat-table--fixed-cols .table { table-layout: fixed; width: 1px; }
.cat-col-name { width: 320px; }
.cat-col-num { width: 150px; }
.cat-col-short { width: 110px; }
.cat-col-act { width: 64px; }
/* KB-362: a catalogue picker in a form table (a product TomSelect next to a
   free-text name column). */
.cat-col-pick { width: 280px; }
/* KB-309: «Баланси» — the same fixed columns, wider: the wallet name with the
   bank line under it (360) and one column per currency (170), so an amount
   stands right next to the name in every block of the page. */
.cat-col-wallet { width: 360px; }
.cat-col-money { width: 170px; }
/* KB-313: a picker table — a number column («№12», 72) and a column that
   holds a per-row form (quantity + button, 220), «Відвантажити» on the ship
   page. The row form is one line: the number field keeps --control-h from
   the global field rule and only gets a narrow width here. */
.cat-col-ref { width: 72px; }
.cat-col-form { width: 220px; }
.cat-row-form { display: flex; align-items: center; gap: 8px; margin: 0; }
.cat-row-form .form-control { width: 76px; flex: none; }
/* KB-345: a per-row text field (the bank name of «Степан») — a name, not a
   number, so a wider field; the action beside it stays one icon. */
.cat-row-form--text .form-control { width: 220px; }
.cat-row-form .cat-btn-icon { margin-left: 0; flex: none; }
.cat-nowrap { white-space: nowrap; }
/* KB-279: small pill next to a value in a cell — a status («скасовано»), a
   count («2 проєкти», «📎 1»). ``--warn`` marks a row that still needs work
   (an expense without a category). */
.cat-badge { display: inline-block; padding: 0 6px; border-radius: var(--r-badge); background: var(--surface-subtle); color: var(--text-muted); font-size: 11px; font-weight: 600; line-height: var(--lh-sub); white-space: nowrap; text-decoration: none; }
a.cat-badge:hover { color: var(--text); text-decoration: none; }
/* KB-396: a pill never wraps inside itself — «акт / виконаних / робіт» in
   three lines; a long text is cut with an ellipsis (the full text goes into
   ``title`` in the template), whole pills wrap in their flex row instead.
   ``overflow: hidden`` moves an inline-block's baseline to its bottom edge,
   so the pill is centred on the line instead. */
.cat-badge,
.doc-type-badge { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: middle; }
.cat-badge--warn { background: var(--warn-soft); color: var(--warn-strong); }
/* KB-303: a two-state status in a journal — «Активний» / «Скасовано» in the
   finance ledgers. One pair for every module; no new colours, the tokens. */
.cat-badge--success { background: var(--success-bg); color: var(--success-text); }
.cat-badge--danger { background: var(--danger-soft); color: var(--danger); }
/* KB-296: an accent badge — a role next to a name («C-level» on the employee
   card), not a status. */
.cat-badge--primary { background: var(--primary-soft); color: var(--primary-dark); }
/* KB-273: numbers never break inside: right-aligned, one line, tabular
   figures so the column lines up. ``.cat-num`` is the same rule for numeric
   columns that are not money (quantities, counts). */
.cat-amount,
.cat-num { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.cat-amount .currency { color: var(--text-muted); margin-left: 4px; font-size: 11px; font-weight: 500; }
/* KB-301: the one value a page wants read in red — a minus on a balance (the
   company owes a person), an overdue row. Also sets Bootstrap's cell colour
   variable, so it works on a <td> and on a whole <tr> (links keep theirs). */
.cat-negative { color: var(--danger); --bs-table-color-state: var(--danger); }
/* KB-298: a cancelled row of a history table (supply invoices, processing
   orders on the contractor card) — muted, on the whole <tr>, the same way
   .cat-negative sets Bootstrap's cell colour variable. */
.cat-row-muted { color: var(--text-muted); --bs-table-color-state: var(--text-muted); }
.cat-actions { white-space: nowrap; text-align: right; }
.cat-archived { color: var(--text-muted); }
.cat-btn-icon { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; padding: 0; border: 1px solid var(--border); border-radius: var(--r-input); background: var(--surface); color: var(--text-muted); font-size: 13px; margin-left: 4px; transition: all 0.15s; text-decoration: none; cursor: pointer; }
.cat-btn-icon:hover { background: var(--surface-subtle); color: var(--text); border-color: var(--border-strong); text-decoration: none; }
.cat-btn-icon.danger:hover { background: #FEF2F2; color: #DC2626; border-color: #FECACA; }
.cat-btn-icon.restore:hover { background: var(--primary-soft); color: var(--primary-dark); border-color: var(--border-strong); }
.cat-inline-form { display: inline; }
.cat-empty { text-align: center; padding: 48px 20px; }
.cat-empty .bi { font-size: 28px; color: var(--text-muted); margin-bottom: 10px; display: block; }
.cat-empty-text { color: var(--text-muted); font-size: 14px; margin-bottom: 14px; }
/* KB-345: the title of a whole-page empty state (403 / 404 / «немає
   доступних розділів»); the buttons under it in one row. */
.cat-empty-title { margin: 0 0 6px; font-size: var(--fs-h1); line-height: var(--lh-h1); }
.cat-empty-actions { display: flex; justify-content: center; gap: 8px; margin: 0; }

/* === Form card + grid === */
.cat-card { border: 1px solid var(--border); border-radius: var(--r-card); padding: 24px; background: var(--surface); max-width: 720px; min-width: min-content; }
/* KB-412: a grid of fields (and of properties, .cat-props) takes the width
   of its card and never asks for more — ``contain: inline-size`` drops its
   intrinsic width and the ``minmax(0, 1fr)`` tracks share the card evenly
   instead of growing to their content, so a long chosen name in a TomSelect (cut with an ellipsis,
   KB-294) or a long IBAN does not widen the card the way a table does
   (.cat-form-card { min-width: min-content }). */
.cat-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px 20px; contain: inline-size; }
.cat-grid .full { grid-column: 1 / -1; }
.cat-field { display: block; }
.cat-field .form-label { display: block; }
.cat-section + .cat-section { margin-top: 24px; padding-top: 20px; border-top: 1px solid var(--border); }
.cat-section .section-title { margin: 0 0 14px; }
.cat-errors { color: #991B1B; font-size: 12px; margin-top: 6px; }
.cat-errors ul { list-style: none; padding: 0; margin: 0; }
.cat-help { color: var(--text-muted); font-size: 12px; margin-top: 4px; }
/* KB-298: a grey explanation on the line of a form section title
   («Підписант (для ТОВ — …)»). */
.cat-title-hint { margin-left: 4px; font-size: var(--fs-sub); font-weight: 400; color: var(--text-muted); }
/* KB-98: textarea тягнеться лише вгору-вниз. Горизонтальний resize
   ламав би сітку картки, а вертикальний — законна потреба: «Короткий
   зміст» буває і на абзац. Правило глобальне для .cat-field, бо шкоди
   поза реєстрами від нього немає. */
.cat-field textarea { resize: vertical; }
/* KB-300: a checkbox field of a form printed by a loop (templates/_form_fields.html)
   — the input and its label on one line, the label without its bottom margin. */
.cat-form-check { display: flex; align-items: center; gap: 8px; }
.cat-form-check .form-label { margin: 0; }
/* KB-98: імʼя вже завантаженого скана над інпутом у правому блоці форми. */
.cat-scan-current { font-size: 12px; margin-bottom: 8px; word-break: break-word; }
/* KB-90: поле + кнопка-дія в один ряд (підказка номера в реєстрах). */
.cat-input-group { display: flex; gap: 8px; align-items: stretch; }
.cat-input-group .form-control { flex: 1; min-width: 0; }
.cat-input-group .btn { flex: 0 0 auto; white-space: nowrap; }
.cat-form-actions { position: sticky; bottom: 0; background: var(--surface); border-top: 1px solid var(--border); padding: 14px 0; margin-top: 28px; display: flex; justify-content: space-between; align-items: center; gap: 12px; max-width: 720px; }

/* === DETAIL PAGES: content left, vertical action panel right (KB-39) === */
/* Mirrors the reference layout of templates/documents/document_detail.html
   (ported from AOK): the card content stays in the left column, every
   action lives in the sidebar card on the right. */
.detail-layout { display: grid; grid-template-columns: 1fr 300px; gap: 28px; align-items: start; }
/* KB-276: a card page whose viewer has no actions gets no empty side column. */
.detail-layout--single { grid-template-columns: minmax(min-content, 1fr); }
/* KB-416: a card whose right column is a stack of cards (state and actions,
   the figures, the metadata) rather than one action panel — the sale card.
   The column is --form-side wide; the cards inside are .detail-side,
   .cat-block and .cat-tally, one under another. */
.detail-layout--rail { grid-template-columns: minmax(min-content, 1fr) var(--form-side); }
.detail-rail { display: flex; flex-direction: column; gap: var(--gap-block); }
.detail-side { border: 1px solid var(--border); border-radius: var(--r-card); background: var(--surface); padding: 22px 24px; }
.detail-actions { display: flex; flex-direction: column; gap: 8px; }
.detail-actions form { margin: 0; }
.detail-actions .btn { width: 100%; display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
.detail-side-section { border-top: 1px solid var(--border); margin-top: 14px; padding-top: 14px; }
.detail-side-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin-bottom: 8px; }
.detail-side-meta { font-size: 12px; color: var(--text-muted); margin-top: 14px; line-height: 1.5; }

/* === Модальне підтвердження (KB-187) === */
/* Перший нативний <dialog> у системі, і стилі названі під
   перевикористання: наступна незворотна дія бере `cat-dialog`, а не
   пише свій блок. Радіуси й рамка — ті самі змінні, що в `cat-card`,
   тож вікно виглядає карткою, яка виїхала наперед, а не чужим
   віджетом. Ширина фіксована й обмежена вьюпортом: усередині одне
   речення про наслідки, і рядок на всю широчінь монітора не читається. */
.cat-dialog { width: 420px; max-width: calc(100vw - 32px); border: 1px solid var(--border); border-radius: var(--r-card); background: var(--surface); color: var(--text); padding: 20px; }
.cat-dialog::backdrop { background: rgba(15, 23, 42, 0.45); }
.cat-dialog h3 { margin: 0 0 10px; font-size: 16px; }
.cat-dialog p { margin: 0 0 8px; font-size: 13.5px; line-height: 1.5; }
.cat-dialog .cat-dialog-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }
.cat-dialog form { margin: 0; }
@media (max-width: 900px) { .detail-layout { grid-template-columns: 1fr; } }

/* === Bank inline-formset (CSS grid) === */
.cat-bank-head-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
.cat-add-btn { background: transparent; border: 1px dashed var(--border-strong); border-radius: var(--r-input); color: var(--primary); font-size: 13px; font-weight: 500; padding: 6px 12px; cursor: pointer; font-family: inherit; display: inline-flex; align-items: center; gap: 6px; transition: all 0.15s; }
.cat-add-btn:hover { border-color: var(--primary); background: var(--primary-soft); }
.cat-bank-table { border: 1px solid var(--border); border-radius: var(--r-card); overflow: hidden; }
.cat-bank-header, .cat-bank-row { display: grid; grid-template-columns: 1.4fr 1fr 1.4fr 0.8fr 80px 40px; gap: 10px; align-items: center; padding: 10px 12px; }
.cat-bank-header { background: var(--surface-subtle); border-bottom: 1px solid var(--border); }
.cat-bank-header > div { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); font-weight: 600; }
.cat-bank-header .center, .cat-bank-row .center { text-align: center; justify-self: center; }
.cat-bank-row { border-bottom: 1px solid var(--border); background: var(--surface); }
#banks-container > .cat-bank-row:last-child { border-bottom: none; }
.cat-bank-row .form-control { height: 32px; padding: 4px 8px; font-size: 13px; }
.cat-bank-empty { padding: 16px 12px; color: var(--text-muted); font-size: 13px; text-align: center; }
/* KB-299: the bank formset table in the 1A′ look (UI_PATTERNS §3) — header
   12/500 in normal case on the table-head background, 6×10 cells, divider
   between rows. A modifier, so the forms not migrated yet (supplier, client,
   importer) keep their look until their own migration takes it. */
.cat-bank-head-row > .section-title { margin: 0; }
.cat-bank-table--1a .cat-bank-header { background: var(--table-head-bg); }
.cat-bank-table--1a .cat-bank-header > div { font-size: var(--fs-th); font-weight: var(--fw-th); line-height: var(--lh-sub); text-transform: none; letter-spacing: 0; }
.cat-bank-table--1a .cat-bank-header, .cat-bank-table--1a .cat-bank-row { padding: var(--cell-py) var(--cell-px); }
.cat-bank-table--1a .cat-bank-row { border-bottom-color: var(--divider); }

/* === Signature upload + preview === */
.cat-file { display: flex; flex-direction: column; gap: 10px; align-items: flex-start; }
.cat-file .form-control { max-width: 360px; }
.cat-signature-preview { width: 80px; height: 80px; object-fit: contain; border: 1px solid var(--border); border-radius: 6px; background: var(--surface-subtle); padding: 4px; }

/* === File field (KB-275, mock-up 2D) === */
/* templates/django/forms/widgets/{file,clearable_file_input}.html. The real
   <input type=file> is visually hidden, not display:none — it keeps focus,
   form submission and the label-for click that opens the file chooser. */
.cat-upload { position: relative; width: 100%; }
.cat-file .cat-upload { max-width: 360px; }
.cat-upload > input[type="file"] { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); border: 0; opacity: 0; }
.cat-upload-drop { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; min-height: 96px; padding: 12px; border: 1.5px dashed var(--border-strong); border-radius: var(--r-input); background: var(--table-head-bg); color: var(--text); text-align: center; cursor: pointer; margin: 0; transition: border-color 0.1s, background 0.1s; }
.cat-upload-drop .bi { font-size: 18px; color: var(--text-muted); }
.cat-upload-link { color: var(--primary); }
.cat-upload-hint { font-size: var(--fs-sub); line-height: var(--lh-sub); color: var(--text-muted); }
.cat-upload-chosen { font-size: var(--fs-sub); line-height: var(--lh-sub); font-weight: 500; color: var(--text); max-width: 100%; overflow-wrap: anywhere; }
.cat-upload.is-dragover .cat-upload-drop,
.cat-upload.is-dragover .cat-upload-card,
.cat-upload > input[type="file"]:focus-visible + .cat-upload-drop { border-color: var(--primary); background: var(--primary-soft); }
.cat-upload-card { display: flex; flex-direction: column; gap: 8px; padding: 12px; border: 1px solid var(--border); border-radius: var(--r-input); background: var(--surface); }
.cat-upload-current { display: flex; align-items: center; gap: 10px; min-width: 0; }
.cat-upload-ext { flex: none; display: inline-flex; align-items: center; justify-content: center; min-width: 34px; height: 22px; padding: 0 5px; border-radius: var(--r-badge); background: var(--danger-soft); color: #B42318; font-size: 11px; font-weight: 600; letter-spacing: 0.02em; }
.cat-upload-name { min-width: 0; font-weight: 500; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cat-upload-actions { display: flex; flex-wrap: wrap; gap: 6px; }
.cat-upload-actions label { margin: 0; cursor: pointer; }
.cat-upload-clear { border: 1px solid transparent; color: #B42318; background: transparent; }
.cat-upload-clear:hover { background: var(--danger-soft); color: #B42318; }
.cat-upload-clear-input { position: absolute; opacity: 0; width: 1px; height: 1px; pointer-events: none; }
.cat-upload-clear-input:focus-visible + .cat-upload-clear { box-shadow: 0 0 0 3px var(--primary-ring); }
/* «Видалити» ticked: the file goes away on save — say so, and let the same
   control take it back. */
.cat-upload.is-cleared .cat-upload-name { text-decoration: line-through; color: var(--text-muted); }
.cat-upload.is-cleared .cat-upload-clear { border-color: var(--danger-border); background: var(--danger-soft); }

/* === Date picker (KB-275, flatpickr) === */
/* The calendar in the primary colour — flatpickr's own #569ff7 would be a
   new «second blue». */
.flatpickr-calendar { font-family: inherit; font-size: var(--fs-body); border-radius: var(--r-card); box-shadow: var(--shadow-md), 0 0 0 1px var(--border); }
.flatpickr-day { border-radius: var(--r-input); }
.flatpickr-day:hover, .flatpickr-day:focus { background: var(--primary-soft); border-color: var(--primary-soft); }
.flatpickr-day.today { border-color: var(--primary); }
.flatpickr-day.today:hover, .flatpickr-day.today:focus { background: var(--primary-soft); border-color: var(--primary); color: var(--text); }
.flatpickr-day.selected, .flatpickr-day.selected:hover, .flatpickr-day.selected:focus { background: var(--primary); border-color: var(--primary); color: #fff; }
.flatpickr-months .flatpickr-prev-month:hover svg, .flatpickr-months .flatpickr-next-month:hover svg { fill: var(--primary); }
/* The visible field is flatpickr's altInput; it carries the original's
   classes, so it looks like every other .form-control. */
.form-control.cat-date { background-color: var(--surface); }
.form-control.cat-date[readonly]:not(.flatpickr-input) { background-color: var(--surface-subtle); }
/* KB-275: the calendar icon of mock-up 2C, right inside the visible field.
   A background, not an element: a click anywhere on the field already opens
   the calendar. A data URI cannot read CSS variables, so the stroke is the
   literal value of --text-muted (#5F6672), and of --text-subtle (#A1A6B0)
   for a field that cannot be changed. The right padding keeps the text off
   the icon: control padding + 16px icon + 6px gap. */
.form-control.cat-date { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235F6672' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='4' y='5' width='16' height='16' rx='2'/%3E%3Cpath d='M16 3v4M8 3v4M4 10h16'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right var(--control-px) center; background-size: 16px 16px; padding-right: calc(var(--control-px) + 22px); }
.form-control.cat-date:disabled,
.form-control.cat-date[readonly] { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23A1A6B0' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='4' y='5' width='16' height='16' rx='2'/%3E%3Cpath d='M16 3v4M8 3v4M4 10h16'/%3E%3C/svg%3E"); }

/* === Page header (KB-276) === */
/* Breadcrumbs «група / розділ» (block breadcrumbs in base.html +
   templates/_breadcrumbs.html), then the title with an optional grey
   subtitle on the left and the actions on the right. The way back is the
   area crumb — text above the title, never a button in the header. The old
   old header classes were removed once the last page moved (KB-345). */
.cat-crumbs { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; font-size: 12.5px; line-height: var(--lh-sub); color: var(--text-muted); margin-bottom: 4px; }
.cat-crumbs a { color: var(--text-muted); }
.cat-crumbs a:hover { color: var(--text); }
.cat-crumbs-sep { color: var(--text-subtle); }
.cat-page-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin-bottom: var(--gap-block); }
.cat-page-head-text { min-width: 0; }
.cat-page-title { margin: 0; font-size: var(--fs-h1); line-height: var(--lh-h1); font-weight: 600; letter-spacing: -0.012em; }
.cat-page-sub { margin: 2px 0 0; font-size: var(--fs-body); color: var(--text-muted); }
.cat-page-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* KB-304: KPI cards on the right of a page head — small tiles «label over a
   sum» (the tab totals of «Касові розриви»). The sum goes through ``money``,
   the currency grey; the label in normal case, never uppercase. */
.cat-kpis { display: flex; flex-wrap: wrap; gap: 8px; }
/* KB-334: counters that fill their block — equal shares of the full width,
   wrapping on a narrow screen (the «Аналітика по документах» of a card). */
.cat-kpis--fill { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }
.cat-kpi { min-width: 128px; padding: 8px 14px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-card); }
.cat-kpi-label { font-size: var(--fs-sub); line-height: var(--lh-sub); color: var(--text-muted); }
.cat-kpi-value { margin-top: 2px; font-size: 15px; line-height: 20px; font-weight: 600; color: var(--text); white-space: nowrap; font-variant-numeric: tabular-nums; }
.cat-kpi-value .currency { margin-left: 4px; color: var(--text-muted); font-size: 11px; font-weight: 500; }
/* KB-335: a counter that IS a tab — a button switching the list under the
   counters (the «Документи» of a product card). Its lines stack like the
   plain counter's; the active one — primary border on the soft primary fill. */
.cat-kpi--tab { display: block; width: 100%; text-align: left; font: inherit; color: inherit; cursor: pointer; transition: border-color 0.15s, background 0.15s; }
.cat-kpi--tab > span { display: block; }
.cat-kpi--tab:hover { border-color: var(--border-strong); }
.cat-kpi--tab:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--primary-ring); }
.cat-kpi--tab.active { border-color: var(--primary); background: var(--primary-soft); }
/* KB-304: a sortable column header — the header text is the link to the
   sorted list, the arrow shows the direction of the active sort. */
.cat-th-sort { color: inherit; text-decoration: none; white-space: nowrap; }
.cat-th-sort:hover { color: var(--text); text-decoration: none; }
.cat-th-sort .bi { font-size: 11px; margin-left: 4px; }
/* KB-304: several status badges in one cell (the «Стан» chips) — they wrap
   as whole chips, a gap between them. */
.cat-badge-list { display: flex; flex-wrap: wrap; gap: 4px; }

/* === Card blocks and property grids (KB-276, mock-ups 3A / 3C) === */
/* A titled card; inside, a grid of properties — label small and grey above
   the value, 2 or 3 columns, empty value .cat-nil (``or_nil`` filter).
   Actions go in the KB-39 side panel (.detail-layout / .detail-side). */
/* KB-412: a block and a stack are never narrower than their content — a
   table inside widens the block (and the page), it never scrolls inside. */
.cat-stack { display: flex; flex-direction: column; gap: var(--gap-block); min-width: min-content; }
.cat-block { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-card); padding: var(--card-pad-lg); min-width: min-content; }
.cat-block-title { margin: 0 0 14px; font-size: var(--fs-card-title); line-height: 20px; font-weight: 600; letter-spacing: 0; }
.cat-block--flush { padding: 0; overflow: hidden; }
.cat-block--flush > .cat-block-title { padding: var(--card-pad) var(--card-pad-lg) 0; margin-bottom: 12px; }
.cat-block--flush > .cat-table { border: 0; border-top: 1px solid var(--border); border-radius: 0; }
.cat-block-empty { margin: 0; padding: 0 var(--card-pad-lg) var(--card-pad); }
.cat-props { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px 24px; margin: 0; contain: inline-size; }
.cat-props--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.cat-prop { min-width: 0; }
.cat-prop--wide { grid-column: 1 / -1; }
.cat-prop dt { font-size: var(--fs-sub); line-height: var(--lh-sub); font-weight: 400; color: var(--text-muted); margin: 0 0 4px; }
.cat-prop dd { margin: 0; font-size: var(--fs-body); line-height: var(--lh-body); color: var(--text); overflow-wrap: anywhere; }
@media (max-width: 900px) { .cat-props--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 600px) { .cat-props, .cat-props--3 { grid-template-columns: minmax(0, 1fr); } }
/* KB-282, mock-up 3C (the transaction card): inside a flush block, the part
   between the title and the table (.cat-block-body) and a bar under it with
   the block's own actions (.cat-block-foot); a table's totals rows
   (.cat-table-totals on <tfoot>); a currency next to an amount anywhere in a
   card. */
.cat-block-body { padding: 0 var(--card-pad-lg) var(--card-pad); }
.cat-block-foot { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; padding: 12px var(--card-pad-lg); border-top: 1px solid var(--border); }
/* KB-336: a one-field form in a block foot («Прив'язати проєкт» of a client
   card) — the label over the field, the button level with the field. */
.cat-bind-form { align-items: flex-end; }
.cat-bind-form > .cat-field { flex: 1 1 280px; max-width: 480px; }
.cat-block-foot.cat-muted { margin: 0; }
.cat-block .currency { color: var(--text-muted); margin-left: 4px; font-size: 11px; font-weight: 500; }
.cat-table .table > tfoot.cat-table-totals > tr > td { padding: 4px var(--cell-px); border: 0; text-align: right; color: var(--text-muted); }
.cat-table .table > tfoot.cat-table-totals > tr > td.cat-amount { color: var(--text); }
.cat-table .table > tfoot.cat-table-totals > tr:first-child > td { padding-top: 10px; border-top: 1px solid var(--border); }
.cat-table .table > tfoot.cat-table-totals > tr:last-child > td { padding-bottom: 10px; color: var(--text); font-weight: 600; }
/* KB-302: summary rows INSIDE a report table's body (the expense analytics,
   «Послуги»), where the label stays in the first column: the «Разом» row
   (.cat-row-total, bold, a stronger line above) and the group row over its
   members (.cat-row-group, bold on the header background — a category type
   over its categories). */
.cat-table .table > tbody > tr.cat-row-total > td { font-weight: 600; border-top: 1px solid var(--border-strong); }
.cat-table .table > tbody > tr.cat-row-group > td { font-weight: 600; --bs-table-bg-state: var(--table-head-bg); }
/* KB-415: the selected row of an overview table whose card is open under it
   (``?item=`` on the sale card) — the primary tint plus a left accent bar.
   Bootstrap paints a cell's background as an inset shadow, so the bar is a
   second inset shadow on the first cell, over the same tint. Declared after
   the hover rule so a hovered selected row stays selected. */
.cat-table .table > tbody > tr.cat-row-selected > * { --bs-table-bg-state: var(--primary-soft); }
.cat-table .table > tbody > tr.cat-row-selected > :first-child { box-shadow: inset 3px 0 0 var(--primary), inset 0 0 0 9999px var(--primary-soft); }
/* KB-415: a thin progress bar for «відвантажено X з Y» — a real <progress>
   (value / max in the markup, no inline width), next to the «X з Y» text. */
.cat-progress { appearance: none; -webkit-appearance: none; display: inline-block; width: 72px; height: 6px; border: 0; border-radius: var(--r-pill); background: var(--divider); overflow: hidden; vertical-align: middle; }
.cat-progress::-webkit-progress-bar { background: var(--divider); border-radius: var(--r-pill); }
.cat-progress::-webkit-progress-value { background: var(--primary); border-radius: var(--r-pill); }
.cat-progress::-moz-progress-bar { background: var(--primary); border-radius: var(--r-pill); }
/* KB-415: an empty state that offers its own first action inline (the
   selected position's «Потреби» before the first wave) — a dashed frame
   instead of the centred .cat-empty. */
.cat-empty-dashed { padding: 14px 16px; border: 1px dashed var(--border-strong); border-radius: var(--r-card); }

/* KB-282, mock-up 3C: a money card's title is the amount — large, tabular,
   the currency grey — with the type / state badges beside it. */
.cat-page-title-row { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 8px; }
.cat-page-amount { font-size: var(--fs-amount); line-height: var(--lh-amount); font-variant-numeric: tabular-nums; white-space: nowrap; }
.cat-page-amount .currency { margin-left: 6px; color: var(--text-muted); font-size: var(--fs-h1); font-weight: 500; }

/* KB-296, mock-up 3C (the employee card): a block title with a control on
   the same line («Показати» of the personal data); the locked message of a
   block the viewer has no key for. */
.cat-block-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 14px; }
.cat-block-head > .cat-block-title { margin: 0; }
/* KB-298: the same head in a flush block (a history table with «Усі →»)
   takes the padding a flush block's bare title has. */
.cat-block--flush > .cat-block-head { padding: var(--card-pad) var(--card-pad-lg) 0; margin-bottom: 12px; }
/* KB-298: a free-text value that keeps its line breaks (notes, summaries) —
   the one rule the contract, letter and contractor cards each wrote locally. */
.cat-multiline { white-space: pre-wrap; }
/* KB-301: a summary card over a list — one wrapping line of «label: value»
   pairs («Цього місяця: проведено 3 з 5 · Лишилось …», the payroll block of
   «Регулярні платежі»); a button in it goes to the right. */
.cat-summary { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 24px; padding: 10px var(--card-pad-lg); margin-bottom: 12px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-card); font-size: var(--fs-body); line-height: var(--lh-body); }
.cat-summary strong { font-weight: 600; font-variant-numeric: tabular-nums; }
.cat-summary .btn { margin-left: auto; }
.cat-locked { display: flex; align-items: center; gap: 8px; margin: 0; color: var(--text-muted); }
/* KB-311: a blocked verb in the action rail — lock, the verb, and under it the
   reason (the very string the service refuses a forged POST with). */
.cat-locked--action { align-items: flex-start; padding: 8px 10px; background: var(--surface-subtle); border-radius: var(--r-input); font-size: var(--fs-body); }
.cat-locked--action > .bi { line-height: var(--lh-body); }
/* KB-311: a figures block of a card («Прибуток», «Повна собівартість», the
   money summary of «Фінанси») — label left, amount right, the closing row
   bold over a divider. Blocks stand side by side in .cat-tally-grid (2–3 in a
   row on a wide screen). A grey marker next to a label («орієнтовно», with
   the explanation in ``title``) — .cat-tally-note. */
.cat-tally-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 16px; align-items: start; }
.cat-tally { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-card); padding: 14px var(--card-pad); }
.cat-tally-title { margin: 0 0 8px; font-size: var(--fs-card-title); line-height: 20px; font-weight: 600; }
.cat-tally-row { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding: 2px 0; font-size: var(--fs-body); line-height: var(--lh-body); }
.cat-tally-row > b { font-weight: 500; white-space: nowrap; font-variant-numeric: tabular-nums; }
.cat-tally-row--total { border-top: 1px solid var(--divider); margin-top: 4px; padding-top: 8px; font-weight: 600; }
.cat-tally-row--total > b { font-weight: 600; }
.cat-tally-note { margin-left: 4px; font-size: 11px; font-weight: 400; color: var(--text-muted); }
.cat-tally .currency { margin-left: 4px; color: var(--text-muted); font-size: 11px; font-weight: 500; }
/* KB-296: a signature (facsimile) preview on a checkerboard, so a transparent
   PNG reads as transparent, and its active / inactive state. Shared by the
   employee card (KB-212) and the director's signature page (KB-299; its
   history table shows the state as .cat-badge instead). */
.cat-sig-preview { display: inline-block; padding: 12px; border: 1px solid var(--border); border-radius: var(--r-input); background-color: var(--surface); background-image: linear-gradient(45deg, #cfcfcf 25%, transparent 25%), linear-gradient(-45deg, #cfcfcf 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #cfcfcf 75%), linear-gradient(-45deg, transparent 75%, #cfcfcf 75%); background-size: 16px 16px; background-position: 0 0, 0 8px, 8px -8px, -8px 0; margin-bottom: 8px; }
.cat-sig-preview img { display: block; max-width: 320px; max-height: 160px; }
/* KB-442: прев'ю файла на картці (фото або PDF) — на ширину блоку, PDF —
   на висоту, з якою сторінку видно без прокрутки самого блоку. */
.cat-file-preview { border: 1px solid var(--border); border-radius: var(--r-input); background: var(--surface-subtle); }
.cat-file-preview img { display: block; max-width: 100%; height: auto; margin: 0 auto; }
.cat-file-preview iframe { display: block; width: 100%; height: 75vh; border: 0; }
/* KB-442: лічильник-вкладка посиланням (фільтр сторінки) — без підкреслення. */
a.cat-kpi--tab, a.cat-kpi--tab:hover { text-decoration: none; }
.cat-sig-status--active { color: #1a7f37; font-weight: 600; }
.cat-sig-status--inactive { color: var(--text-muted); }
.cat-sig-deactivate { margin-top: 12px; }
.cat-sig-upload { margin-top: 16px; }
.cat-sig-upload-foot { display: flex; justify-content: flex-end; margin-top: 12px; }
.cat-sig-history { margin-top: 16px; }

/* === Filter bar (KB-276, mock-up 4A) === */
/* Main filters in one row, the rest under «Ще фільтри» (a button toggling
   the panel, base.html), active filters as chips below (core.filters +
   templates/_filter_chips.html) with «Скинути все» and the result count. */
.cat-filterbar { display: flex; flex-direction: column; gap: 10px; margin-bottom: var(--gap-block); }
.cat-filterbar form { margin: 0; display: flex; flex-direction: column; gap: 10px; }
.cat-filterbar-main, .cat-filterbar-more { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.cat-filterbar-more { padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--r-card); background: var(--table-head-bg); }
.cat-filterbar-more[hidden] { display: none; }
.cat-filterbar .ts-wrapper { flex: 0 0 auto; width: 170px; }
.cat-filterbar .ts-wrapper .ts-control { width: 100%; min-height: var(--control-h); height: var(--control-h); }
.cat-filterbar .ts-dropdown { width: 100%; min-width: 170px; }
.cat-filterbar .ts-control > .item,
.cat-filterbar .ts-dropdown .option { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Dates: the intended 170px — the width the templates always asked for with an
   inline max-width that the old ``.cat-search--wide .form-control { min-width:
   220px }`` overruled. «26.09.2026» plus the KB-275 icon fit with ~60px spare. */
.cat-filterbar .cat-filterbar-date { flex: 0 0 auto; width: 170px; }
/* KB-295: пошук ``q`` у головному рядку бару — росте до 320px, не забираючи
   весь рядок (``.form-control`` має ``width: 100%``). Спільний для списків
   «Компанії»; реєстри тримають свій ``.reg-search`` із тими самими числами. */
.cat-filterbar .cat-filterbar-search { flex: 1 1 240px; width: auto; min-width: 200px; max-width: 320px; }
.cat-filterbar-more-btn .bi { font-size: 14px; }
.cat-filterbar-period { display: inline-flex; align-items: center; gap: 6px; }
.cat-filterbar-dash { color: var(--text-subtle); }
.cat-filterbar-count { display: inline-flex; align-items: center; justify-content: center; min-width: 18px; height: 18px; padding: 0 5px; margin-left: 2px; border-radius: var(--r-pill); background: var(--primary-soft); color: var(--primary-dark); font-size: 11px; font-weight: 600; }
/* KB-352: «Перевірка Степана» — бейдж у списках і на картках, лічильник меню,
   блок «Що зробив Степан», поля зі змінами, кнопка «Перевірено». */
.review-badge { display: inline-flex; align-items: center; gap: 3px; padding: 1px 7px; border-radius: 10px; font-size: 11.5px; font-weight: 600; background: #FEF3C7; color: #92400E; white-space: nowrap; }
.review-bot { color: var(--text-muted); font-size: 12px; margin-left: 4px; }
.app-nav-item { position: relative; }
.app-nav-item .app-nav-link { padding-right: 34px; }
.app-nav-item .app-nav-count { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); }
.app-nav-count { display: inline-flex; align-items: center; justify-content: center; min-width: 18px; height: 18px; padding: 0 5px; margin-left: auto; border-radius: var(--r-pill); background: #FEF3C7; color: #92400E; font-size: 11px; font-weight: 600; text-decoration: none; }
.app-nav-count:hover { color: #92400E; text-decoration: none; box-shadow: inset 0 0 0 1px #F59E0B; }
.review-origin { display: inline-flex; align-items: center; gap: 4px; color: var(--text-muted); }
.review-reasons { margin: 0 0 16px; padding: 10px 14px; border-radius: var(--r-input); background: #FFFBEB; border: 1px solid #FDE68A; }
.review-reasons-title { display: flex; align-items: center; gap: 6px; font-weight: 600; color: #92400E; margin-bottom: 4px; }
.review-reasons ul { margin: 0; padding-left: 20px; }
.review-field { box-shadow: inset 2px 0 0 #F59E0B; padding-left: 8px; }
.review-btn-done { width: 100%; background: #16A34A; border-color: #16A34A; color: #fff; }
.review-btn-done:hover, .review-btn-done:focus { background: #15803D; border-color: #15803D; color: #fff; }
.review-done { font-size: 12.5px; color: var(--text-muted); }
.review-done form { display: inline; }
.review-reopen { padding: 0; font-size: 12.5px; }
.review-registry { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.cat-chips { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; min-height: 28px; }
.cat-chip { display: inline-flex; align-items: center; gap: 4px; height: 28px; padding: 0 6px 0 10px; border-radius: var(--r-pill); background: var(--primary-soft); color: var(--primary-dark); font-size: 12.5px; font-weight: 500; white-space: nowrap; text-decoration: none; }
.cat-chip:hover { color: var(--primary-dark); box-shadow: inset 0 0 0 1px var(--primary); text-decoration: none; }
.cat-chip .bi { font-size: 14px; }
.cat-chips-clear { font-size: 12.5px; color: var(--text-muted); padding: 0 6px; }
.cat-chips-clear:hover { color: var(--text); }
.cat-chips-count { margin-left: auto; font-size: var(--fs-body); color: var(--text-muted); font-variant-numeric: tabular-nums; }

/* === Column settings (KB-280, mock-up 1A) === */
/* «Колонки» — an icon button at the right end of the chips row (the main row
   of the bar is full at 1440px), right above the table; the panel is a
   POST form of its own, so it sits after the GET form and is placed under the
   button by the script in base.html (top/right against the positioned
   container). «Приховані колонки: … · Налаштувати» — a grey line under the
   table, only when something is hidden. */
.cat-filterbar, .cat-page-head { position: relative; }
.cat-columns-btn { margin-left: auto; width: 28px; height: 28px; min-height: 0; padding: 0; display: inline-flex; align-items: center; justify-content: center; }
.cat-chips-count + .cat-columns-btn { margin-left: 0; }
.cat-columns-btn .bi { font-size: 15px; }
.cat-columns-panel { position: absolute; top: calc(var(--control-h) + 6px); right: 0; z-index: 30; width: 288px; padding: 12px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-card); box-shadow: var(--shadow-md); }
.cat-columns-panel[hidden] { display: none; }
.cat-columns-panel form { gap: 0; }
.cat-columns-title { font-size: var(--fs-card-title); font-weight: 600; margin-bottom: 8px; }
.cat-columns-list { display: flex; flex-direction: column; gap: 2px; margin-bottom: 12px; }
.cat-columns-item { display: flex; align-items: center; gap: 8px; min-height: 28px; font-size: var(--fs-body); cursor: pointer; }
.cat-columns-item .form-check-input { margin: 0; flex: 0 0 auto; }
.cat-columns-item .form-check-input:disabled ~ span { color: var(--text-muted); }
.cat-columns-note { margin-left: auto; font-size: var(--fs-sub); color: var(--text-subtle); }
.cat-columns-actions { display: flex; flex-wrap: wrap; gap: 8px; padding-top: 12px; border-top: 1px solid var(--divider); }
.cat-table-foot { display: flex; justify-content: flex-end; flex-wrap: wrap; gap: 4px; margin-top: 8px; font-size: var(--fs-sub); color: var(--text-muted); }
.cat-table-foot-link { padding: 0; border: 0; background: none; color: var(--primary); font: inherit; cursor: pointer; }
.cat-table-foot-link:hover { color: var(--primary-hover); text-decoration: underline; }
@media (max-width: 600px) { .cat-columns-panel { width: auto; left: 0; } }

/* === Form frame (KB-275, mock-up 2C) === */
/* A card with the footer inside it: «* обов'язкові поля» on the left,
   «Скасувати / Зберегти» on the right. Two widths — narrow (560) and wide
   (760); the wide one can take a 328px side panel (scan, hints). The old
   .cat-card / .cat-form-actions stay for the forms not migrated yet.
   KB-412: the width is a cap for the fields, not for a table — the card is
   never narrower than its content (``min-width`` beats ``max-width``), so a
   formset table wider than 560 / 760 widens its card; a form without a table
   keeps its width. */
.cat-form-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-card); width: 100%; max-width: var(--form-wide); min-width: min-content; }
.cat-form-card--narrow { max-width: var(--form-narrow); }
.cat-form-card--wide { max-width: var(--form-wide); }
.cat-form-card-body { padding: var(--card-pad-lg); display: flex; flex-direction: column; gap: var(--gap-block); }
.cat-form-card-footer { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px var(--card-pad-lg); border-top: 1px solid var(--border); background: var(--table-head-bg); border-radius: 0 0 var(--r-card) var(--r-card); }
.cat-form-card-footer .cat-form-buttons { display: flex; gap: 8px; margin-left: auto; }
.cat-form-required-note { font-size: var(--fs-sub); color: var(--text-muted); }
/* KB-303: a quick-add form in one row above a list (the finance ledgers'
   «+ Додати …» card): fields side by side, wrapping, the button at the end;
   ``--wide`` for a select with long values. The grey explanatory note of a
   form (what saving will do) — .cat-note. */
.cat-add-card { margin-bottom: var(--gap-block); }
.cat-form-row { display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-end; }
.cat-form-row > .cat-field { flex: 1 1 150px; min-width: 130px; }
.cat-form-row > .cat-field--wide { flex: 2 1 200px; min-width: 170px; }
.cat-form-row > .cat-field .form-control, .cat-form-row > .cat-field .ts-wrapper { width: 100%; }
.cat-form-row > .btn { flex: 0 0 auto; }
.cat-note { margin: 0; padding: 10px 12px; border-radius: var(--r-input); background: var(--surface-subtle); color: var(--text-muted); font-size: var(--fs-sub); line-height: var(--lh-sub); }
/* KB-318: a state notice over a card (a cancelled / unstocked supply invoice:
   when, who, why) — the same note in the warning tone, body-sized. */
.cat-note--warn { background: var(--warn-soft); color: var(--warn-strong); font-size: var(--fs-body); line-height: var(--lh-body); }
/* KB-296: sections inside a 2C card body — the body's gap already spaces
   them, the divider stays. A checkbox field in a grid row sits on the line of
   its neighbours' inputs (.cat-field--check), the check itself without
   Bootstrap's outer margin (.cat-check). */
.cat-form-card-body > .cat-section + .cat-section { margin-top: 0; }
.cat-field--check { display: flex; align-items: center; gap: 10px; padding-top: 22px; }
.cat-check { margin: 0; }
.cat-form-layout { display: grid; grid-template-columns: minmax(min-content, var(--form-wide)) var(--form-side); gap: 24px; align-items: start; }
.cat-form-layout > .cat-form-card { max-width: none; }
.cat-form-side { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-card); padding: var(--card-pad); }
@media (max-width: 1100px) { .cat-form-layout { grid-template-columns: minmax(min-content, 1fr); } }
/* KB-281, mock-up 2C: an action inside the field («Запропонувати №» in the
   letter form). The button sits in the field's right end; the field's right
   padding keeps typed text from running under it. */
.cat-input-inset { position: relative; }
.cat-input-inset > .form-control { padding-right: 136px; }
.cat-input-inset-btn { position: absolute; top: 4px; right: 4px; bottom: 4px; display: inline-flex; align-items: center; padding: 0 8px; border: 0; border-radius: var(--r-badge); background: var(--primary-soft); color: var(--primary); font: inherit; font-size: var(--fs-sub); font-weight: 500; white-space: nowrap; cursor: pointer; }
.cat-input-inset-btn:hover { color: var(--primary-dark); }
.cat-input-inset-btn:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--primary-ring); }
@media (max-width: 600px) {
    .cat-form-card-footer { flex-direction: column-reverse; align-items: stretch; }
    .cat-form-card-footer .cat-form-buttons { flex-direction: column-reverse; margin-left: 0; }
}

@media (max-width: 600px) {
    .cat-grid { grid-template-columns: minmax(0, 1fr); }
    .cat-form-actions { flex-direction: column-reverse; }
    .cat-form-actions > * { width: 100%; }
    .cat-bank-header { display: none; }
    .cat-bank-row { grid-template-columns: 1fr 1fr; }
}

/* === TomSelect === */
.ts-wrapper { min-width: 0; }
.ts-control { min-height: var(--control-h); padding: 0 var(--control-px); display: flex; align-items: center; font-size: var(--fs-body); line-height: var(--lh-body); border-radius: var(--r-input); border: 1px solid var(--border-strong); background: var(--surface); color: var(--text); box-shadow: none; }
.ts-wrapper.focus .ts-control, .ts-control:focus-within { border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-ring); outline: none; }
.ts-control input::placeholder { color: var(--text-subtle); }
.ts-dropdown { border-radius: var(--r-input); border: 1px solid var(--border); box-shadow: var(--shadow-md); font-size: var(--fs-body); margin-top: 4px; z-index: 1100 !important; }
.ts-dropdown .option { padding: 7px 10px; }
.ts-dropdown .option.active { background: var(--primary-soft); color: var(--primary-dark); }
.ts-dropdown .no-results { color: var(--text-muted); padding: 7px 10px; }
.ts-dropdown-content { max-height: 60vh !important; overflow-y: auto !important; }
/* KB-362: a dropdown moved out of a table frame (``tomSelectDetach`` in
   base.html) gets the control's width from TomSelect — 150–280px, too narrow
   for product names. It gets a readable minimum, never wider than the
   viewport; an option is one line with an ellipsis, the full name in its
   ``title``.  KB-521: any clipped select is detached now, a wide form field
   too — ``tomSelectDetach`` lifts this cap inline to the field's own width on
   open, so a dropdown is never narrower than its field. */
.ts-dropdown.ts-dropdown--detached { min-width: 320px; max-width: min(560px, 100vw - 32px); }
.ts-dropdown--detached .option { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* KB-294: одиночний TomSelect — завжди один рядок висотою --control-h.
   Довге обране значення обрізається трьома крапками і не заходить під
   стрілку: правий відступ під стрілку (і під хрестик ``clear_button``) дає
   сам TomSelect через ``--ts-pr-caret`` / ``--ts-pr-clear-button``, а
   значення тут лише вміє стискатися (``min-width: 0``). Без ``nowrap`` його
   пошуковий інпут (``min-width: 7rem`` у TomSelect) переносився на другий
   рядок і поле росло. Інпут забирає місце лише у фокусі, коли в ньому
   друкують. Повну назву показує ``title`` контрола (``tsSingleSync`` у
   base.html). Множинні (``.multi``) не зачеплені. */
/* KB-409: чому KB-294 не спрацьовував. (1) Відступ під стрілку TomSelect
   2.3.1 рахує як max() від ``--ts-pr-min`` і суми ``--ts-pr-clear-button``
   + ``--ts-pr-caret`` (``!important``), а ``--ts-pr-clear-button`` у нього —
   безрозмірний ``0``: ``0 + 2.25rem`` у calc() недійсне, і ``padding-right``
   ставав 0 — значення йшло під стрілку в кожному полі без хрестика. Нулі з
   одиницею роблять вираз дійсним. (2) Тема bootstrap5 робить ``.item``
   flex-контейнером, а ``text-overflow`` на flex-контейнері не діє — текст
   обрізався без трикрапки; ``display: block`` її повертає. (3) Картка
   ``min-width: min-content`` (KB-412) росла до повної довжини обраної назви
   (``nowrap``), тож обрізати не було чого: ``contain: inline-size`` знімає з
   одиночного селекта внутрішню ширину, як ``.cat-grid`` — з полів. */
:root { --ts-pr-clear-button: 0px; --ts-pr-caret: 0px; }
.ts-wrapper.single { contain: inline-size; }
.ts-wrapper.single .ts-control { flex-wrap: nowrap; height: var(--control-h); }
.ts-wrapper.single .ts-control > .item { display: block; flex: 0 1 auto; min-width: 0; max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ts-wrapper.single .ts-control > input { flex: 1 1 0; min-width: 0 !important; }
.ts-wrapper.single.focus .ts-control > input { flex: 1 0 4rem; min-width: 4rem !important; }
/* KB-294: хрестик «Скинути» у фільтрах — лише коли обрано не порожнє
   значення («Усі …» — це ``value=""``, і TomSelect теж рахує його елементом). */
.ts-wrapper.single.ts-value-empty .clear-button { display: none; }
/* KB-333: обране значення — хрестик видно завжди, без наведення (у
   TomSelect 2.3.1 він ``opacity: 0`` і з'являється лише на hover / focus, і
   на білому фоні людина не бачила, що фільтр можна скинути). Колір —
   ``--text-muted``, на hover / focus — ``--text``. ``--ts-pr-clear-button`` —
   місце під хрестик у правому відступі контрола, щоб обрізане трикрапкою
   значення (KB-294) не налазило ні на хрестик, ні на стрілку. */
.ts-wrapper.plugin-clear_button { --ts-pr-clear-button: 1.5rem; }
.ts-wrapper.plugin-clear_button.has-items .clear-button { opacity: 1; color: var(--text-muted); font-size: 1.125em; line-height: 1; padding: 0 2px; transition: color 0.15s; }
.ts-wrapper.plugin-clear_button.has-items .clear-button:hover,
.ts-wrapper.plugin-clear_button.focus .clear-button,
.ts-wrapper.plugin-clear_button:hover .clear-button { color: var(--text); }

/* === Pagination === */
.pagination { display: flex; justify-content: center; align-items: center; gap: 16px; margin-top: 24px; padding: 12px; font-size: 14px; }
.pagination a { color: var(--primary); text-decoration: none; padding: 7px 14px; border: 1px solid var(--border); border-radius: var(--r-input); transition: background 0.1s, border-color 0.1s; }
.pagination a:hover { background: var(--surface-subtle); border-color: var(--border-strong); text-decoration: none; }
.pagination .disabled { color: var(--text-subtle); padding: 7px 14px; }

@media (max-width: 768px) {
    main.container { padding: 20px 16px; }
}

/* === Sign-in page (KB-345) === */
/* The one page without the sidebar: a card in the middle of the screen, the
   sidebar's brand on top, the fields and the button on the global control
   rules (--control-h, the primary colour), a failed sign-in as the shared
   .alert. */
.cat-auth { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 24px 16px; background: var(--surface-subtle); }
.cat-auth-card { width: 100%; max-width: 360px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-card); padding: var(--card-pad-lg); }
.cat-auth-card .app-brand { margin-bottom: 20px; }
.cat-auth-title { margin: 0 0 16px; font-size: var(--fs-card-title); }
.cat-auth-form { display: flex; flex-direction: column; gap: 14px; }
.cat-auth-form .btn { width: 100%; }
