/* Two themes, switched by data-theme on <html> (rendered server-side from the
   user's saved preference, so there's no flash of the wrong palette).
   The bucket/status palette (BUCKET_COLORS / STATUS_COLORS in app.js) is
   shared by both themes — charts and status chips keep the agreed colours;
   only the chrome around them changes. */
:root,
:root[data-theme="dark"] {
    color-scheme: dark; /* native scrollbars, date pickers and selects go dark */

    --bg: #0f1420;
    --panel: #1a2130;
    --panel-2: #212a3b;   /* nested blocks: help boxes, toolbars */
    --input-bg: #131a27;
    --ink: #e6ecf5;
    --ink-soft: #c3ccdb;
    --muted: #93a1b5;
    --line: #2c3648;

    --brand: #6fa8ff;       /* accent TEXT: headings, links, summaries */
    --accent-solid: #3d6fd4;/* filled accents: active seg button, badges */
    --bar: #1c2740;         /* top bar / drill header background */
    --bar-ink: var(--ink);  /* text on the bar */
    --bar-muted: var(--muted);

    /* Semantic status colours.
       --active/--queue/--blocked must stay in sync with BUCKET_COLORS in
       app.js — the doughnut and the status table read them.
       For TEXT on dark we use the lighter *-text variants below. */
    --active: #3b82f6;
    --queue: #e0a92b;
    --blocked: #dc2626;
    --unstarted: #9aa5b5;
    --done: #22a35d;
    --success-text: #4ec27a;
    --danger-text: #f2686d;
    /* Epic badge text. The #8b5cf6 of the status palette is only 4.2:1 on a
       light panel, so it gets the same treatment as the two above: a per-theme
       variant that clears 4.5:1 either way. */
    --epic-text: #a78bfa;

    --shadow: 0 2px 6px rgba(0, 0, 0, .35), 0 1px 2px rgba(0, 0, 0, .25);
    --hover: rgba(255, 255, 255, .05);
    /* Заливка текущего периода на таймлайне планирования. Своя переменная, а не
       --hover: --hover обязан оставаться реакцией на курсор, и подсветка "здесь
       мы сейчас" не должна выглядеть как наведение. Тон брендовый (--brand),
       поэтому задан как rgba того же цвета — альфа подобрана так, чтобы
       линии сетки под ней остались видны. */
    --now-tint: rgba(111, 168, 255, .12);
    /* Внутренность выбранного отрезка в календаре. Тоже своя, и по той же
       причине: --hover это курсор, --now-tint это "сегодня", а здесь третье
       утверждение — "выбрано". Альфа заметно выше, чем у --now-tint: полоса
       выбора обязана читаться сама, а не только рядом с закрашенными концами. */
    --dp-band: rgba(111, 168, 255, .22);
    /* Выходные на диаграмме Ганта. Четвёртое утверждение того же ряда, и тоже со
       своей переменной: --hover это курсор, --now-tint это "сегодня", --dp-band
       это "выбрано", а здесь — "в этот день не работают". Тон НЕЙТРАЛЬНЫЙ, а не
       брендовый: выходные не событие и не выбор, они фон календаря, и брендовый
       оттенок конкурировал бы с полосой "сейчас". На тёмной теме это затемнение
       (чёрным), потому что панель и без того тёмная и осветление читалось бы как
       подсветка. */
    --weekend-tint: rgba(0, 0, 0, .22);
    /* Чётные строки таблиц («зебра»). Пятое утверждение того же ряда и тоже со
       своей переменной: --hover это курсор, --now-tint это «сегодня», --dp-band
       это «выбрано», --weekend-tint это «не работают», а здесь — «это одна
       строка». Заметно СЛАБЕЕ --hover (.028 против .05): зебра работает
       периферийным зрением и не должна выглядеть как наведение. */
    --zebra: rgba(255, 255, 255, .028);
}

:root[data-theme="light"] {
    color-scheme: light;

    --bg: #f4f6fa;
    --panel: #ffffff;
    --panel-2: #f7f9fd;
    --input-bg: #ffffff;
    --ink: #1c2430;
    --ink-soft: #3a4453;
    --muted: #6b7686;
    --line: #e2e7ef;

    --brand: #2f5597;
    --accent-solid: #2f5597;
    --bar: #2f5597;          /* solid brand bar, as in the original design */
    --bar-ink: #ffffff;
    --bar-muted: rgba(255, 255, 255, .85);

    /* Status/bucket colours stay identical; only the text-on-light variants
       are darkened so they stay legible against a white panel. */
    --success-text: #2f7d4a;
    --danger-text: #c02630;
    --epic-text: #6d28d9;

    --shadow: 0 1px 3px rgba(20, 30, 50, .08), 0 1px 2px rgba(20, 30, 50, .04);
    --hover: #f2f5fb;
    /* Светлая тема: тот же брендовый тон (--brand здесь тёмно-синий), альфа ниже
       — на белом фоне он темнит заметно сильнее, чем на тёмной панели. */
    --now-tint: rgba(47, 85, 151, .09);
    --dp-band: rgba(47, 85, 151, .16);
    /* На светлой панели затемнение слабее: те же .22 чёрным по белому дают почти
       серую колонку, и сетка под ней перестаёт читаться. */
    --weekend-tint: rgba(0, 0, 0, .07);
    /* На белом та же альфа чёрным читается сильнее, чем белым на тёмном, —
       поэтому здесь ещё слабее. */
    --zebra: rgba(0, 0, 0, .022);
}

* { box-sizing: border-box; }
html, body { margin: 0; }
body {
    font-family: -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
    background: var(--bg);
    color: var(--ink);
    font-size: 14px;
    line-height: 1.4;
}

/* Wraps onto a second line instead of pushing the page wider: the bar holds a
   title, the snapshot line and up to three buttons, and below ~430px those
   cannot fit on one row. Wrapping costs nothing at normal widths (there is
   nothing to wrap) and keeps the page free of a horizontal scrollbar, which on
   a sticky bar would otherwise slide the header away from the content. */
.topbar {
    display: flex; justify-content: space-between; align-items: center;
    flex-wrap: wrap; row-gap: 8px;
    background: var(--bar); color: var(--bar-ink);
    padding: 10px 18px; position: sticky; top: 0; z-index: 30;
    box-shadow: var(--shadow); border-bottom: 1px solid var(--line);
}
.topbar-left { display: flex; align-items: center; gap: 12px; min-width: 0; }
.topbar-left h1 { overflow-wrap: anywhere; }

/* burger + side menu */
.burger {
    background: transparent; border: none; cursor: pointer; padding: 6px 4px;
    display: flex; flex-direction: column; gap: 4px; width: 30px;
}
.burger span { display: block; height: 2px; background: var(--bar-ink); border-radius: 2px; transition: .18s; }
.burger:hover span { opacity: .7; }
.burger.active span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.burger.active span:nth-child(2) { opacity: 0; }
.burger.active span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

.menu-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,.5); z-index: 70; animation: fade-in .15s ease; }
.menu-backdrop.hidden { display: none; }
.sidemenu {
    position: fixed; top: 0; left: 0; bottom: 0; width: min(280px, 84vw);
    background: var(--panel); border-right: 1px solid var(--line); z-index: 71;
    display: flex; flex-direction: column; padding: 14px 0;
    box-shadow: 6px 0 24px rgba(0,0,0,.45);
    animation: slide-in-left .18s ease;
}
.sidemenu.hidden { display: none; }
@keyframes slide-in-left { from { transform: translateX(-24px); opacity: .5; } to { transform: none; opacity: 1; } }
.sidemenu-user { padding: 6px 18px 14px; border-bottom: 1px solid var(--line); margin-bottom: 8px; }
.sidemenu-user .sm-name { font-weight: 700; font-size: 14px; }
.sidemenu-user .sm-role { font-size: 12px; color: var(--muted); margin-top: 2px; }
.sm-item {
    display: block; padding: 10px 18px; color: var(--ink-soft); text-decoration: none;
    font-size: 14px; font-weight: 600; border-left: 3px solid transparent;
}
.sm-item:hover { background: var(--hover); color: var(--ink); }
.sm-item.active { color: var(--brand); border-left-color: var(--brand); background: var(--hover); }
/* Заголовок раздела меню («Финансы») — подпись, не ссылка; страницы раздела под ним с отступом. */
.sm-section {
    padding: 12px 18px 4px; font-size: 11px; font-weight: 700; letter-spacing: .06em;
    text-transform: uppercase; color: var(--muted);
}
.sm-item.sm-sub { padding-left: 30px; }
.sidemenu-foot { margin-top: auto; border-top: 1px solid var(--line); padding-top: 8px; }
.sm-logout { color: var(--muted); font-weight: 500; }
.topbar h1 { font-size: 18px; margin: 0; font-weight: 700; letter-spacing: .3px; }
.filter-id { font-size: 12px; color: var(--bar-muted); }
.topbar-right { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; row-gap: 8px; min-width: 0; }
.snapshot { font-size: 12px; color: var(--bar-muted); }
.export { display: flex; gap: 8px; flex-wrap: wrap; }

.btn {
    background: var(--panel-2); color: var(--ink);
    border: 1px solid var(--line);
    border-radius: 6px; padding: 6px 12px; font-size: 13px; cursor: pointer; font-weight: 600;
}
.btn:hover { background: var(--hover); border-color: var(--muted); }
.btn.ghost { background: transparent; color: var(--brand); border: 1px solid var(--line); }
.btn.ghost:hover { background: var(--hover); }
/* Buttons sitting on the coloured/dark bar need to read against it, not
   against the page background. */
.topbar .btn {
    background: rgba(255, 255, 255, .12); color: var(--bar-ink);
    border-color: rgba(255, 255, 255, .25);
}
.topbar .btn:hover { background: rgba(255, 255, 255, .22); border-color: rgba(255, 255, 255, .4); }
/* The planning view switcher (#board-mode) moved into the top bar — same
   reasoning as .topbar .btn just above: .seg-btn is styled for a plain panel
   background, not the coloured/dark bar. */
.topbar .seg { border-color: rgba(255, 255, 255, .25); }
.topbar .seg-btn { background: rgba(255, 255, 255, .12); color: var(--bar-ink); }
.topbar .seg-btn + .seg-btn { border-left-color: rgba(255, 255, 255, .25); }
.topbar .seg-btn:not(.active):hover { background: rgba(255, 255, 255, .22); color: var(--bar-ink); }
.topbar .seg-btn.active { background: var(--accent-solid); color: #fff; }

.banner {
    background: #3a2f12; border: 1px solid #6b5520; color: #f2d494;
    margin: 12px 18px; padding: 10px 14px; border-radius: 8px; font-size: 13px;
}
.banner.hidden { display: none; }
.banner code { background: rgba(255, 255, 255, .12); padding: 1px 5px; border-radius: 4px; }

.filters {
    background: var(--panel); margin: 12px 18px; padding: 12px 14px;
    border-radius: 10px; box-shadow: var(--shadow); border: 1px solid var(--line);
}
.filter-row { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.filter-row.second { margin-top: 10px; }
.filter-row.quick { padding-bottom: 10px; margin-bottom: 10px; border-bottom: 1px solid var(--line); }

/* quick filters: LK&CRM / UAPI / WEB (project groups, mutually exclusive)
   and Frontend / Backend (label toggles, independent). Active state always
   reflects the real filter state — see updateQuickFilterButtons() in app.js. */
.qf-label { font-size: 12px; color: var(--muted); margin-right: 2px; }
.quick-group { display: flex; gap: 6px; }
.quick-sep { width: 1px; height: 18px; background: var(--line); }
.qf-btn {
    border: 1px solid var(--line); background: var(--input-bg); color: var(--ink-soft);
    border-radius: 999px; padding: 5px 13px; font-size: 12.5px; font-weight: 600; cursor: pointer;
}
.qf-btn:hover { border-color: #3b485e; background: var(--hover); }
.qf-btn.active { background: var(--accent-solid); border-color: var(--accent-solid); color: #fff; }
.filter-row input[type=search] {
    flex: 1; min-width: 240px; padding: 7px 10px; border: 1px solid var(--line);
    border-radius: 6px; font-size: 14px;
}
label.inline { font-size: 13px; color: var(--muted); display: flex; align-items: center; gap: 6px; }

/* form controls need explicit colours on dark */
input, select, textarea, button { font-family: inherit; }
input[type=search], input[type=number], input[type=date], input[type=text], select, textarea {
    background: var(--input-bg); color: var(--ink); border: 1px solid var(--line);
    border-radius: 6px; padding: 6px 8px; font-size: 13px;
}
input::placeholder { color: #77839a; }
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--accent-solid); }

/* custom multiselect */
.ms { position: relative; }
.ms-btn {
    border: 1px solid var(--line); background: var(--input-bg); color: var(--ink);
    border-radius: 6px;
    padding: 7px 10px; font-size: 13px; cursor: pointer; min-width: 130px;
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
}
.ms-btn:hover { border-color: #3b485e; }
.ms-btn .cnt { background: var(--accent-solid); color: #fff; border-radius: 10px; font-size: 11px; padding: 0 6px; }
.ms-pop {
    position: absolute; top: 105%; left: 0; z-index: 20; background: var(--panel-2);
    border: 1px solid var(--line); border-radius: 8px; box-shadow: var(--shadow);
    max-height: 280px; overflow: auto; min-width: 220px; padding: 6px;
}
.ms-pop.hidden { display: none; }
.ms-pop label { display: flex; gap: 8px; align-items: center; padding: 4px 6px; border-radius: 5px; font-size: 13px; cursor: pointer; }
.ms-pop label:hover { background: var(--hover); }
.ms-search { width: 100%; margin-bottom: 6px; padding: 5px 7px; border: 1px solid var(--line); border-radius: 5px; }

/* --- календарь выбора отрезка (daterange.js) --------------------------------
   Кнопка и поповер намеренно повторяют .ms-btn / .ms-pop: это две разные
   выпадающие штуки в одной строке фильтров, и выглядеть они должны как одно
   семейство, а не как два подхода. */
.dp { position: relative; }
.dp-btn {
    border: 1px solid var(--line); background: var(--input-bg); color: var(--ink);
    border-radius: 6px; padding: 7px 10px; font-size: 13px; cursor: pointer;
    font-variant-numeric: tabular-nums; white-space: nowrap;
}
.dp-btn:hover { border-color: #3b485e; }
.dp-pop {
    position: absolute; top: 105%; left: 0; z-index: 20; background: var(--panel-2);
    border: 1px solid var(--line); border-radius: 8px; box-shadow: var(--shadow);
    padding: 10px 12px 8px;
}
.dp-pop.hidden { display: none; }
/* Привязка к правому краю кнопки — ставит placePop(), когда у левого не влезает. */
.dp-pop-right { left: auto; right: 0; }
/* Стрелки прижаты к краям, между ними — календарь: месяцы подписаны в самих
   сетках, поэтому в шапке нет и не должно быть второго заголовка. */
.dp-head { display: flex; justify-content: space-between; margin-bottom: 2px; }
.dp-nav {
    background: none; border: none; color: var(--ink-soft); cursor: pointer;
    font-size: 18px; line-height: 1; padding: 2px 8px; border-radius: 5px;
}
.dp-nav:hover { background: var(--hover); color: var(--ink); }
.dp-grids { display: flex; gap: 18px; }
.dp-month {
    text-align: center; font-size: 13px; color: var(--ink); margin-bottom: 6px;
}
/* Семь равных колонок, одна сетка на дни недели и на сами дни: иначе шапка
   «пн вт ср» разъезжается с числами под ней. */
.dp-week, .dp-days { display: grid; grid-template-columns: repeat(7, 30px); }
/* --ink-soft, а не --muted: на светлой панели --muted даёт этому 11px тексту
   4.37:1 — ниже порога. Та же ловушка и то же лечение, что у названия под
   стори/эпиком в таблице сравнения. Замерено, не на глаз. */
.dp-dow {
    text-align: center; font-size: 11px; color: var(--ink-soft); padding-bottom: 2px;
}
.dp-day {
    height: 28px; border: none; background: none; color: var(--ink);
    font: inherit; font-size: 12px; font-variant-numeric: tabular-nums;
    cursor: pointer; border-radius: 6px;
}
.dp-day:hover { background: var(--hover); }
/* Внутренность отрезка — тон, концы — сплошной акцент: у выбора должно быть
   видно, где он начинается и где кончается, а не только что он есть.
   Сама полоса даёт к панели всего 1.3–1.5:1 — как и любой тон выделения; это
   допустимо, потому что она ДУБЛИРУЕТ: концы залиты сплошным (4.75:1 тёмная /
   7.32:1 светлая), отрезок написан на кнопке словами и посчитан в подсказке.
   Тот же довод, что у 4px-полосы проекта в таблице сравнения. */
.dp-day.dp-in { background: var(--dp-band); border-radius: 0; }
.dp-day.dp-edge { background: var(--accent-solid); color: #fff; }
/* Сегодня отмечено точкой под числом, а не рамкой: рамка спорила бы с концом
   отрезка, а точка читается и внутри выделения. */
.dp-day.dp-today { position: relative; font-weight: 700; }
.dp-day.dp-today::after {
    content: ''; position: absolute; left: 50%; bottom: 3px; width: 3px; height: 3px;
    margin-left: -1.5px; border-radius: 50%; background: currentColor;
}
.dp-hint {
    margin-top: 6px; font-size: 11px; color: var(--ink-soft); min-height: 14px;
    white-space: nowrap;
}
/* Гранулярность «месяц» (makeMonthRangePicker): вместо дней — 12 месяцев
   года в 3 колонки; заголовок сетки — год, тем же правилом .dp-month. Ячейки
   шире дневных, потому что в них слово, а не число. Недоступные месяцы (вне
   данных страницы) остаются видны, но приглушены и не кликаются. */
.dp-months { display: grid; grid-template-columns: repeat(3, 64px); gap: 2px; }
.dp-day.dp-mon { height: 30px; }
.dp-day.dp-off { color: var(--muted); opacity: .45; cursor: default; }
.dp-day.dp-off:hover { background: none; }

/* Gap under the sticky topbar. On the metrics page, .filters sits BEFORE
   <main> and already supplies this gap via its own top margin — so <main>'s
   top padding is cancelled there to avoid doubling it up. On pages with no
   .filters block (e.g. users.php), <main> is the first thing after the
   header/side-menu, so it needs the padding itself. */
main { padding: 12px 18px 40px; }
.filters ~ main { padding-top: 0; }
.panel {
    background: var(--panel); border-radius: 10px; box-shadow: var(--shadow);
    border: 1px solid var(--line);
    padding: 14px 16px; margin-bottom: 14px;
}
.panel h2 { margin: 0 0 10px; font-size: 14px; color: var(--brand); font-weight: 700; }
.panel h2 small { color: var(--muted); font-weight: 400; font-size: 12px; }
/* The grid's gap already spaces the panels. A panel's own bottom margin adds
   to it, so a second row sat 28px below the first while the columns were 14px
   apart — the grid carries the trailing margin instead. */
/* minmax(0, 1fr), not 1fr: a grid item's min-width is `auto`, so `1fr` is only
   a *starting* size — a child wider than the track (a nowrap table) pushed the
   track out, and with it the panel, the grid and the whole page, which is where
   the dashboard's horizontal scrollbar came from. minmax(0, …) lets the track
   stay put and leaves the overflow to the child's own scroll container.
   Same reason `.plan-main` below is written that way. */
.grid-2 { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; margin-bottom: 14px; }
.grid-2 > .panel { margin-bottom: 0; min-width: 0; }
@media (max-width: 900px) { .grid-2 { grid-template-columns: minmax(0, 1fr); } }

/* Flex rather than a fixed column count: the number of cards varies (the cost
   card only appears once there is cost to show) and a hardcoded grid pushed the
   extra one onto a second row. */
.cards { display: flex; flex-wrap: nowrap; gap: 12px; margin: 12px 0; }
.card {
    flex: 1 1 0; min-width: 0;
    background: var(--panel); border-radius: 10px; box-shadow: var(--shadow);
    border: 1px solid var(--line);
    padding: 12px 14px; text-align: center;
}
/* Money runs to eight digits where the others hold two or three, so it gets a
   bigger share of the row and a smaller face — the figure must never wrap. */
.card-cost { flex: 1.9 1 0; }
.card-cost .n { font-size: 21px; }
@media (max-width: 900px) {
    .cards { flex-wrap: wrap; }
    .card, .card-cost { flex: 1 1 28%; }
}
.card .n { font-size: 26px; font-weight: 800; color: var(--brand); line-height: 1; white-space: nowrap; }
/* `anywhere`, because the longest label («ЗАБЛОКИРОВАННЫЕ») is a single word
   with no break opportunity: on a narrow screen the cards shrink below it and
   an unbreakable word pushed the whole row — and with it the page — wider. */
.card .l { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .4px; margin-top: 6px; overflow-wrap: anywhere; }
.card.warn .n { color: var(--danger-text); }
.card.ok .n { color: var(--success-text); }

.chart-wrap { position: relative; height: 260px; }
.chart-wrap.tall { height: 340px; }

/* A footnote under a widget — states what the widget does NOT show (a clipped
   window), so a bounded chart is never read as the whole history. */
.panel-note { margin-top: 8px; font-size: 11px; color: var(--muted); line-height: 1.5; }

/* Per-bucket figures under the flow doughnut. Reuses table.top, minus the
   sortable-header cursor it carries for the issues tables. */
.flow-stats { margin-top: 10px; }
/* table.top gives its headers the sortable-column cursor; nothing sorts here,
   so it has to be beaten on specificity, not just declared later. */
table.top.flow-stats th { cursor: default; }
.flow-stats td:first-child { display: flex; align-items: center; gap: 7px; }
.flow-stats tr.clickable { cursor: pointer; }
.flow-stats tr.clickable:hover td { background: var(--hover); }
.flow-stats tr:last-child td { border-bottom: none; }
.fs-dot { width: 9px; height: 9px; border-radius: 2px; flex: 0 0 auto; }

/* Виджет «Статусы задач» — пончик и таблица по статусам стоят рядом, а не друг
   под другом, пока для этого есть место (просьба владельца).
   minmax(0, …) обязателен у ОБЕИХ колонок — у таблицы `white-space: nowrap`, и
   с `1fr` она распёрла бы трек, панель и страницу целиком; ровно та причина, по
   которой так написан .grid-2 выше.
   .flow-panel — контейнер для container query ниже: секция теперь стоит и на
   всю ширину, и половиной в .grid-2 (просьба владельца, см. CLAUDE.md §7a), а
   `@media` видит только ширину ОКНА — в узкой половине широкого окна он не
   срабатывал бы, и пончик с таблицей просто сжимались бы со скроллом внутри
   .table-scroll вместо того, чтобы встать друг под другом. */
.flow-panel { container-type: inline-size; }
/* 460px под пончик: у него легенда справа (она забирает ~130px), и на более
   узкой колонке от самого круга оставалось меньше 250px — заметно мельче, чем
   было в половинной панели до этой правки. Таблице остатка хватает: её ширина
   ограничена 640px (см. ниже), и растягиваться по треку она не пытается. */
.flow-split {
    display: grid;
    grid-template-columns: minmax(0, 460px) minmax(0, 1fr);
    gap: 18px; align-items: start;
}
/* Пончику нужна своя высота: рядом с таблицей на 20 статусов он иначе
   растягивался бы по её высоте и занимал пол-экрана.
   overflow: hidden обязателен, и это не косметика. Chart.js держит на канвасе
   ПИКСЕЛЬНУЮ ширину и пересчитывает её сам; пока он этого не сделал (например
   после того, как окно расширили и колонка снова стала узкой), канвас остаётся
   шире трека и распирает панель со страницей — тот самый горизонтальный скролл
   из §9 CLAUDE.md. Обрезка держит раскладку, а ближайшая перерисовка графика
   ставит канвасу верный размер. */
.flow-split .chart-wrap { height: 300px; overflow: hidden; }
/* Ширина таблицы ограничена, и это про читаемость, а не про аккуратность.
   `table.top` — width: 100%, поэтому на 2К-мониторе пять колонок растягивались
   на ~2000px: статус занимал 631px, и от чипса до его же суммы глаз шёл через
   полэкрана пустоты. Замерено на 2545px вьюпорта.
   table-layout: fixed — чтобы ширины ниже действительно соблюдались, а не
   пересчитывались браузером по самому длинному значению; при контейнере уже
   суммы колонок он же пропорционально их сожмёт, а .table-scroll вокруг
   остаётся страховкой. */
.flow-split .flow-stats {
    margin-top: 0;
    table-layout: fixed;
    max-width: 640px;
}
/* Статусу — под самый длинный чипс (REQUIRED REWORK), числовым — под самое
   длинное значение вида «3m 25d 19h», доле — под «100.0%». */
.flow-split .flow-stats th:nth-child(1), .flow-split .flow-stats td:nth-child(1) { width: 190px; }
.flow-split .flow-stats th:nth-child(2), .flow-split .flow-stats td:nth-child(2) { width: 130px; }
.flow-split .flow-stats th:nth-child(3), .flow-split .flow-stats td:nth-child(3) { width: 110px; }
.flow-split .flow-stats th:nth-child(4), .flow-split .flow-stats td:nth-child(4) { width: 110px; }
.flow-split .flow-stats th:nth-child(5), .flow-split .flow-stats td:nth-child(5) { width: 80px; }
/* Порог считан, а не взят круглым: 460 (график) + 18 (gap) + 640 (таблица) =
   1118px — ниже этого таблица уже не влезает в свой трек и получает
   горизонтальный скролл внутри .table-scroll. Складываем в одну колонку раньше,
   чем до этого дойдёт, — полная ширина таблице полезнее внутренней прокрутки.
   Container query (не @media): секция теперь бывает и в половину окна
   (.grid-2), и во всю ширину, и решать нужно по месту, которое реально есть
   ЭТОЙ секции, а не по ширине окна — .flow-panel выше объявляет её контейнером
   по инлайн-оси. */
@container (max-width: 1150px) {
    .flow-split { grid-template-columns: minmax(0, 1fr); }
}

table.issues, table.top { width: 100%; border-collapse: collapse; font-size: 13px; }
table.top th, table.top td, .issues th, .issues td {
    padding: 6px 8px; border-bottom: 1px solid var(--line); text-align: left; white-space: nowrap;
}
table.top th, .issues th { color: var(--muted); font-weight: 600; font-size: 12px; text-transform: uppercase; cursor: pointer; user-select: none; }
/* «Название» is always the 2nd column across every renderTop() config
   (agingWip/agingBlocked/agingQueue/agingUnstarted/reworkAntitop). Clipped by
   CSS ellipsis instead of a fixed character count (used to be
   trunc(r.summary, 34) in app.js) — that cut names short even in a column
   with plenty of room to spare, and left the rest of the cell empty. 360px is
   a cap the column claims outright in table.top's auto layout (it does not
   shrink back down for a short name), so it's picked as "comfortably fits
   most titles" rather than "as generous as possible" — wider would starve the
   other, fixed-content columns next to it and force .table-scroll more often
   than the fuller titles are worth. */
table.top td:nth-child(2) { max-width: 360px; overflow: hidden; text-overflow: ellipsis; }
.issues th.sorted-asc::after { content: " ▲"; }
.issues th.sorted-desc::after { content: " ▼"; }
.issues td.summary { white-space: normal; max-width: 340px; }
.issues tr:hover td { background: var(--hover); }
.table-scroll { overflow-x: auto; }

/* Sticky header for the long issues table.
   It cannot pin to the viewport: a wrapper with overflow-x:auto has its
   overflow-y computed to auto too (and overflow-y:clip is coerced to hidden
   next to it), which makes the wrapper the scrollport and swallows the sticky.
   So the wrapper becomes the scroll region and the header pins to the top of
   it — with the region nearly viewport-tall, that reads the same while
   scrolling the rows. Scoped to this one table so the short ones keep flowing
   with the page. */
.table-sticky { max-height: calc(100vh - var(--topbar-h, 56px) - 110px); overflow: auto; }
.table-sticky thead th {
    position: sticky; top: 0; z-index: 3;
    background: var(--panel);
    /* A collapsed border can drop out while a sticky row scrolls; an inset
       shadow is painted with the cell and always survives. */
    box-shadow: inset 0 -1px 0 var(--line);
}
/* All table data and headers are left-aligned (owner request). */
.issues th, .issues td, table.top th, table.top td { text-align: left; }
.issues td.num, table.top td.num { font-variant-numeric: tabular-nums; }
.issues th[title] { text-decoration: underline dotted; text-underline-offset: 3px; }

a.key { color: var(--brand); text-decoration: none; font-weight: 600; }
a.key:hover { text-decoration: underline; }

.tag { display: inline-block; background: rgba(255, 255, 255, .1); color: var(--ink-soft); border-radius: 4px; padding: 0 6px; font-size: 11px; margin: 1px; }

/* --- status badge (.st) --------------------------------------------------
   One badge for every status, everywhere: tables, cards, panel headers.
   A soft tint of the status colour with text in a readable shade of the SAME
   colour, uppercase. Replaced a solid fill with white text, which read as an
   alert on every row and turned a column of statuses into noise; it also
   never cleared 4.5:1 on most of the palette (Delivered 3.25, In QA 3.68,
   Required rework 2.13 — the fill was the constraint, not the palette).
   Both themes' values arrive inline as custom properties from statusTone()
   in ui.js, and CSS picks the pair — so a theme switch costs no re-render. */
.st {
    display: inline-block; padding: 2px 7px; border-radius: 6px;
    border: 1px solid transparent; white-space: nowrap;
    font-size: 10px; font-weight: 700; letter-spacing: .04em; line-height: 1.5;
    text-transform: uppercase;
}
[data-theme="dark"] .st  { color: var(--st-fg-d); background: var(--st-bg-d); border-color: var(--st-bd-d); }
[data-theme="light"] .st { color: var(--st-fg-l); background: var(--st-bg-l); border-color: var(--st-bd-l); }
/* On the panel header the badge sits on --bar, and there the tint cannot work:
   the backdrop is already mid-luminance (solid blue in the light theme), so a
   translucent tint of the status colour drags it towards the text, which is
   the same hue — measured, In Review landed at 2.51:1. So on the bar the fill
   is dropped entirely: the colour moves to the border and the text to
   --bar-ink, which makes contrast independent of which status it is
   (12.5:1 dark / 7.3:1 light). Same reasoning as everything else on the bar,
   CLAUDE.md §6. */
.drill-head .st {
    background: transparent; color: var(--bar-ink); border-color: var(--st-dot);
}

/* --- status breakdown: list form (panels) --------------------------------
   Fixed columns so the rows line up and the eye runs down them, instead of a
   wrapped row of equal-sized chips where an hour looked like a year. */
.sbl { display: flex; flex-direction: column; gap: 3px; }
.sbl-row {
    display: grid; grid-template-columns: 8px minmax(0, 1fr) 96px 74px;
    gap: 8px; align-items: center; font-size: 11px;
}
.sbl-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--st-dot); }
.sbl-name {
    color: var(--ink-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    font-size: 10px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
}
.sbl-bar { height: 5px; border-radius: 3px; background: var(--line); overflow: hidden; }
.sbl-bar > i { display: block; height: 100%; background: var(--st-dot); border-radius: 3px; }
.sbl-dur { text-align: right; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
/* The old "stuck too long" signal was a red glow around the chip — the single
   gaudiest thing on the panel. The fact is worth keeping, the shouting is not. */
.sbl-dur-stale { color: var(--danger-text); font-weight: 600; }

/* --- status breakdown: stacked bar (table cells) -------------------------- */
.sbar {
    display: flex; height: 8px; border-radius: 4px; overflow: hidden;
    min-width: 110px; max-width: 240px; background: var(--line);
}
.sbar > i { display: block; height: 100%; background: var(--st-dot); }
.out-delivered { color: var(--success-text); font-weight: 600; }
.out-cancelled { color: var(--danger-text); font-weight: 600; }
.out-open { color: var(--muted); }
.eff-good { color: var(--success-text); font-weight: 600; }
.eff-mid { color: var(--queue); font-weight: 600; }
.eff-bad { color: var(--danger-text); font-weight: 600; }
.muted { color: var(--muted); }
.empty { color: var(--muted); font-style: italic; padding: 8px 2px; }

.loading { position: fixed; inset: 0; background: rgba(8, 12, 20, .55); display: flex; align-items: center; justify-content: center; z-index: 50; }
.loading.hidden { display: none; }
.spinner { width: 40px; height: 40px; border: 4px solid var(--line); border-top-color: var(--brand); border-radius: 50%; animation: spin .8s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
.hidden { display: none; }

/* collapsible help hints */
details.help { margin: -4px 0 10px; font-size: 12.5px; }
.panel details.help { margin: -2px 0 12px; }
.cards + details.help {
    background: var(--panel); border-radius: 10px; box-shadow: var(--shadow);
    border: 1px solid var(--line); padding: 4px 14px; margin: 0 0 14px;
}
details.help > summary {
    cursor: pointer; color: var(--brand); font-weight: 600; list-style: none;
    display: inline-block; padding: 2px 0; user-select: none;
}
details.help > summary::-webkit-details-marker { display: none; }
details.help > summary:hover { text-decoration: underline; }
details.help > div {
    margin-top: 6px; color: var(--ink-soft); background: var(--panel-2); border: 1px solid var(--line);
    border-radius: 8px; padding: 8px 12px; line-height: 1.5;
}
details.help ul { margin: 4px 0; padding-left: 18px; }
details.help li { margin: 2px 0; }
details.help code { background: rgba(255, 255, 255, .1); padding: 0 4px; border-radius: 3px; }
details.help b { color: var(--ink); }

/* segmented mode toggle (CFD) */
.seg { display: inline-flex; border: 1px solid var(--line); border-radius: 6px; overflow: hidden; }
.seg-btn {
    border: none; background: var(--input-bg); color: var(--muted); padding: 6px 12px; font-size: 12.5px;
    cursor: pointer; font-weight: 600;
}
.seg-btn + .seg-btn { border-left: 1px solid var(--line); }
.seg-btn.active { background: var(--accent-solid); color: #fff; }
.seg-btn:not(.active):hover { background: var(--hover); color: var(--ink); }

/* CFD date-range controls */
.cfd-controls { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-bottom: 8px; }
.cfd-controls input[type=date] { padding: 5px 8px; border: 1px solid var(--line); border-radius: 6px; font-size: 13px; }

/* --- «Сравнение периодов»: выбор периодов -----------------------------------
   Готовые периоды и произвольный отрезок делят одно место в строке фильтров,
   поэтому оба — display:contents: видимый лежит прямо в .filter-row и
   переносится по её правилам, а не отдельным блоком со своими отбивками
   (тот же приём, что у #cmp-filter-controls). .hidden обязан пересилить
   display:contents, иначе спрятанный выборщик остался бы на экране. */
.cmp-picker { display: contents; }
.cmp-picker.hidden { display: none; }
.cmp-range-group { display: flex; align-items: center; gap: 8px; }
/* label.inline не подходит: подпись стоит рядом с кнопкой календаря, а не
   вокруг поля, и <label> без своего контрола — обещание, которого нет. */
.cmp-range-lbl { font-size: 13px; color: var(--muted); white-space: nowrap; }
/* Длина отрезка и вычисленный конец второго — следствие выбора, не выбор:
   тоном и размером они держатся ближе к подсказке, чем к полю. */
.cmp-range-len { font-size: 12px; color: var(--muted); white-space: nowrap; }
.btn.small { padding: 4px 9px; font-size: 12px; }

/* refresh progress line */
.refresh-status {
    margin: 12px 18px 0; padding: 10px 14px; border-radius: 8px; font-size: 13px;
    display: flex; align-items: center; gap: 10px;
    background: #16233d; border: 1px solid #2d4470; color: #a8c6ff;
}
.refresh-status.hidden { display: none; }
.refresh-status.done { background: #12301f; border-color: #245c3a; color: #7fd9a0; }
.refresh-status.error { background: #351417; border-color: #6e2a30; color: #f5959a; }
.refresh-status .mini-spinner {
    width: 16px; height: 16px; border: 3px solid rgba(168, 198, 255, .25);
    border-top-color: #a8c6ff; border-radius: 50%; animation: spin .8s linear infinite; flex: none;
}
.btn.busy { opacity: .6; pointer-events: none; }

/* ---------------------------------------------- drill-down side panel ---- */
.drill-backdrop {
    position: fixed; inset: 0; background: rgba(0, 0, 0, .55);
    z-index: 60; animation: fade-in .15s ease;
}
.drill-backdrop.hidden { display: none; }
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }

.drill {
    position: fixed; top: 0; right: 0; bottom: 0; width: min(760px, 94vw);
    background: var(--panel); z-index: 61; display: flex; flex-direction: column;
    box-shadow: -6px 0 24px rgba(0, 0, 0, .5);
    border-left: 1px solid var(--line);
    animation: slide-in .18s ease;
}
.drill.hidden { display: none; }
@keyframes slide-in { from { transform: translateX(24px); opacity: .6; } to { transform: none; opacity: 1; } }

/* ---- нижний вариант той же панели (планирование, .drill-sheet) -----------
   Вылезает снизу во всю ширину на 70% высоты экрана. МОДИФИКАТОР, а не правка
   `.drill`: боковую форму делит drill-панель дашборда, где она уместна — там
   панель отвечает на «какие задачи в этом срезе», то есть показывает список,
   а списку нужна высота. У панели задачи внутри лежит диаграмма Ганта, и ей
   наоборот нужна ширина: на 760px бокового сайдбара трёхмесячный эпик не
   разложить. Тень и рамка переезжают наверх — единственная сторона, которой
   панель теперь касается страницы. */
.drill.drill-sheet {
    top: auto; left: 0; right: 0; bottom: 0;
    /* 90vh, а не 70: диаграмма Ганта у эпика с восемью стори — это полтора
       экрана строк, и каждый отданный ей процент высоты снимает один заход
       скролла (просьба владельца). Полоска сверху остаётся намеренно: панель
       обязана читаться как слой над доской, а не как отдельная страница. */
    width: auto; height: 90vh;
    border-left: none; border-top: 1px solid var(--line);
    box-shadow: 0 -6px 24px rgba(0, 0, 0, .5);
    animation: sheet-up .18s ease;
}
@keyframes sheet-up { from { transform: translateY(24px); opacity: .6; } to { transform: none; opacity: 1; } }
/* Во всю ширину заголовок обязан быть однострочным: 15px по центру полосы на
   1900px читается как подпись к пустому месту. Сроки задачи уезжают вправо, к
   кнопке закрытия — они относятся к таймлайну, а не к названию. */
.drill-sheet .drill-head { padding: 10px 16px; align-items: center; }
.drill-sheet .drill-head h2 { font-size: 15px; }
.drill-sheet .drill-stats { padding: 9px 16px; gap: 4px 30px; align-items: flex-end; }
/* Тело панели перестаёт быть скроллящимся столбцом: диаграмма Ганта внизу
   обязана занять весь остаток высоты и скроллиться САМА (иначе горизонтальный
   скролл оси уводил бы за собой всю панель). Класс ставит JS и только когда
   диаграмма действительно нарисована, — у обычной задачи (не стори/эпик)
   панель остаётся прежним прокручиваемым списком. */
.drill-body.ip-body-gantt {
    display: flex; flex-direction: column; overflow: hidden; padding: 0 16px 12px;
}

.drill-head {
    display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
    padding: 14px 16px; background: var(--bar); color: var(--bar-ink);
    border-bottom: 1px solid var(--line);
}
.drill-head h2 { margin: 0; font-size: 15px; font-weight: 700; }
.drill-sub { font-size: 12px; color: var(--bar-muted); margin-top: 3px; }
.drill-close {
    background: transparent; border: none; color: var(--bar-muted); font-size: 26px; line-height: 1;
    cursor: pointer; padding: 0 4px;
}
.drill-close:hover { color: var(--bar-ink); }

/* Figures for the whole slice, above the list. */
.drill-stats {
    display: flex; flex-wrap: wrap; gap: 4px 26px;
    padding: 11px 16px; background: var(--panel-2); border-bottom: 1px solid var(--line);
}
.drill-stats:empty { display: none; }
.ds-l { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .3px; }
.ds-n { font-size: 17px; font-weight: 700; font-variant-numeric: tabular-nums; }

.drill-tools {
    display: flex; gap: 8px; align-items: center;
    padding: 10px 16px; border-bottom: 1px solid var(--line); background: var(--panel-2);
}
.drill-tools input[type=search] {
    flex: 1; padding: 6px 10px; border: 1px solid var(--line); border-radius: 6px; font-size: 13px;
}
.drill-tools select {
    padding: 6px 8px; border: 1px solid var(--line); border-radius: 6px; font-size: 13px;
    background: var(--panel); color: var(--ink);
}
.drill-tools .btn.active { border-color: var(--brand); color: var(--brand); }

.drill-body { overflow-y: auto; padding: 6px 16px 20px; }
.drill-item { padding: 11px 0; border-bottom: 1px solid var(--line); }
.drill-item:last-child { border-bottom: none; }
.drill-item-top { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.drill-item-top .seconds { margin-left: auto; font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
.drill-item .summary { margin: 3px 0 6px; color: var(--ink); font-size: 13px; }
.drill-meta { display: flex; flex-wrap: wrap; gap: 6px 14px; font-size: 12px; color: var(--muted); margin-bottom: 4px; }
.drill-meta b { color: var(--ink); font-weight: 600; }
.drill-empty { color: var(--muted); font-style: italic; padding: 16px 0; }

.di-cancelled { font-size: 11px; color: var(--muted); font-style: italic; }
.di-prio { font-size: 11px; color: var(--muted); }

.di-toggle {
    display: inline-flex; align-items: center; gap: 6px; margin-top: 2px;
    background: transparent; border: none; padding: 2px 0; cursor: pointer;
    font: inherit; font-size: 12px; color: var(--muted);
}
.di-toggle:hover { color: var(--ink); }
.di-caret { display: inline-block; transition: transform .12s ease; }
.di-toggle.open .di-caret { transform: rotate(90deg); }
/* Kept visible while collapsed — hiding the chips must not hide the one thing
   worth acting on: a status the task has been sitting in for too long. */
/* --danger-text, not the #e5313f of the chip outline: as 12px text that red
   only reaches 3:1 on the dark panel. The outline stays as it is — a border
   is not text. */
.di-stale { color: var(--danger-text); font-weight: 600; }
.drill-item .sbl { margin: 5px 0 2px; }
.drill-item .sbl.hidden { display: none; }

/* charts become clickable */
.chart-wrap canvas { cursor: pointer; }
.hint-click { font-size: 11px; color: var(--muted); font-weight: 400; }

/* priority tasks table */
#priority-tasks table.pri { width: 100%; border-collapse: collapse; font-size: 13px; }
#priority-tasks th, #priority-tasks td { padding: 6px 8px; border-bottom: 1px solid var(--line); text-align: left; vertical-align: top; }
#priority-tasks th { color: var(--muted); font-weight: 600; font-size: 12px; text-transform: uppercase; }
#priority-tasks tr:hover td { background: var(--hover); }
#priority-tasks td.summary { white-space: normal; max-width: 320px; }
/* Строка стори/эпика в панели приоритетов. Сначала пробовали разворачивать её
   вложенными задачами прямо в таблицу — владелец забраковал: один эпик с меткой
   притаскивал десяток Medium-задач, и панель перестала быть коротким списком
   (9 строк превращались в 31). Теперь родитель — одна строка со счётчиком, а
   задачи открываются в сайд-баре по клику.
   Полоса слева — тот же приём, что у строк «Стори и эпики» на сравнении: тон на
   краю, а не заливка под текстом (там это и контраст спасло, см. §12). */
#priority-tasks tr.pri-parent td:first-child { box-shadow: inset 4px 0 0 var(--epic-text); }
#priority-tasks tr.clickable { cursor: pointer; }
/* Что это за строка. Приглушённый чипс: он подпись к названию, а не сигнал —
   кричать здесь должны счётчик и статус. */
.pri-type {
    display: inline-block; margin-right: 6px; white-space: nowrap;
    font-size: 10px; font-weight: 700; text-transform: uppercase;
    letter-spacing: .3px; color: var(--epic-text);
    border: 1px solid var(--line); border-radius: 4px; padding: 0 5px;
    vertical-align: middle;
}
/* Незакрытые крупнее закрытых: вопрос «сколько ещё осталось», а не «сколько
   сделано», и цифры не должны выглядеть равноправными. */
.pri-kids b { font-size: 14px; }
/* Счётчик — кнопка (панель со всеми вложенными задачами), но выглядит как текст:
   в колонке чисел рамка читалась бы как поле ввода. Подчёркивание точками —
   тот же намёк «здесь можно нажать», что у th[title] в таблицах. */
.pri-kids-btn {
    border: 0; background: none; padding: 0; font: inherit; color: inherit;
    cursor: pointer; text-decoration: underline dotted; text-underline-offset: 3px;
}
.pri-kids-btn:hover { color: var(--brand); }

/* Раскрытие вложенных приоритетных задач. Стрелка занимает место всегда (у
   строк без вложенных — пустая), иначе ключи в первой колонке съезжают друг
   относительно друга и колонка перестаёт читаться сверху вниз. */
.pri-caret {
    display: inline-block; width: 12px; margin-right: 4px;
    color: var(--muted); font-size: 10px; user-select: none;
}
#priority-tasks tr.pri-open td:first-child .pri-caret { color: var(--brand); }
/* Вложенная задача: отступ + уголок, чтобы принадлежность читалась без цвета.
   Полосы проекта у неё нет — она есть у родителя, и вторая полоса рядом
   выглядела бы как ещё один эпик. */
#priority-tasks tr.pri-child td:first-child {
    padding-left: 28px; position: relative;
}
#priority-tasks tr.pri-child td:first-child::before {
    content: '└'; position: absolute; left: 12px; color: var(--muted); font-size: 11px;
}
#priority-tasks tr.pri-child td { background: var(--panel-2); }
/* --- priority badge (.pb) -------------------------------------------------
   Same construction as .st: a tint of the level's colour with text in a
   readable shade of it, uppercase. Built from the same colorTone() so the two
   badge families cannot drift apart. The colours themselves are unchanged —
   they were already picked to keep the five levels distinguishable — only the
   way they are worn changed, from a solid fill to a tint.
   No border, unlike .st: priority and status sit in neighbouring columns, and
   this is the one quiet difference that stops them reading as the same chip. */
.pb {
    display: inline-block; padding: 2px 7px; border-radius: 6px; white-space: nowrap;
    font-size: 10px; font-weight: 700; letter-spacing: .04em; line-height: 1.5;
    text-transform: uppercase;
}
[data-theme="dark"] .pb  { color: var(--st-fg-d); background: var(--st-bg-d); }
[data-theme="light"] .pb { color: var(--st-fg-l); background: var(--st-bg-l); }
/* "Not set" carries no colour at all — see prioBadge(). */
.pb-none { color: var(--muted); background: transparent; font-weight: 600; }

/* Тип работ (FRONT / BACK / TECH / DESIGN) — третье семейство бейджей, устроено
   как .pb: тинт цвета плюс выведенный из него читаемый оттенок текста, обе темы
   уезжают в разметку сразу (colorTone в ui.js). Чуть мельче приоритета и с
   отступом справа: он стоит ПЕРЕД названием и не должен спорить с ним за
   внимание — его работа в том, чтобы строки различались боковым зрением. */
/* Группа: у задачи может быть два маркера — «[F][B]» значит «и фронт, и бэк»,
   и это не редкость. Отступ живёт на группе, а не на каждом чипсе, иначе между
   двумя бейджами он удваивается. */
.wt-group { display: inline-flex; gap: 3px; margin-right: 6px; vertical-align: middle; }
.wt {
    display: inline-block; padding: 1px 6px; border-radius: 5px; white-space: nowrap;
    font-size: 9.5px; font-weight: 700; letter-spacing: .04em; line-height: 1.5;
}
[data-theme="dark"] .wt  { color: var(--st-fg-d); background: var(--st-bg-d); }
[data-theme="light"] .wt { color: var(--st-fg-l); background: var(--st-bg-l); }
/* На карточке доски название однострочное и место дорого: там бейдж ужимается
   до буквы (см. workTypeBadge в planning.js) — правило только про плотность. */
.pcard .wt-group { margin-right: 4px; }
.pcard .wt { padding: 0 4px; letter-spacing: 0; }
/* On the panel header the tint sits on --bar and cannot be trusted, same as
   for .st — the colour moves to the border, the text to --bar-ink. */
.drill-head .pb { background: transparent; color: var(--bar-ink); border: 1px solid var(--st-dot); }

/* ------------------------------------ planning: issue panel -------------- */
/* Reuses the drill panel's shell (.drill / .drill-head / .drill-item); only the
   related-issues block is new. */
.pcard { cursor: pointer; }
.pcard-ghost { cursor: default; }
.bv-sum.ip-link { cursor: pointer; }
.bv-sum.ip-link:hover { color: var(--brand); text-decoration: underline; }

.ip-section { padding: 12px 0; border-bottom: 1px solid var(--line); }
.ip-section:last-child { border-bottom: none; }
.ip-h {
    font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .3px;
    color: var(--muted); margin: 10px 0 6px;
}
.ip-why { cursor: help; }
.ip-note { font-size: 12px; color: var(--queue); margin-bottom: 6px; }
.ip-warn { color: var(--danger-text); font-weight: 600; }

.ip-sum {
    display: flex; flex-wrap: wrap; gap: 6px 16px; font-size: 12px; color: var(--muted);
    background: var(--panel-2); border: 1px solid var(--line); border-radius: 6px;
    padding: 8px 10px; margin-bottom: 8px;
}
.ip-sum b { color: var(--ink); font-weight: 700; }
.ip-bkt { display: inline-flex; align-items: center; gap: 5px; }
.ip-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }

/* Заказчик/Цель/Бизнес-ценность — единственная причина, по которой карточка
   вообще оказалась на доске, поэтому у блока своя рамка и текст крупнее
   остальной панели (14px против 12px), а не мелкая подпись вперемешку с
   остальными полями. Поля редактируемые (см. editableCell()/saveField() в
   planning.js) — тот же путь записи в Jira, что и на вкладке
   «Бизнес-ценность». */
.ip-bv {
    display: grid; gap: 10px; margin: 10px 0 14px; padding: 10px 12px;
    background: var(--panel-2); border: 1px solid var(--line); border-radius: 8px;
}
.ip-bv-row { display: grid; gap: 4px; }
.ip-bv-l { font-size: 12px; font-weight: 700; color: var(--ink-soft); text-transform: uppercase; letter-spacing: .03em; }
.ip-bv .bv-edit { font-size: 14px; }

/* Разбивка по статусам вложенных задач — плитка в шапке панели, а не отдельный
   график. Донат тут и был (Chart.js), но в панели на 70vh он забирал 170px,
   чтобы сказать ровно то же, что полоска в 8px; поимённые цифры по бакетам всё
   равно есть ниже, в .ip-sum. Плитка широкая и стоит последней в .drill-stats,
   поэтому забирает остаток строки, но не растягивается на весь экран: полоска
   шириной 900px перестаёт читаться как пропорция. */
.ds-wide { flex: 1 1 220px; min-width: 180px; max-width: 460px; }
.ds-wide .sbar { margin-top: 7px; }

/* «Поля задачи и список вложенных» — то, что в панели стори/эпика уступило
   место диаграмме. Свёрнуто по умолчанию, но своё состояние помнит между
   карточками (pstate.ipMoreOpen): бизнес-ценность правят у нескольких эпиков
   подряд, и схлопывать блок на каждом — издевательство. Раскрытый блок
   скроллится сам и ограничен по высоте — иначе он выдавил бы диаграмму. */
.ip-more { flex: 0 0 auto; border-bottom: 1px solid var(--line); }
.ip-more > summary {
    cursor: pointer; padding: 8px 0; font-size: 12px; font-weight: 700;
    color: var(--muted); text-transform: uppercase; letter-spacing: .3px;
}
.ip-more > summary:hover { color: var(--brand); }
.ip-more-body { max-height: 34vh; overflow-y: auto; padding-bottom: 6px; }

/* ---- диаграмма Ганта внутри панели стори/эпика ---------------------------
   Второй уровень планирования: на таймлайне стоит эпик, а здесь — задачи,
   которые в него входят. Ось РОВНО равна срокам эпика на таймлайне, поэтому
   своих «с/по» у диаграммы нет и быть не может: сдвинуть границу можно только
   там, снаружи.

   Слева пул неразмещённых задач, справа сетка. Перетаскивание, упаковка по
   свободным строкам и превью места — те же функции, что у таймлайна
   (renderGantt/ganttDropTarget в planning.js переиспользуют overlaps/freeRow и
   onDrag), поэтому и классы ручек с кнопкой снятия здесь общие с ним:
   .tl-h / .tl-h-l / .tl-h-r / .tl-x. Полоса ниже карточки таймлайна (26 против
   76px) — на ней одна строка текста, а строк тут бывает двадцать. */
.ip-gantt { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; padding-top: 10px; }
.gt-tools {
    display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
    padding-bottom: 8px; border-bottom: 1px solid var(--line);
}
.gt-tools .ip-h { margin: 0; }
.gt-range { font-size: 12px; color: var(--muted); }
.gt-range b { color: var(--ink); font-weight: 600; }
/* Счётчик конфликтов — единственное красное в панели, поэтому кликабельный: он
   же и кнопка «показать только конфликтные». */
.gt-conflicts {
    font-size: 12px; font-weight: 600; color: var(--danger-text);
    border: 1px solid var(--blocked); border-radius: 6px; padding: 1px 7px;
    background: transparent; cursor: help;
}
.gt-panes { flex: 1 1 auto; min-height: 0; display: grid; grid-template-columns: 236px minmax(0, 1fr); }

/* Пул: то, что ещё не расставлено. Скроллится сам — у эпика бывает 30 детей, а
   высота панели фиксирована. */
.gt-pool { min-height: 0; overflow-y: auto; border-right: 1px solid var(--line); padding-right: 8px; }
.gt-pool-h {
    position: sticky; top: 0; z-index: 1; background: var(--panel);
    font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .3px;
    color: var(--muted); padding: 6px 0;
}
.gt-pool-empty { font-size: 12px; color: var(--muted); font-style: italic; padding: 8px 0; }
/* Строка списка в ДВА уровня: сверху ключ с бейджами, снизу название целиком.
   Название переносится и не обрезается (просьба владельца): в рельсе 236px
   выбирают, что ставить следующим, а по «Счета европейских рекламодателей:
   созда…» этого не выберешь — обрезка съедала как раз то, чем задачи друг от
   друга отличаются. Отдельная строка нужна ради ширины: в одном ряду с ключом
   названию оставалось ~140px, то есть переносов было бы втрое больше.
   Цена решения — строка стала выше, и в пуле на 80 задач больше скролла; это
   осознанный обмен в пользу читаемости. */
.gt-pool-item {
    display: flex; flex-direction: column; gap: 3px; padding: 5px 6px;
    border: 1px solid var(--line); border-radius: 6px; margin-bottom: 4px;
    background: var(--panel-2); cursor: grab; font-size: 12px;
}
.gt-pool-item:hover { border-color: var(--brand); }
.gt-pool-item.dragging { opacity: .35; }
.gt-pool-top { display: flex; align-items: center; gap: 6px; }
/* Ключ — ссылка в Jira. cursor:pointer перебивает grab у строки: строку тянут, а
   ключ открывают, и курсор обязан говорить, что тут другое действие. */
.gt-pool-key {
    flex: 0 0 auto; color: var(--brand); font-weight: 700; font-size: 11px;
    text-decoration: none; cursor: pointer;
}
a.gt-pool-key:hover { text-decoration: underline; }
.gt-pool-sum {
    color: var(--ink-soft); line-height: 1.3;
    /* break-word — страховка от длинного неразрывного куска («affiliates-balance:»,
       ссылки): без неё такой кусок вылез бы за рельсу по горизонтали. */
    white-space: normal; overflow-wrap: break-word;
}
/* Задача, только что вернувшаяся в пул из-за сокращения сроков родителя. Гаснет
   сама (анимация), потому что это сообщение о событии, а не состояние задачи:
   после перезагрузки она просто лежит в пуле, и красная рамка навсегда врала
   бы про то, что с ней что-то не так. */
.gt-pool-item.gt-returned { border-color: var(--blocked); animation: gt-fade 6s ease forwards; }
@keyframes gt-fade { 0%, 60% { border-color: var(--blocked); } 100% { border-color: var(--line); } }

.gt-chart { min-height: 0; overflow: auto; }
/* Заголовок в ДВА уровня: месяцы отрезками над своими днями, ниже номера дней.
   На колонке в 26px месяц не написать, а без него ось читалась как набор чисел
   от 1 до 31, начинающийся посередине (замечание владельца). Прилипает целиком —
   при вертикальном скролле сетки обе строки обязаны оставаться на месте. */
.gt-head {
    display: flex; flex-direction: column;
    position: sticky; top: 0; z-index: 2; background: var(--panel);
    box-shadow: inset 0 -1px 0 var(--line);
}
.gt-months, .gt-days { display: flex; }
/* Месяц по центру своего отрезка: подпись относится к промежутку, а не к его
   первому дню — именно это и было не так, когда месяц дописывался к «1». */
.gt-month {
    flex: 0 0 auto; text-align: center; font-size: 11px; font-weight: 700;
    color: var(--ink-soft); padding: 4px 4px 3px;
    border-left: 1px solid var(--line); border-bottom: 1px solid var(--line);
    white-space: nowrap; overflow: hidden;
}
.gt-col {
    flex: 0 0 auto; text-align: center; font-size: 11px; font-weight: 600;
    color: var(--muted); padding: 5px 2px; border-left: 1px solid var(--line);
    white-space: nowrap; overflow: hidden;
}
.gt-col-now { color: var(--brand); font-weight: 700; background: var(--now-tint); box-shadow: inset 0 -2px 0 var(--brand); }
/* Выходные — тёмной заливкой, без учёта праздников (их календаря в приложении
   нет, а угадывать хуже, чем не показывать). Своя переменная, а не --hover:
   --hover обязан оставаться реакцией на курсор — то же правило, по которому у
   «сегодня» на таймлайне собственный --now-tint. */
.gt-col-weekend { background: var(--weekend-tint); }
.gt-col-now.gt-col-weekend { background: var(--now-tint); }
/* Заливка на всю высоту сетки. Лежит ПОД полосами и обязана не перехватывать
   курсор: сброс считается по координатам, и pointer-events здесь сломали бы
   перетаскивание всего, что окажется над выходными (то же, что у .tl-now). */
.gt-weekend { position: absolute; top: 0; bottom: 0; z-index: 0; pointer-events: none; background: var(--weekend-tint); }
.gt-grid {
    position: relative; min-height: 90px;
    background-image:
        linear-gradient(to right, var(--line) 1px, transparent 1px),
        linear-gradient(to bottom, var(--line) 1px, transparent 1px);
}
/* Тот же язык, что у большого таймлайна: тон «сейчас» и красная линия «сегодня»
   (см. .tl-now / .tl-today выше — там же объяснено, почему цвета разные и
   почему обоим обязателен pointer-events:none). */
.gt-today { position: absolute; top: 0; bottom: 0; width: 2px; background: var(--danger-text); z-index: 0; pointer-events: none; }

/* Полоса задачи. Тинт цвета бакета + полоска слева тем же цветом — тот же приём,
   что у бейджа статуса (§6b): сплошная заливка бакетов не проходит 4.5:1 на
   большей части палитры, а как тинт те же цвета работают. --bkt подставляет JS. */
.gt-bar {
    position: absolute; height: 26px; overflow: hidden; z-index: 1;
    display: flex; align-items: center; gap: 6px; padding: 0 6px 0 7px;
    border: 1px solid var(--line); border-left: 3px solid var(--bkt, var(--muted));
    border-radius: 5px; background: var(--panel); cursor: grab;
    box-shadow: var(--shadow); transition: transform 130ms ease-out;
    font-size: 12px; white-space: nowrap;
}
.gt-bar::before {
    content: ''; position: absolute; inset: 0; pointer-events: none;
    background: var(--bkt, var(--muted)); opacity: .14;
}
.gt-bar > * { position: relative; z-index: 1; }
.gt-bar.dragging { z-index: 10; opacity: .85; transition: none; }
.gt-bar-key { flex: 0 0 auto; color: var(--brand); font-weight: 700; font-size: 11px; text-decoration: none; }
.gt-bar-key:hover { text-decoration: underline; }
.gt-bar-sum { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--ink); }

/* Название, не влезшее в свою полосу, — ВПРАВО, за полосу (ganttOutsideLabels).
   Внутри его целиком не показать: ширина полосы это её срок, и в трёхдневную
   задачу (78px) не поместится ни одно название, сколько ни переноси; переносить в
   несколько строк тоже некуда — строка сетки 34px. Так делают ганты, и место для
   подписи справа гарантированно свободно: строки упакованы по пересечению дат.
   Без фона и рамки — это подпись к полосе, а не вторая полоса.
   pointer-events:none обязателен: сброс считается по координатам курсора, и
   подпись, перехватывающая события, ломала бы перетаскивание над своей строкой
   (то же правило, что у заливки выходных и полосы «сейчас»). */
.gt-bar-narrow .gt-bar-sum { display: none; }
.gt-bar-label {
    position: absolute; z-index: 1; pointer-events: none;
    display: flex; align-items: center;
    font-size: 12px; color: var(--ink-soft); white-space: nowrap;
    overflow: hidden; text-overflow: ellipsis;
}
.gt-bar-label-story { font-size: 13px; font-weight: 600; color: var(--ink); }
/* На время жеста подписи убираются: они привязаны к прежним координатам полос и
   за курсором не едут. Возвращает их renderGantt() в конце жеста. */
.gt-grid.gt-drag .gt-bar-label { display: none; }
/* Бейджи типа работ и типа задачи стоят МЕЖДУ ключом и названием: на диаграмме
   сравнивают области работ, поэтому «BACK» важнее, чем хвост названия, и при
   нехватке места урезаться должно название, а не бейдж. Отступ группы обнулён —
   на полосе высотой 26px он живёт в общем gap флекса (иначе удваивается). */
.gt-bar .wt-group, .gt-pool-item .wt-group { margin-right: 0; }
.gt-bar .wt, .gt-pool-item .wt { padding: 0 4px; letter-spacing: 0; }
/* Полоса — тинтованная поверхность (заливка бакета через ::before, 14%), ровно
   тот же случай, что карточка с тинтом проекта: полупрозрачный бейдж лёг бы
   тинтом на тинт, и его контраст зависел бы от статуса задачи. Лечится так же —
   своя НЕПРОЗРАЧНАЯ подложка (--panel, тинт поверх неё картинкой), и цвет
   бакета из стека выпадает. Селектор с атрибутом темы, чтобы перебивать общее
   правило [data-theme=…] .wt по специфичности, а не по порядку в файле.
   Рельса пула тоже: там под бейджем --panel-2, а не --panel. */
[data-theme="dark"] .gt-bar .wt {
    background-color: var(--panel);
    background-image: linear-gradient(var(--st-bg-d), var(--st-bg-d));
}
[data-theme="light"] .gt-bar .wt {
    background-color: var(--panel);
    background-image: linear-gradient(var(--st-bg-l), var(--st-bg-l));
}
[data-theme="dark"] .gt-pool-item .wt {
    background-color: var(--panel-2);
    background-image: linear-gradient(var(--st-bg-d), var(--st-bg-d));
}
[data-theme="light"] .gt-pool-item .wt {
    background-color: var(--panel-2);
    background-image: linear-gradient(var(--st-bg-l), var(--st-bg-l));
}
/* Тип задачи — ПУСТОЙ чипс, в отличие от залитого тинтом типа работ: тип работ
   говорит про содержание («здесь бэкенд»), тип задачи — про форму («это
   контейнер со своей работой внутри»), и это два разных утверждения. Цвет —
   --epic-text: он свой в каждой теме и проходит 4.5:1 в обеих (см. §6), тогда
   как палитровый #8b5cf6 на светлой панели даёт только 4.2:1. */
.gt-type {
    flex: 0 0 auto; font-size: 9.5px; font-weight: 700; line-height: 1.5;
    padding: 0 4px; border-radius: 5px; white-space: nowrap;
    color: var(--epic-text); border: 1px solid var(--epic-text); background: transparent;
}
/* Полоса, вылезшая за сроки родителя. Не обрезаем и не снимаем — конфликт это
   состояние, о котором менеджер должен узнать и решить сам (см. §7). */
.gt-bar-conflict { border-color: var(--blocked); border-style: dashed; border-left-style: solid; }
.gt-bar-conflict .gt-bar-sum { color: var(--danger-text); }
.gt-bar .tl-x { top: 4px; right: 8px; }

/* ---- вложенность: полосы-группы, соединители, стрелки --------------------
   Диаграмма делится на горизонтальные полосы — по одной на стори плюс «вне
   стори» в конце. Вложенность выражена МЕСТОМ (задача лежит в полосе своей
   стори), а стрелка её лишь достраивает: у стори бывает пятнадцать задач, и
   пятнадцать свободных стрелок из разных точек дали бы клубок, тогда как место
   в полосе читается сразу. Фон полос чередуется — граница между ними должна
   быть видна и там, где стрелок нет. */
.gt-band { position: absolute; left: 0; z-index: 0; pointer-events: none; }
.gt-band-alt { background: var(--hover); }
/* «Вне стори» — остаток, а не равноправная группа: отделён чертой сверху и
   не претендует на свой тон. */
.gt-band-loose { border-top: 1px dashed var(--line); background: transparent; }

/* Полоса-стори: контейнер, а не единица работы. Выше обычной, тон свой
   (--epic-text через --bkt) и подпись плотнее — она читается как заголовок
   группы, которым и является. */
.gt-bar-story {
    height: 30px; font-weight: 600;
    border-left-width: 4px; border-color: var(--epic-text);
}
/* Название стори чуть крупнее остальных полос (просьба владельца: «немного»):
   13px против 12 у задач. Полоса-стори — заголовок группы, и размер шрифта
   отделяет её от содержимого сильнее, чем цвет и высота: цветов на диаграмме уже
   много (бакеты статусов, тип работ, просрочка), а размер в этом ряду не занят.
   Полоса стори выше остальных (30 против 26px), поэтому лишний пиксель строки
   она принимает без обрезки; название однострочное с ellipsis. */
.gt-bar-story .gt-bar-sum { font-size: 13px; color: var(--ink); }
.gt-bar-count {
    flex: 0 0 auto; font-size: 10px; font-weight: 700; cursor: help;
    color: var(--epic-text); border: 1px solid var(--epic-text);
    border-radius: 4px; padding: 0 4px; background: transparent;
}
.gt-caret {
    flex: 0 0 auto; width: 16px; height: 16px; padding: 0; margin-right: 16px;
    border: 0; background: transparent; color: var(--epic-text);
    font-size: 11px; line-height: 16px; cursor: pointer;
    transition: transform 120ms ease-out;
}
.gt-caret-closed { transform: rotate(-90deg); }

/* Нерасставленной стори на диаграмме НЕТ — ни полосы, ни заглушки: её место в
   списке слева. Заглушка здесь была и выглядела как «тут уже что-то есть»
   (замечание владельца). Полоса появляется в тот момент, когда стори ставят или
   когда в неё кладут первую задачу — она ставится сама и покрывает её
   (gtCoverStory в planning.js). */

/* Вертикаль от начала стори вниз по её полосе + горизонтальные стрелки к началу
   каждой задачи. Стрелка от НАЧАЛА стори намеренно: она тогда работает ещё и
   как отметка «задача начинается раньше своей стори» — направление
   разворачивается, и это видно, не читая дат. */
.gt-spine {
    position: absolute; width: 2px; z-index: 0; pointer-events: none;
    background: var(--epic-text); opacity: .45;
    border-radius: 1px;
}
.gt-tick {
    position: absolute; height: 2px; z-index: 0; pointer-events: none;
    background: var(--epic-text); opacity: .45;
}
/* Наконечник: у обычной стрелки — справа, у развёрнутой («задача раньше своей
   стори») — слева. Он же и есть тот признак, по которому видно направление. */
.gt-tick::after {
    content: ''; position: absolute; right: -1px; top: -3px;
    border: 4px solid transparent; border-left-color: var(--epic-text);
    border-right: 0;
}
.gt-tick-back::after {
    right: auto; left: -1px;
    border-left: 0; border-right: 4px solid var(--epic-text);
}

/* Метки .gt-bar-outside («задача вне сроков своей стори») здесь больше нет:
   сроки стори теперь выводятся из её задач в обе стороны (gtCoverStory), и
   состояние стало недостижимым. */
/* Задача связана с несколькими стори — показана в первой. Пунктирная левая
   кромка: «принадлежность выбрана, но выбор был не единственным». */
.gt-bar-ambiguous { border-left-style: dashed; }

/* Просрочка и завершённость — КОЛЬЦОМ, а не рамкой, и тем же языком, что на
   карточках доски (.pcard-late / .pcard-done). Рамка полосы уже занята
   утверждениями про вложенность (конфликт с осью, неоднозначный родитель), а
   это утверждения про факт — они обязаны накладываться, а не вытеснять друг
   друга. Кольцо должно пережить собственную тень полосы, поэтому она перечислена
   второй. Взаимоисключающие: просрочка требует НЕтерминального статуса.

   Зелёное кольцо тут один раз убирали (тинт бакета и так несёт цвет Done) и
   вернули по требованию владельца: «с обводкой очевиднее, что стори или задача
   закрыты». Тинт полупрозрачный и на глаз спорит с соседними бакетами, обводка
   же читается однозначно. */
.gt-bar-late { box-shadow: 0 0 0 2px var(--blocked), var(--shadow); }
.gt-bar-done { box-shadow: 0 0 0 2px var(--done), var(--shadow); }

.gt-note { font-size: 11px; color: var(--muted); cursor: help; }

/* Пул сгруппирован так же, как сетка: выбирая, что ставить следующим, надо
   видеть, к какой части эпика это относится. */
.gt-pool-group {
    display: flex; align-items: baseline; gap: 4px;
    margin: 8px 0 4px; font-size: 10px; font-weight: 700;
    text-transform: uppercase; letter-spacing: .3px; color: var(--muted);
}
.gt-pool-group-k { color: var(--epic-text); }
/* Группа завершённой стори — она уехала в конец списка, и заголовок должен это
   подтверждать, иначе выглядит как обычная группа, оказавшаяся почему-то внизу. */
.gt-pool-group-done .gt-pool-group-k { color: var(--success-text); }
.gt-pool-group-loose { color: var(--muted); font-style: italic; text-transform: none; letter-spacing: 0; }
/* Отступ, а не рамка: вложенность в списке — про подчинение, и лишняя рамка на
   каждой строке в рельсе 236px съела бы место, которого и так нет. */
.gt-pool-nested { margin-left: 12px; }
.gt-pool-story { border-color: var(--epic-text); }
/* Завершённые в списке — зелёным, тот же язык, что у карточек доски
   (.pcard-done). В списке на тридцать строк это первое, что хочется отделить:
   расставлять завершённое обычно уже незачем. */
.gt-pool-done { border-color: var(--done); }

/* Ось редактируется прямо в шапке — это сроки карточки на таймлайне. Поля
   компактные и без рамки в покое: это подпись, которая оказалась
   редактируемой, а не форма, и ряд из двух полноценных инпутов перетянул бы на
   себя всю шапку. */
.gt-axis-date {
    width: 124px; padding: 1px 4px; font-size: 12px; font-weight: 600;
    color: var(--ink); background: transparent;
    border: 1px solid transparent; border-radius: 4px;
}
.gt-axis-date:hover { border-color: var(--line); background: var(--input-bg); }
.gt-axis-date:focus { border-color: var(--brand); background: var(--input-bg); outline: none; }
.gt-range-days { color: var(--muted); }

/* Превью места — то же обещание, что на таймлайне, и той же формы. */
.gt-drop {
    position: absolute; height: 26px; z-index: 0; pointer-events: none;
    border: 2px dashed var(--brand); border-radius: 5px;
    background: rgba(111, 168, 255, .10); animation: tl-drop-in 120ms ease-out;
}
.gt-drop.hidden { display: none; }
/* Диаграмму рисовать не от чего: у задачи нет места на таймлайне, а ось — это и
   есть её сроки. Не ошибка, а следующий шаг, поэтому подсказка, а не warning. */
.gt-noaxis { font-size: 13px; color: var(--muted); padding: 14px 0; }
.gt-noaxis b { color: var(--ink); }

/* Список выпавших задач в модалке предупреждения. */
.gantt-modal-list { margin: 0 0 10px; padding-left: 18px; font-size: 12px; color: var(--muted); }
.gantt-modal-list b { color: var(--ink); font-weight: 600; }

/* ------------------------------------------------------ login page ------- */
.auth-body { display: flex; align-items: center; justify-content: center; min-height: 100vh; padding: 20px; }
.auth-card {
    width: 100%; max-width: 380px; background: var(--panel); border: 1px solid var(--line);
    border-radius: 12px; box-shadow: var(--shadow); padding: 26px 24px;
}
.auth-title { margin: 0; font-size: 20px; font-weight: 800; letter-spacing: .3px; }
.auth-sub { margin: 4px 0 20px; font-size: 13px; color: var(--muted); }
.auth-field { display: block; margin-bottom: 14px; }
.auth-field > span { display: block; font-size: 12px; color: var(--muted); margin-bottom: 5px; }
.auth-field input { width: 100%; padding: 9px 11px; font-size: 14px; }
.field-hint { display: block; margin-top: 4px; font-size: 11px; color: var(--muted); }
.auth-submit { width: 100%; margin-top: 6px; padding: 10px; font-size: 14px; background: var(--accent-solid); border-color: var(--accent-solid); color: #fff; }
.auth-submit:hover { background: #4a7ce0; }
.auth-error {
    background: #351417; border: 1px solid #6e2a30; color: #f5959a;
    padding: 9px 12px; border-radius: 8px; font-size: 13px; margin-bottom: 16px;
}

/* ------------------------------------------------------ users page ------- */
.flash { margin: 14px 18px 0; padding: 10px 14px; border-radius: 8px; font-size: 13px; }
.flash-ok { background: #12301f; border: 1px solid #245c3a; color: #7fd9a0; }
.flash-err { background: #351417; border: 1px solid #6e2a30; color: #f5959a; }
.panel-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 8px; }
.panel-head h2 { margin: 0; }
.users-table td { vertical-align: middle; }
.users-table td.actions { white-space: nowrap; }
/* A <td> must stay display:table-cell. Making it a flex container takes it out
   of the table's row-height sizing, so the cell renders shorter than its
   neighbours and its bottom border floats mid-row. Flex goes on a wrapper
   inside the cell instead. */
.cell-row { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.inline-form { display: inline; margin: 0; }
.btn.danger { color: var(--danger-text); border-color: #5a2a2f; }
.btn.danger:hover { background: rgba(242, 104, 109, .12); border-color: var(--danger-text); }

/* ------------------------------------------------------ modals ---------- */
.modal { position: fixed; inset: 0; z-index: 80; display: flex; align-items: center; justify-content: center; padding: 20px; }
.modal.hidden { display: none; }
.modal-backdrop { position: absolute; inset: 0; background: rgba(0,0,0,.6); animation: fade-in .15s ease; }
.modal-card {
    position: relative; width: 100%; max-width: 440px; background: var(--panel);
    border: 1px solid var(--line); border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,.5);
    max-height: 90vh; display: flex; flex-direction: column;
}
.modal-head {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    padding: 14px 18px; border-bottom: 1px solid var(--line);
}
.modal-head h3 { margin: 0; font-size: 15px; font-weight: 700; }
.modal-body { padding: 16px 18px; overflow-y: auto; }
.modal-body .auth-field:last-child { margin-bottom: 0; }
.modal-foot {
    display: flex; justify-content: flex-end; gap: 8px;
    padding: 12px 18px; border-top: 1px solid var(--line); background: var(--panel-2);
    border-radius: 0 0 12px 12px;
}
.modal-foot .btn:last-child { background: var(--accent-solid); border-color: var(--accent-solid); color: #fff; }
.modal-foot .btn:last-child:hover { background: #4a7ce0; }
/* The close "×" is reused from the drill panel, but here it sits on a panel
   background rather than the coloured bar — so it needs panel-ink colours. */
.modal-head .drill-close { color: var(--muted); }
.modal-head .drill-close:hover { color: var(--ink); }

/* ------------------------------------------- light-theme component tints -- */
/* Tinted status blocks are defined dark by default; these are their light
   counterparts (same semantics, readable on a white panel). */
:root[data-theme="light"] .banner {
    background: #fff6e5; border-color: #f0d089; color: #7a5a10;
}
:root[data-theme="light"] .banner code { background: rgba(0, 0, 0, .06); }
:root[data-theme="light"] details.help code,
:root[data-theme="light"] .tag { background: rgba(0, 0, 0, .06); }
:root[data-theme="light"] .refresh-status {
    background: #eaf1ff; border-color: #b9cdf3; color: #274b8f;
}
:root[data-theme="light"] .refresh-status.done {
    background: #e7f6ec; border-color: #a9d9ba; color: #1f6b3a;
}
:root[data-theme="light"] .refresh-status.error {
    background: #fdecee; border-color: #f0b6bc; color: #9a2530;
}
:root[data-theme="light"] .refresh-status .mini-spinner {
    border-color: rgba(39, 75, 143, .25); border-top-color: #274b8f;
}
:root[data-theme="light"] .flash-ok { background: #e7f6ec; border-color: #a9d9ba; color: #1f6b3a; }
:root[data-theme="light"] .flash-err { background: #fdecee; border-color: #f0b6bc; color: #9a2530; }
:root[data-theme="light"] .auth-error { background: #fdecee; border-color: #f0b6bc; color: #9a2530; }
:root[data-theme="light"] .loading { background: rgba(255, 255, 255, .6); }
:root[data-theme="light"] .btn.danger { border-color: #f0b6bc; }
:root[data-theme="light"] .btn.danger:hover { background: #fdecee; }
:root[data-theme="light"] .sidemenu { box-shadow: 6px 0 24px rgba(20, 30, 50, .15); }
:root[data-theme="light"] .drill { box-shadow: -6px 0 24px rgba(20, 30, 50, .18); }
:root[data-theme="light"] .modal-card { box-shadow: 0 10px 40px rgba(20, 30, 50, .25); }

/* theme switch in the side menu */
.sm-theme { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 18px 10px; }
.sm-theme-label { font-size: 12px; color: var(--muted); }
.sm-theme .seg-btn { padding: 5px 10px; font-size: 12px; }

/* ==========================================================================
   Планирование: таймлайн + матрица «ценность / сложность».
   Карточки и зоны сброса общие для обеих досок, поэтому .pcard живёт отдельно
   от табличных стилей дашборда. Бейджи статуса и приоритета на карточке — те
   же общие .st / .pb (ui.js), здесь только обвязка и непрозрачная подложка,
   без которой тон проекта под тоном бейджа рушит контраст (см. ниже).
   ========================================================================== */
.plan-main {
    display: grid;
    grid-template-columns: 300px minmax(0, 1fr);
    gap: 12px;
    align-items: start;
}
@media (max-width: 1100px) {
    .plan-main { grid-template-columns: 1fr; }
}

/* Sticks below the sticky top bar (--topbar-h is measured in ui.js). The
   max-height below is only the pinned-state fallback: unpinned the panel starts
   further down the page, so syncBacklogHeight() in planning.js keeps it exact. */
.backlog {
    position: sticky;
    top: calc(var(--topbar-h, 56px) + 12px);
    max-height: calc(100vh - var(--topbar-h, 56px) - 28px);
    display: flex; flex-direction: column;
    overflow: hidden;
}
.backlog h2 { margin-top: 0; }
/* Used to be a page-wide .filters row above the whole board, rarely touched
   and costing both boards a third of their height for it. Scoped here now —
   see backlogCards()/matchesFilters() in planning.js for why that also means
   it only ever affects what's offered up for placement, never a card that's
   already on a board. */
.backlog-filters { display: flex; flex-direction: column; gap: 8px; padding-bottom: 10px; border-bottom: 1px solid var(--line); margin-bottom: 8px; }
.backlog-filters input[type=search] {
    width: 100%; padding: 7px 10px; border: 1px solid var(--line);
    border-radius: 6px; font-size: 13px;
}
.filter-row-compact { display: flex; flex-wrap: wrap; gap: 6px; }
.backlog-tools { padding-bottom: 8px; border-bottom: 1px solid var(--line); margin-bottom: 8px; }
/* min-height:0 is load-bearing: a flex item defaults to min-height:auto and
   refuses to shrink below its content, which makes the panel blow past its
   own max-height instead of scrolling the list inside. */
.backlog-list {
    flex: 1 1 auto; min-height: 0;
    overflow-y: auto; display: flex; flex-direction: column; gap: 6px; padding-right: 4px;
}

/* 2K and up: a 300px rail wastes the space, so the backlog doubles in width and
   lists its cards in two columns. The threshold sits above a maximised FHD
   window (~1905px of viewport) and below DCI 2K (~2033px), so only genuinely
   wide screens get it; the board still keeps ~1900px at 2560.
   Must come after the rules it overrides — equal specificity, so source order
   decides and a media query alone would lose. */
@media (min-width: 2000px) {
    .plan-main { grid-template-columns: 600px minmax(0, 1fr); }
    .backlog-list {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        align-content: start;
    }
}

/* ------------------------------------------------------------------ card */
/* Two independent signals share the card, so they must not fight for the same
   pixels: the PROJECT is the fill + left stripe, the STATE (готово / просрочено)
   is a ring drawn outside the border box. A ring keeps the stripe readable,
   which a border-colour swap would paint over. */
.pcard {
    --proj: #22a35d;                    /* всё остальное — зелёный */
    --proj-bg: rgba(34, 163, 93, .13);
    position: relative;
    background-color: var(--panel-2);
    background-image: linear-gradient(var(--proj-bg), var(--proj-bg));
    border: 1px solid var(--line);
    border-left: 4px solid var(--proj);
    border-radius: 8px;
    padding: 6px 8px;
    cursor: grab;
    user-select: none;
    touch-action: none; /* pointer events own the gesture, not the scroller */
}
.pcard[data-proj="LK"]   { --proj: #3b82f6; --proj-bg: rgba(59, 130, 246, .14); }  /* синий */
.pcard[data-proj="CRM"]  { --proj: #8b5cf6; --proj-bg: rgba(139, 92, 246, .14); }  /* фиолетовый */
.pcard[data-proj="UAPI"] { --proj: #f59e0b; --proj-bg: rgba(245, 158, 11, .15); }  /* оранжевый */

.pcard:hover { border-top-color: var(--muted); border-right-color: var(--muted); border-bottom-color: var(--muted); }
.pcard.dragging { opacity: .35; }
.pcard-ghost { border-style: dashed; opacity: .55; }

/* Terminal (Closed / Delivered / Resolved) — снято с повестки.
   Название НЕ перечёркиваем: зелёной обводки достаточно, чтобы понять, что
   задача закрыта, а перечёркнутый текст читается заметно хуже — а название на
   доске именно читают (замечание владельца). Так же в списке задач диаграммы
   (.gt-pool-done). */
.pcard-done { box-shadow: 0 0 0 2px var(--done); }
/* Планируемый срок в прошлом, а задача не завершена. */
.pcard-late { box-shadow: 0 0 0 2px var(--blocked); }

/* overflow:hidden is what keeps a narrow card's badges out of the × gutter:
   without it the auto margin pushes the priority chip past the padding and
   straight under the button. The project chip gives up space first — the key
   and the priority are the two things worth keeping legible. */
.pcard-top { display: flex; align-items: center; gap: 6px; margin-bottom: 2px; overflow: hidden; }
.pcard-key { flex: 0 0 auto; color: var(--brand); font-weight: 700; font-size: 12px; text-decoration: none; }
.pcard-key:hover { text-decoration: underline; }
.pcard-proj {
    flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    font-size: 10px; color: var(--muted); border: 1px solid var(--line); border-radius: 4px; padding: 0 4px;
}
/* Only the placement — the badge's own look comes from .pb (see below). */
.pcard-prio { flex: 0 0 auto; margin-left: auto; }
.pcard-warn { margin-left: auto; color: var(--queue); }
/* Story points live in the top-right corner, next to the × — see cardEl() in
   planning.js. margin-left:auto pins them there whatever the project chip does. */
.pcard-sp {
    flex: 0 0 auto; margin-left: auto;
    font-size: 10px; font-weight: 700; color: var(--ink-soft);
    border: 1px solid var(--line); border-radius: 4px; padding: 0 5px; background: var(--panel);
}
/* "?" = не оценено. Dashed and muted so it reads as a gap rather than a value,
   but the same shape, so the corner doesn't shift once SP appear. */
.pcard-sp-none { color: var(--muted); border-style: dashed; }
/* The × sits in that same corner (absolute), so a card carrying one has to keep
   its top row clear — otherwise the SP chip ends up underneath the button. */
.pcard-hasx .pcard-top { padding-right: 22px; }
.pcard-sum {
    font-size: 12px; color: var(--ink-soft); line-height: 1.25;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.pcard-bot { margin-top: 4px; display: flex; align-items: center; gap: 6px; overflow: hidden; }
/* Only the shrinking behaviour — a long status must ellipsis rather than push
   the cost and priority chips off a one-column-wide timeline bar. */
.pcard-status { max-width: 100%; overflow: hidden; text-overflow: ellipsis; }

/* --- soft badges ON A CARD ------------------------------------------------
   A card is the one surface in the app that is already tinted (by project), so
   a translucent badge tint would land on tint-over-tint and its contrast would
   depend on which project the card belongs to. Measured that way it fell to
   3.87:1 (In Review on a UAPI card) and failed on 7 of 8 project/theme
   combinations.
   Fix: give the badge its own OPAQUE backdrop — --panel underneath, the tint
   painted over it as a background-image layer. The project tint is then no
   longer part of the stack, so contrast is exactly what it is everywhere else
   (5.39 light / 5.31 dark) and cannot be broken by adding a project colour.
   Written with the theme attribute in the selector so it outranks the generic
   [data-theme=…] .st rule instead of relying on source order. */
[data-theme="dark"] .pcard .st,  [data-theme="dark"] .pcard .pb,
[data-theme="dark"] .pcard .wt {
    background-color: var(--panel);
    background-image: linear-gradient(var(--st-bg-d), var(--st-bg-d));
}
[data-theme="light"] .pcard .st, [data-theme="light"] .pcard .pb,
[data-theme="light"] .pcard .wt {
    background-color: var(--panel);
    background-image: linear-gradient(var(--st-bg-l), var(--st-bg-l));
}
/* Cards are dense — a timeline bar can be a single column wide — so the badges
   drop the letter-spacing and tighten the padding here, keeping the same size,
   shape and case as everywhere else. Measured: that brings the label back to
   the width the old solid chip had (Dev Pause 61px either way, To Do slightly
   narrower); only the longest statuses stay a few pixels wider, because
   uppercase simply is. They ellipsis, which they did before too. */
.pcard .st, .pcard .pb { padding: 0 4px; letter-spacing: 0; line-height: 1.6; }

/* Ghost that follows the cursor while dragging. */
.pcard-floating {
    position: fixed; z-index: 900; pointer-events: none;
    box-shadow: var(--shadow); opacity: .95; transform: rotate(-1deg);
}

/* -------------------------------------------------------------- timeline */
.board-controls {
    display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
    padding-bottom: 10px; margin-bottom: 4px; border-bottom: 1px solid var(--line);
}
.tl-scroll { overflow-x: auto; overflow-y: hidden; }
/* Полоса дат приклеена к верху ЭТОЙ области, а не к экрану, и к экрану её
   приклеить нельзя: у обёртки с overflow-x:auto вычисленный overflow-y тоже
   становится auto, обёртка сама и есть scrollport, и viewport-относительный
   sticky она проглатывает (тот же вывод, что у .table-sticky выше).
   Поэтому вертикальный скролл достаётся обёртке — но только в шелле
   (body.page-planning ниже), где её высота равна экрану и «прилипло к верху
   области» и есть «прилипло к верху экрана». В узкой раскладке шелла нет,
   область по высоте содержимого, и sticky здесь просто ничего не делает —
   страница скроллится целиком, как и на всех остальных страницах. */
.tl-head {
    display: flex; position: sticky; top: 0; z-index: 2; background: var(--panel);
    /* Карточки уезжают ПОД шапку, и без этой черты они на неё наползают: у
       границы колонки (border-left у .tl-col) горизонтального аналога нет. */
    box-shadow: inset 0 -1px 0 var(--line);
}
.tl-col {
    flex: 0 0 auto; text-align: center; font-size: 11px; font-weight: 600;
    color: var(--muted); padding: 6px 2px; border-left: 1px solid var(--line);
    white-space: nowrap; overflow: hidden;
}
/* Текущая колонка в шапке: тот же тон, что у полосы на сетке, чтобы шапка и
   полоса читались одной колонкой, плюс черта по низу — она стыкует их встык. */
.tl-col-now {
    color: var(--brand); font-weight: 700; background: var(--now-tint);
    box-shadow: inset 0 -2px 0 var(--brand);
}
.tl-grid {
    position: relative; min-height: 140px;
    background-image:
        linear-gradient(to right, var(--line) 1px, transparent 1px),
        linear-gradient(to bottom, var(--line) 1px, transparent 1px);
}

/* Текущий период — то, от чего отсчитывают всё остальное на доске, поэтому он
   выделен на всю высоту сетки, а не только подсветкой ячейки заголовка: у
   доски бывают десятки строк, и на дальних от шапки эта подсветка не видна
   вовсе. Полоса отвечает на «где мы сейчас» (брендовый тон), красная линия — на
   «какой сегодня день» внутри периода (в месячной гранулярности это половина
   ответа). Два разных цвета намеренно: это два разных утверждения, и красный
   здесь единственный, поэтому его нельзя спутать с заливкой периода.
   Оба слоя лежат ПОД карточками (z-index) и обязаны иметь pointer-events:none:
   сброс карточки из бэклога считается по координатам курсора, а перехваченный
   pointerdown сломал бы перетаскивание всего, что окажется над этой колонкой. */
.tl-now, .tl-today { position: absolute; top: 0; bottom: 0; z-index: 0; pointer-events: none; }
.tl-now { background: var(--now-tint); }
/* Линия сегодняшнего дня — КРАСНАЯ (просьба владельца, и это же общий язык
   гантов). Именно --danger-text, а не палитровый --blocked: на тёмной панели
   #dc2626 даёт к полосе всего 2.7:1, тогда как per-theme вариант — 4.33:1
   (тёмная) и 5.16:1 (светлая). Прозрачности нет: единственный красный элемент
   доски обязан быть своего цвета, а не смесью с тоном полосы под ним. */
.tl-today { width: 2px; background: var(--danger-text); }
/* Точка в начале линии: без неё вертикальная черта читается как ещё одна
   граница колонки, которых на сетке и так много. */
.tl-today::before {
    content: ''; position: absolute; left: -3px; top: 0;
    width: 8px; height: 8px; border-radius: 50%; background: var(--danger-text);
}
/* Three lines have to fit, and the × needs its own gutter on the right so it
   stops landing on the priority badge (which is pushed right by margin-left).
   Content is centred vertically rather than stacked from the top, which is
   what left the summary sitting on the bottom edge. Height carries a second
   summary line (see .pcard-sum override below) — a one-week bar is only one
   column wide, and a single clamped line was cutting off most of the title. */
.tl-card {
    position: absolute; height: 76px; overflow: hidden;
    padding: 6px 8px;
    display: flex; flex-direction: column; justify-content: center; gap: 2px;
    background-color: var(--panel); box-shadow: var(--shadow); z-index: 1;
}
/* The × gutter comes from .pcard-hasx — the top row is the only one that needs
   it, and both boards attach the button, so the rule lives with the card. */
.tl-card .pcard-top { margin-bottom: 0; }
.tl-card .pcard-bot { margin-top: 0; }
/* The state ring has to survive the card's own drop shadow. */
.tl-card.pcard-done { box-shadow: 0 0 0 2px var(--done), var(--shadow); }
.tl-card.pcard-late { box-shadow: 0 0 0 2px var(--blocked), var(--shadow); }
.tl-card .pcard-sum { -webkit-line-clamp: 2; }
.tl-card.dragging { z-index: 10; opacity: .85; }

/* Полоса выходит за окно: срез вместо скругления, пунктирная кромка и
   растушёвка цветом проекта. Без этого обрезанная карточка выглядела ровно как
   начинающаяся в первой колонке, а это разные вещи — настоящие даты в подсказке
   (fmtPlanRange в planning.js). */
.tl-card.tl-clip-l {
    border-top-left-radius: 0; border-bottom-left-radius: 0;
    border-left-style: dashed;
}
.tl-card.tl-clip-r {
    border-top-right-radius: 0; border-bottom-right-radius: 0;
    border-right: 1px dashed var(--proj);
}
.tl-card.tl-clip-l::before,
.tl-card.tl-clip-r::after {
    content: ''; position: absolute; top: 0; bottom: 0; width: 24px;
    /* Обязательно: под растушёвкой лежит ручка изменения размера (7px у самого
       края), и без этого псевдоэлемент перехватывал бы и её, и драг карточки. */
    pointer-events: none;
    opacity: .3;
}
.tl-card.tl-clip-l::before { left: 0; background: linear-gradient(to right, var(--proj), transparent); }
.tl-card.tl-clip-r::after { right: 0; background: linear-gradient(to left, var(--proj), transparent); }
/* Текст поверх растушёвки: строки не позиционированы, поэтому иначе рисуются
   НИЖЕ позиционированного псевдоэлемента и уходили бы под заливку. */
.tl-card .pcard-top, .tl-card .pcard-sum, .tl-card .pcard-bot { position: relative; z-index: 1; }

/* right:8px clears the 7px resize handle underneath it. */
.tl-x {
    position: absolute; top: 3px; right: 8px; z-index: 3;
    width: 16px; height: 16px; line-height: 14px; text-align: center;
    border: 0; border-radius: 4px; background: transparent;
    color: var(--muted); cursor: pointer; font-size: 14px; padding: 0;
}
.tl-x:hover { background: var(--blocked); color: #fff; }

/* ---- живая обратная связь при перетаскивании -----------------------------
   Куда встанет карточка, пока кнопку не отпустили. До этого таймлайн не
   показывал ничего, и узнать место можно было только отпустив (просьба
   владельца). Пунктир, а не заливка: это ещё не карточка, а обещание места.
   Анимации короткие (120–130ms) и только на появление/сдвиг — на доске, где
   тащат мышью, всё длиннее читается как задержка интерфейса. */
.tl-drop {
    position: absolute; height: 60px; z-index: 0; pointer-events: none;
    border: 2px dashed var(--brand); border-radius: 8px;
    background: rgba(111, 168, 255, .10);
    animation: tl-drop-in 120ms ease-out;
}
.tl-drop.hidden { display: none; }
@keyframes tl-drop-in { from { opacity: 0; transform: scale(.97); } to { opacity: 1; transform: none; } }
/* Вставка НОВОЙ строки: превью тоньше и лежит на границе строк, а не в строке —
   это другое утверждение («здесь появится строка»), и выглядеть оно должно
   иначе. Карточки ниже в этот момент отъезжают вниз (transform в JS). */
.tl-drop-insert {
    height: 6px; border-radius: 3px; margin-top: -3px;
    border: 0; background: var(--brand); opacity: .85;
}
.tl-card { transition: transform 130ms ease-out; }
/* Перетаскиваемая карточка от этого перехода избавлена: она обязана следовать за
   курсором мгновенно, иначе тянется как на резинке. */
.tl-card.dragging { transition: none; }

/* Кнопки строк у правого края видимой части сетки. Появляются по наведению —
   постоянно висящий ряд кнопок на доске, где и так много цвета, читался бы как
   часть плана.
   Общие для ДВУХ сеток: таймлайна (bindRowControls) и диаграммы Ганта
   (gtRowControls). На диаграмме строка выше (34 против 86px), но кнопка
   центрируется в строке из JS, так что размер здесь один на оба места. */
.tl-rowbtn {
    position: absolute; z-index: 4; width: 22px; height: 22px; padding: 0;
    line-height: 20px; text-align: center; font-size: 14px; font-weight: 700;
    border: 1px solid var(--line); border-radius: 50%;
    background: var(--panel); color: var(--muted); cursor: pointer;
    animation: fade-in 120ms ease;
}
.tl-rowbtn.hidden { display: none; }
.tl-rowplus:hover { border-color: var(--brand); color: var(--brand); }
.tl-rowdel:hover { border-color: var(--blocked); background: var(--blocked); color: #fff; }
.row-modal-text { margin: 0 0 10px; }
.modal-body .hint { margin: 0; font-size: 12px; color: var(--muted); }

/* Диаграмма Ганта внутри карточки: «⊞ N» — расставлено задач, «⚠ N» — столько
   из них вылезло за сроки. Красный вариант — единственный способ узнать о
   конфликте не открывая панель, а возникает конфликт как раз здесь, на доске,
   когда правят сроки. Тот же размер и та же форма, что у чипа стоимости
   (.pcard-cost ниже) — это соседи на одной строке карточки. */
.pcard-gt {
    flex: 0 0 auto; font-size: 10px; font-weight: 700; line-height: 1.6;
    padding: 0 5px; border-radius: 4px; cursor: help;
    border: 1px solid var(--line); color: var(--muted); background: var(--panel-2);
}
.pcard-gt-bad { border-color: var(--blocked); color: var(--danger-text); }

.tl-h { position: absolute; top: 0; bottom: 0; width: 7px; cursor: ew-resize; z-index: 2; }
.tl-h-l { left: 0; }
.tl-h-r { right: 0; }
.tl-h:hover { background: var(--brand); opacity: .5; }

/* ---------------------------------------------------------------- matrix */
.mx-wrap {
    display: grid;
    grid-template-columns: 22px minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr) 22px;
    gap: 6px;
}
.mx-axis { display: flex; align-items: center; justify-content: center; color: var(--muted); font-size: 11px; font-weight: 600; }
.mx-axis-y { grid-column: 1; grid-row: 1; }
.mx-axis-y span { writing-mode: vertical-rl; transform: rotate(180deg); }
.mx-axis-x { grid-column: 2; grid-row: 2; }
.mx-grid {
    grid-column: 2; grid-row: 1;
    display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; gap: 8px;
}
/* Each quadrant carries its own colour: быстрые победы зелёный, крупные ставки
   синий, мелочи серый, неблагодарные красный. The wash stays faint because the
   cards inside are already tinted by project. */
.mx-q[data-quadrant="0"] { --q: #22a35d; --q-bg: rgba(34, 163, 93, .07); }
.mx-q[data-quadrant="1"] { --q: #3b82f6; --q-bg: rgba(59, 130, 246, .07); }
.mx-q[data-quadrant="2"] { --q: #9aa5b5; --q-bg: rgba(154, 165, 181, .08); }
.mx-q[data-quadrant="3"] { --q: #dc2626; --q-bg: rgba(220, 38, 38, .07); }
.mx-q {
    border: 1px solid var(--line);
    border-top: 3px solid var(--q);
    border-radius: 8px;
    background-color: var(--input-bg);
    background-image: linear-gradient(var(--q-bg), var(--q-bg));
    min-height: 220px; display: flex; flex-direction: column;
}
.mx-q-head {
    display: flex; justify-content: space-between; align-items: center;
    padding: 6px 10px; border-bottom: 1px solid var(--line);
    font-weight: 700; font-size: 12px; color: var(--q);
}
.mx-q-n { color: var(--muted); font-weight: 600; }
/* Two columns: a quadrant is wide enough that one column wasted most of it.
   They are two real lists, not one list reflowed by the grid, because the column
   is part of what gets SAVED (packed into plan_matrix_items.sort_order, see
   Planning::encodeSlot). Reflowing meant a drop renumbered the quadrant and every
   card after the new one changed column. */
.mx-q-list {
    padding: 8px; display: flex; gap: 6px;
    overflow-y: auto; flex: 1; min-height: 0;
}
/* stretch (the flex default) is load-bearing: it makes each column as tall as
   the quadrant, so the empty space below the last card is still a drop target. */
.mx-col { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 6px; }

/* Live drag feedback. The dragged card leaves the flow and this slot takes its
   place at the drop position, sized to the card in JS so the neighbours move
   exactly as far as they will have to (they slide — see slideCards()). */
.mx-lifted { display: none; }
.mx-slot {
    flex: 0 0 auto;
    border: 1px dashed var(--brand); border-radius: 8px;
    background: var(--hover);
}

/* --- filter row: search sits inline with the dropdowns --------------------- */
/* display:contents lets the injected multiselects participate in the parent
   flex row directly, so JS can keep rewriting only this container. */
.filter-controls { display: contents; }
/* flex and min-width already come from .filter-row input[type=search] above,
   which outranks a bare class; this only caps the width so the box doesn't
   stretch across half a 2K screen. Written as input.search-inline to match that
   rule's specificity and win on source order. */
.filter-row input.search-inline { max-width: 360px; }

/* --- business value table -------------------------------------------------- */
.plan-main.no-backlog { grid-template-columns: minmax(0, 1fr); }
.backlog.hidden { display: none; }

/* Ширины заданы через <colgroup> (его рисует valueHead), а НЕ через
   th:nth-child: шапка двухуровневая, и в верхней строке номер ячейки давно не
   равен номеру колонки — там rowspan'ы и один colspan на всю группу RICE. */
table.bv { table-layout: fixed; width: 100%; }
table.bv th, table.bv td { vertical-align: top; }
/* Проценты отданы тексту, пиксели — оценкам: числовых колонок шесть, и на
   ноутбучной ширине (1280) горизонтальный скролл появляется именно из-за них.
   Посчитано: 62% + 470px умещается в 1280. */
table.bv .bv-col-task { width: 22%; }
table.bv .bv-col-customer { width: 10%; }
table.bv .bv-col-goal { width: 15%; }
table.bv .bv-col-bizvalue { width: 15%; }
table.bv .bv-col-priority { width: 104px; }
table.bv .bv-col-reach,
table.bv .bv-col-impact,
table.bv .bv-col-confidence,
table.bv .bv-col-effort { width: 54px; }
table.bv .bv-col-value,
table.bv .bv-col-score { width: 62px; }
/* Последняя колонка — только кнопка «убрать со вкладки», заголовка у неё нет. */
table.bv .bv-col-x { width: 34px; }
.bv-sum { white-space: normal; word-break: break-word; }

/* Ключ, статус и название в одной ячейке: ключ со статусом строкой выше,
   название под ними. Так три прежние колонки занимают место одной, и именно это
   освободило ширину под пять колонок RICE. */
.bv-task-top { display: flex; align-items: center; gap: 6px; margin-bottom: 3px; }
.bv-task-top .key { font-size: 11px; }

/* Заголовок группы RICE над пятью её колонками. */
table.bv th.bv-grp {
    text-align: center; letter-spacing: .5px; color: var(--ink-soft);
    border-bottom: 1px solid var(--line);
}
/* Значок ⓘ в шапке колонки «C»: открывает шпаргалку по шкале уверенности.
   Кнопка без рамки и фона, чтобы не спорить с самим заголовком, но с фокусом —
   до неё добираются и с клавиатуры. */
.bv-hint {
    background: none; border: 0; padding: 0 0 0 3px; margin: 0;
    font: inherit; font-size: 11px; line-height: 1; color: var(--muted); cursor: help;
}
.bv-hint:hover { color: var(--brand); }
.bv-hint:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; border-radius: 3px; }

/* Ссылка, которая на самом деле кнопка: открывает ту же шпаргалку из блока
   справки. Настоящая <button> ради клавиатуры, а выглядит как ссылка, потому
   что стоит внутри текста. */
.linklike {
    background: none; border: 0; padding: 0; font: inherit; color: var(--brand);
    text-decoration: underline; text-underline-offset: 2px; cursor: pointer;
}
.linklike:hover { text-decoration-thickness: 2px; }

/* Шпаргалка по Confidence: широкая карточка и таблица, в которой читают
   ПРАВУЮ колонку, поэтому текст переносится, а не жмётся в одну строку. */
.conf-card { max-width: 760px; }
.conf-table { width: 100%; border-collapse: collapse; font-size: 13px; margin-top: 10px; }
.conf-table th, .conf-table td {
    padding: 8px 10px; border-bottom: 1px solid var(--line);
    text-align: left; vertical-align: top; white-space: normal;
}
.conf-table th { font-size: 11px; text-transform: uppercase; letter-spacing: .5px; color: var(--muted); }
.conf-table tbody tr:last-child td { border-bottom: 0; }
.conf-table .conf-n {
    width: 52px; font-weight: 700; color: var(--brand);
    font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* Сортируемый заголовок — курсор и подсветка активной колонки. Стрелку рисует
   JS текстом: своего места она не занимает, а в узкой колонке «R» это важно. */
table.bv th.bv-th-sort { cursor: pointer; user-select: none; }
table.bv th.bv-th-sort:hover { color: var(--brand); }
table.bv th.bv-th-on { color: var(--brand); }
/* Узкие числовые поля RICE: по центру, без стрелок-спиннеров — те съедают
   половину и без того узкой ячейки. */
input.bv-num {
    text-align: center; padding: 4px 2px; -moz-appearance: textfield;
    appearance: textfield;
}
input.bv-num::-webkit-outer-spin-button,
input.bv-num::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
/* Обе посчитанные колонки — не поля ввода, поэтому им нужен свой отступ
   сверху: иначе число стоит выше, чем текст в соседних инпутах. */
.bv-value, .bv-score { padding-top: 9px; }
.bv-score { font-weight: 700; }

/* Editable cells write straight to Jira; the states below are the only signal
   the user gets that a save is in flight or was rejected. */
.bv-edit {
    width: 100%; font: inherit; font-size: 12px;
    background: var(--input-bg); color: var(--ink);
    border: 1px solid var(--line); border-radius: 6px; padding: 4px 6px;
    resize: vertical;
}
.bv-edit:focus { outline: none; border-color: var(--brand); }
.bv-edit:disabled { opacity: .6; cursor: progress; }
.bv-saving { border-color: var(--queue); }
.bv-ok { border-color: var(--done); box-shadow: 0 0 0 2px rgba(34, 163, 93, .25); }
.bv-err { border-color: var(--blocked); box-shadow: 0 0 0 2px rgba(220, 38, 38, .25); }
/* Поля «Цель» и «Бизнес-ценность» растут под текст (autoGrow в planning.js),
   поэтому тянуть их мышкой не нужно и ручка убрана. Потолок — страховка от
   чужой простыни: за ним появляется скролл внутри самого поля, и одна длинная
   запись не растягивает строку таблицы на весь экран. 320px — не круглое число
   с потолка: самая длинная запись в базе (242 символа) на узком окне (1180px,
   где колонка уже всего) требует 273px, и потолок обязан быть выше неё, иначе
   страховка сработает на реальных данных. */
textarea.bv-edit {
    min-height: 46px; line-height: 1.3; resize: none;
    max-height: 320px; overflow-y: hidden;
}
/* Полосу прокрутки получает только поле, упёршееся в потолок; класс ставит
   markCapped в planning.js. С `overflow-y: auto` на всех полоса появлялась и
   там, где прокручивать нечего: высота ставится целым числом пикселей, а текст
   бывает на доли пикселя выше. */
textarea.bv-edit.bv-capped { overflow-y: auto; }

/* Два чекбокса вкладки живут в той же полосе, что контролы таймлайна, — это
   один и тот же жанр «настройки этой доски», и выглядеть они должны одинаково. */
.bv-tools { padding-bottom: 8px; }
/* This tab's own filter block, kept deliberately smaller than the backlog's
   (.ms-btn/search there stay full-size) — the ask here was specifically to
   take up as little room as possible, sharing the one .bv-tools row with the
   two checkboxes instead of a row of its own. */
.bv-search {
    width: 140px; padding: 5px 8px; border: 1px solid var(--line);
    border-radius: 6px; font-size: 12px;
}
.bv-ms .ms-btn { padding: 4px 8px; font-size: 12px; min-width: 84px; }
/* Строка, которую видно ТОЛЬКО из-за «Показать скрытые»: приглушена, как
   задачи «вне периодов» в панели сравнения (.drill-item-off). Не opacity на
   всей строке — под ней редактируемые поля, и гасить их вместе с текстом
   значит гасить и то, что человек сейчас правит. */
/* Зебра. Строки здесь разной высоты — «Цель» и «Бизнес-ценность» растут под
   текст, — и глаз терял, какие ячейки относятся к одной задаче (замечание
   владельца). Устройство то же, что у таблицы метрик на «Сравнении»: чётные
   строки, чтобы первая осталась на фоне панели, и красим td, а не tr — у tr фон
   перекрывался бы фоном ячеек при border-collapse. */
table.bv tbody tr:nth-child(even) td { background: var(--zebra); }
/* И подсветка строки под курсором — зебра общая с другими таблицами и намеренно
   очень мягкая (она работает периферийным зрением и не должна читаться как
   наведение), а здесь 12 колонок и высокие строки, поэтому нужен ещё и точный
   ответ «вот эта строка». Правило стоит ПОСЛЕ зебры: специфичность у них equal,
   и побеждает последнее. `--hover` — ровно его назначение, реакция на курсор. */
table.bv tbody tr:hover td { background: var(--hover); }

.bv-row-hidden td { color: var(--muted); }
.bv-row-hidden .bv-sum { opacity: .7; }
/* Кнопка «убрать со вкладки» / «вернуть». Тот же × и та же красная реакция, что
   у .tl-x на карточке, потому что действие того же рода — убрать с этого экрана,
   не тронув Jira. Здесь она в своей ячейке, а не позиционирована абсолютно. */
.bv-x {
    width: 22px; height: 22px; line-height: 20px; text-align: center;
    border: 1px solid var(--line); border-radius: 4px; background: transparent;
    color: var(--muted); cursor: pointer; font-size: 13px; padding: 0;
}
.bv-x:hover { background: var(--blocked); border-color: var(--blocked); color: #fff; }

/* ==========================================================================
   «Планирование» как страница-приложение: доска занимает ровно высоту экрана.

   Причина не косметическая. Доска — единственная страница, где содержимое не
   читают сверху вниз, а РАБОТАЮТ с ним: тянут карточки, сверяют колонки, ищут
   строку. Пока страница скроллилась целиком, это давало два скролла один в
   другом (сначала внутренний у доски, потом внешний у страницы), и полоса дат
   с шапкой квадрантов уезжала из вида ровно тогда, когда она нужнее всего.
   Теперь скроллится только содержимое доски, а фильтры, шапка и полоса дат
   стоят на месте всегда.

   Ограничено шириной (ниже 1100px колонки складываются в одну — там честный
   скролл страницы уместнее) и ВЫСОТОЙ: на коротком окне overflow:hidden без
   скролла означает обрезанное содержимое без способа до него добраться.
   ========================================================================== */
@media (min-width: 1101px) and (min-height: 560px) {
    body.page-planning {
        height: 100vh;
        /* dvh там, где он поддержан: 100vh на мобильных считается по свёрнутой
           панели браузера, то есть часть страницы уезжает под неё. */
        height: 100dvh;
        display: flex; flex-direction: column;
        overflow: hidden;
    }
    /* На уровне страницы больше ничего не скроллится, поэтому всей обвязке нужно
       только не сжиматься. Оверлеи (сайд-меню, панель задачи, лоадер) — position:
       fixed, они из потока выпадают и flex-элементами не становятся. */
    body.page-planning > .topbar,
    body.page-planning > .banner,
    body.page-planning > .refresh-status { flex: 0 0 auto; }
    body.page-planning > main.plan-main {
        flex: 1 1 auto; min-height: 0;
        padding-bottom: 12px;
        /* Сетка из двух колонок в одну строку известной высоты; align-items:start
           выше растянуться бы не дал. */
        grid-template-rows: minmax(0, 1fr);
        align-items: stretch;
    }
    /* Бэклог: высота теперь ровно строка сетки, поэтому ни sticky, ни замеренный
       max-height (syncBacklogHeight) ему больше не нужны — внутренний скролл
       списка уже описан у .backlog-list. */
    body.page-planning .backlog {
        position: static; max-height: none; margin-bottom: 0;
    }
    /* :not(.hidden) обязателен: display:flex здесь специфичнее, чем .hidden,
       и без этого невидимые доски стали бы видимыми. */
    body.page-planning .board:not(.hidden) {
        display: flex; flex-direction: column;
        min-height: 0; margin-bottom: 0;
    }
    body.page-planning .board > .board-controls,
    body.page-planning .board > details.help,
    body.page-planning .board > h2 { flex: 0 0 auto; }
    /* В каждой доске тянется ровно одна часть — та, внутри которой скролл.
       Именно здесь у таймлайна появляется вертикальный скролл, и вместе с ним
       начинает работать sticky у .tl-head (см. комментарий там). */
    body.page-planning .tl-scroll { flex: 1 1 auto; min-height: 0; overflow: auto; }
    body.page-planning .mx-wrap { flex: 1 1 auto; min-height: 0; }
    /* 220px на квадрант — это про страницу, которая тянется вниз. Здесь четыре
       квадранта делят фиксированную высоту, и каждый скроллит свой список сам
       (.mx-q-list уже с overflow-y: auto). */
    body.page-planning .mx-q { min-height: 0; }
    body.page-planning #board-value .table-scroll { flex: 1 1 auto; min-height: 0; overflow: auto; }
    /* Раз область стала scrollport'ом, шапка таблицы может к ней прилипнуть —
       тем же приёмом и по той же причине, что .table-sticky на дашборде. */
    body.page-planning #value-table thead th {
        position: sticky; top: 0; z-index: 3;
        background: var(--panel);
        box-shadow: inset 0 -1px 0 var(--line);
    }
}

/* --- ФОТ ------------------------------------------------------------------- */
.payroll-table .roles-cell { gap: 4px; }        /* layout comes from .cell-row */
.payroll-table td { vertical-align: middle; }
.role-chip {
    font-size: 10px; font-weight: 600; color: var(--ink-soft);
    border: 1px solid var(--line); border-radius: 10px; padding: 1px 7px;
    background: var(--panel-2); white-space: nowrap;
}
.row-inactive { opacity: .55; }
.salary-input {
    width: 130px; text-align: right; font: inherit; font-size: 13px; font-variant-numeric: tabular-nums;
    background: var(--input-bg); color: var(--ink);
    border: 1px solid var(--line); border-radius: 6px; padding: 4px 8px;
}
.salary-input:focus { outline: none; border-color: var(--brand); }
.salary-input:disabled { opacity: .6; cursor: progress; }
.role-select {
    background: var(--input-bg); color: var(--ink); font: inherit; font-size: 12px;
    border: 1px solid var(--line); border-radius: 6px; padding: 3px 6px;
}

/* --- стоимость -------------------------------------------------------------- */
.cost-warn { color: var(--queue); font-size: 11px; }
.pcard-cost {
    flex: 0 0 auto; font-size: 10px; font-weight: 700; color: var(--ink-soft);
    border: 1px solid var(--line); border-radius: 4px; padding: 0 5px;
    background: var(--panel); white-space: nowrap;
}
/* Dashed edge marks a figure that is missing somebody's rate. */
.pcard-cost-partial { border-style: dashed; border-color: var(--queue); }

/* --- "Сравнение периодов" ---------------------------------------------- */
table.cmp-table { width: 100%; border-collapse: collapse; font-size: 13px; }
table.cmp-table th, table.cmp-table td {
    padding: 7px 10px; border-bottom: 1px solid var(--line); text-align: left; white-space: nowrap;
}
table.cmp-table th { color: var(--muted); font-weight: 600; font-size: 12px; text-transform: uppercase; }
table.cmp-table td:first-child { color: var(--ink); font-weight: 500; white-space: normal; }
/* ---- только таблица КЛЮЧЕВЫХ МЕТРИК ------------------------------------
   Правила ниже адресуют `#cmp-table`, а НЕ класс `.cmp-table`: класс носят три
   таблицы страницы — метрики, статусы (`#cmp-status-table`) и стори/эпики
   (`#cmp-parents-table`). Поймано замером: `width: auto` на классе распустил
   восьмиколоночную таблицу стори/эпиков до 1078px и увёл страницу в
   горизонтальный скролл, а зебра включилась там, где её не просили.

   Ширина по содержимому, а не 100%: растянутая таблица разносила четыре колонки
   по 1900px, и чтобы сверить метрику с её числами, приходилось вести глазами
   через полэкрана (замечание владельца). Стало 760px в панели 1849px. Справа
   остаётся пустое место — оно ничего не сообщало и раньше. max-width, чтобы на
   узком экране скролл достался обёртке (.table-scroll), а не панели. */
#cmp-table { width: auto; max-width: 100%; }
/* Табличные цифры — чтобы разряды были одной ширины и числа стояли друг под
   другом от строки к строке.
   Выравнивание остаётся ЛЕВЫМ, хотя для чисел напрашивается правое: у части
   значений после числа идёт примечание («37 (готово 33, закрыто 4)»), и по
   правому краю прижималось бы примечание, а сами числа расходились бы тем
   сильнее, чем длиннее хвост. Проверено на живых данных: при левом все значения
   начинаются на одном отступе. Поэтому здесь не как в `table.cmp-status` — там
   в ячейке только число, и правое выравнивание уместно. */
#cmp-table td:not(:first-child) { font-variant-numeric: tabular-nums; }
/* Зебра. Чётные строки, чтобы первая осталась на фоне панели. Красим td, а не
   tr: у tr фон перекрывался бы фоном ячеек при border-collapse. */
#cmp-table tbody tr:nth-child(even) td { background: var(--zebra); }

/* ---- тренд: график всех метрик справа от таблицы ------------------------
   Занял место, освободившееся после сжатия таблицы, и отвечает на вопрос,
   которого таблица не задаёт вовсе: сдвиг между двумя периодами — это тренд или
   разовый скачок. Двум числам это недоступно в принципе.

   Здесь стояли спарклайны построчно (104px на строку) — сняты по просьбе
   владельца: на строке метрики такой график слишком мелкий, чтобы что-то
   сказать, а места справа хватает на настоящий. */
.cmp-metrics-split { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 18px; align-items: start; }
/* minmax(0, 1fr) у колонки графика, а не 1fr: у grid-элемента min-width: auto,
   и канвас Chart.js, оставшийся шире трека до своего пересчёта, распёр бы всю
   страницу — та же ловушка, что у .grid-2 и .flow-split (§9). */
.cmp-trend-box { min-width: 0; }
.cmp-trend-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; margin-bottom: 6px; font-size: 13px; }
/* Ось Y нормированная, и это надо говорить вслух: без подписи график читался бы
   как абсолютные величины (см. renderTrendChart). */
.cmp-trend-note { color: var(--ink-soft); font-size: 12px; }
.cmp-trend-chart { position: relative; height: 300px; overflow: hidden; }

/* Легенда: четырнадцать позиций, поэтому в несколько рядов и компактно. По ней
   ВОДЯТ мышью — она же переключатель выделения, отсюда cursor и своя разметка
   вместо встроенной легенды Chart.js (та рисуется на канвасе). */
.cmp-trend-legend { display: flex; flex-wrap: wrap; gap: 2px 12px; margin-top: 8px; font-size: 12px; }
/* Позиция легенды — настоящая <button> (по ней кликают, значит она обязана
   работать с клавиатуры), поэтому браузерную обвязку кнопки надо снять руками.
   transition только на opacity: цвет у выделенной позиции меняется вместе с
   font-weight, а тот не анимируется — плавный цвет рядом со скачущим начертанием
   выглядит рассинхроном, поэтому цвет переключается сразу. */
.cmp-lg { display: inline-flex; align-items: center; gap: 5px; padding: 1px 3px; border: 0; border-radius: 4px;
          background: none; font: inherit; font-size: 12px; line-height: 1.5; text-align: left;
          cursor: pointer; color: var(--ink-soft); transition: opacity .1s; }
.cmp-lg:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.cmp-lg-dot { width: 9px; height: 9px; border-radius: 2px; flex: 0 0 auto; }
.cmp-lg-txt { white-space: nowrap; }
/* Приглушаем ТЕКСТ и точку вместе (opacity на всём элементе), а не только цвет:
   иначе точка остаётся яркой и продолжает притягивать глаз, то есть выделение
   не работает ровно там, где оно и нужно. */
.cmp-lg-dim { opacity: .35; }
.cmp-lg-on { color: var(--ink); font-weight: 600; }
/* Закрепление — ОТДЕЛЬНАЯ метка от выделения, и они накладываются: пока ховер
   ушёл на соседнюю линию, закреплённая приглушена как все, но обязана остаться
   узнаваемой — иначе непонятно, куда всё вернётся, когда мышь уедет.
   Поэтому рамка, а не цвет: цвет уже занят выделением. */
.cmp-lg-pin { background: var(--dp-band); box-shadow: inset 0 0 0 1px var(--brand); }
.cmp-lg-pin.cmp-lg-dim { opacity: .7; }

/* Выделенная строка таблицы — обратная связь от легенды. Не var(--hover): тот
   обязан остаться реакцией на курсор, а здесь строка подсвечивается, когда мышь
   в ДРУГОМ месте (та же причина, по которой существуют --now-tint / --zebra).
   !important — потому что зебра красит td напрямую и перебила бы фон строки. */
#cmp-table tbody tr { cursor: pointer; }
#cmp-table tbody tr.cmp-row-hi td { background: var(--dp-band) !important; }
/* Закреплённая строка помечена полосой слева, а не фоном: фон занят выделением,
   и пока ховер на другой строке, закреплённая обязана остаться отличимой. */
#cmp-table tbody tr.cmp-row-pin td:first-child { box-shadow: inset 3px 0 0 var(--brand); }
/* --- status table: two-level header, one group of columns per period -----
   The two periods carry the same three figures, so without a visible seam the
   eye loses which Σ belongs to which period. A left rule on the first cell of
   each group draws that seam once per row instead of boxing every cell. */
/* A row runs across eight columns, so highlighting it is what keeps the eye on
   one status while reading right. No cursor change: unlike the Стори/Эпики
   table the row itself is not clickable — only the Σ cells are buttons. */
table.cmp-status tbody tr:hover td { background: var(--hover); }
table.cmp-status th.cmp-grp, table.cmp-status td.cmp-grp { border-left: 1px solid var(--line); }
table.cmp-status td.num, table.cmp-status th.num { text-align: right; }
table.cmp-status td.num { font-variant-numeric: tabular-nums; }
/* Sub-headers sit over numbers, so they line up on the right with them. */
table.cmp-status thead tr:nth-child(2) th { text-align: right; }
/* A period name spans its three columns and carries the hierarchy, so it is
   centred over the group and a shade stronger than the sub-headers. Selected
   by [colspan] rather than by class, so the rowspan'd Статус and Δ headers —
   which label single columns — keep their own alignment. */
table.cmp-status thead th[colspan] {
    text-align: center; color: var(--ink-soft); border-bottom: 1px solid var(--line);
}

.cmp-note { color: var(--muted); font-size: 11px; }
.cmp-delta { font-weight: 700; }
.cmp-delta-good { color: var(--success-text); }
.cmp-delta-bad { color: var(--danger-text); }
.cmp-delta-neutral { color: var(--muted); font-weight: 500; }
/* Clickable value in the metrics table — a real <button> (keyboard-reachable),
   styled to read as the plain number it replaces until hovered. */
.cmp-drill-link {
    font: inherit; color: inherit; background: none; border: 0; padding: 0;
    cursor: pointer; text-align: left; border-bottom: 1px dashed var(--muted);
}
.cmp-drill-link:hover { color: var(--brand); border-bottom-color: var(--brand); }
.cmp-drill-link:focus-visible { outline: 2px solid var(--accent-solid); outline-offset: 2px; }
.cmp-donut-col { min-width: 0; }
.cmp-donut-title {
    margin: 0 0 8px; font-size: 12px; font-weight: 600; color: var(--muted);
    text-align: center; text-transform: uppercase; letter-spacing: .3px;
}

/* --- "Стори и эпики" ------------------------------------------------------ */
table.cmp-parents td { vertical-align: middle; }
/* Colour identifies the PROJECT, same palette as the planning board's cards
   (see .pcard above), so a project reads the same on both pages.
   It is a left-to-right fade inside the FIRST cell only — strongest at the
   row's left edge, gone by the end of the Стори/Эпик column. A flat wash
   across the whole row read as muddy, the light theme especially.
   Two details that keep it clean rather than grey:
     - the colour is held as an RGB triple so one gradient rule serves every
       project; only the triple changes per data-proj;
     - it fades to `rgba(<same hue>, 0)`, NOT to `transparent` — the latter is
       rgba(0,0,0,0), and interpolating towards it drags the midpoint through
       grey, which is exactly the dirty look being fixed. */
.cmp-parent-row {
    --proj-rgb: 34, 163, 93;               /* всё остальное — зелёный */
    cursor: pointer;
}
.cmp-parent-row[data-proj="LK"]   { --proj-rgb: 59, 130, 246; }
.cmp-parent-row[data-proj="CRM"]  { --proj-rgb: 139, 92, 246; }
.cmp-parent-row[data-proj="UAPI"] { --proj-rgb: 245, 158, 11; }

/* No fill anywhere: the project lives on the EDGES only. Nothing then sits on
   a coloured background, which is what kept breaking the contrast of the text
   and the type badge — and a wash behind 67 rows read as noise either way.
   Same 4px stripe as the planning board's cards (.pcard), so the two pages
   speak the same language. */
.cmp-parent-row > td:first-child {
    position: relative;
    border-left: 4px solid rgb(var(--proj-rgb));
}
/* The stripe bleeds along the bottom of the name cell and dies out at its right
   edge. Drawn ON TOP of the ordinary grey separator rather than replacing it:
   a gradient border would leave the row's line missing from the point it
   reaches zero to the end of the table, which reads as a rendering fault. */
.cmp-parent-row > td:first-child::after {
    content: '';
    position: absolute;
    left: 0; right: 0; bottom: -1px; height: 2px;
    background: linear-gradient(to right, rgb(var(--proj-rgb)), rgba(var(--proj-rgb), 0));
    pointer-events: none;
}
.cmp-parent-row:hover > td { background-color: var(--hover); }
.cmp-parent-name { white-space: normal; min-width: 260px; }
/* --ink-soft, not --muted: --muted only just clears 4.5:1 on a white panel, and
   the project tint underneath pushes it to 4.0. Still visibly secondary to the
   key above it, but readable on every tint in both themes (measured). */
.cmp-parent-summary { color: var(--ink-soft); font-size: 12px; margin-top: 2px; font-weight: 400; }
.cmp-parent-count { white-space: nowrap; }
.cmp-muted { color: var(--muted); }
.cmp-empty { color: var(--muted); text-align: center; padding: 14px 10px; }
.cmp-trophy { margin-left: 6px; white-space: nowrap; }
.cmp-ptype {
    display: inline-block; font-size: 10px; font-weight: 700; text-transform: uppercase;
    letter-spacing: .3px; padding: 1px 5px; border-radius: 4px; margin-right: 6px;
    border: 1px solid var(--line); color: var(--muted);
}
/* Per-theme text variants, not the raw palette hex: as 10px text on a panel
   the palette colours fall under 4.5:1 on the light theme (CLAUDE.md §6).
   The border follows the text so the two can't drift apart. */
.cmp-ptype-epic { color: var(--epic-text); border-color: currentColor; }
.cmp-ptype-story { color: var(--success-text); border-color: currentColor; }

/* The Story/Epic panel's heading is "{key}: {summary}" — the subject of the
   whole panel, so it gets more weight than the drill titles that are just a
   slice label. Sits on --bar, so it uses --bar-ink, not --ink (CLAUDE.md §6). */
.drill-head h2.drill-title-lg { font-size: 18px; line-height: 1.3; }
.drill-title-link { color: var(--bar-ink); text-decoration: none; border-bottom: 1px solid rgba(255, 255, 255, .35); }
.drill-title-link:hover { border-bottom-color: var(--bar-ink); }
/* The type badge is an outline chip, so on --bar (solid blue in the light
   theme) it must switch to --bar-ink — left on its panel colour, the Epic
   badge's dark purple measured 1.03:1 against that blue, i.e. invisible
   (CLAUDE.md §6). The status badge next to it brings its own tinted background
   and matching text colour, so it needs no such treatment. */
.drill-sub .cmp-ptype, .drill-sub .st, .drill-sub .pb { vertical-align: middle; }
.drill-sub .cmp-ptype { color: var(--bar-ink); border-color: rgba(128, 128, 128, .55); }
.drill-sub .cmp-sub-fact { margin-left: 8px; color: var(--bar-muted); font-weight: 600; }

/* Period markers on a child issue inside the Story/Epic panel. */
.cmp-marks { display: inline-flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
.cmp-mark {
    display: inline-block; font-size: 10px; font-weight: 600; padding: 1px 6px;
    border-radius: 4px; border: 1px solid transparent;
}
.cmp-mark-early { background: rgba(224, 169, 43, .22); border-color: var(--queue); color: var(--ink); }
.cmp-mark-late { background: rgba(59, 130, 246, .22); border-color: var(--accent-solid); color: var(--ink); }
.cmp-mark-none { display: inline-block; margin-top: 4px; color: var(--muted); font-style: italic; font-size: 11px; }
/* Everything under the epic is listed, so the part that did NOT move in either
   period recedes rather than disappearing. */
.drill-item-off { opacity: .55; }
.cost-sum { color: var(--muted); font-weight: 700; }

/* =====================================================================
   «Финансы → Дебет и кредит» (finance.php + finance.js)
   Перенос вёрстки прототипа операционного директора. Всё под .fin, чтобы
   его имена классов (.tab, .badge, .kpi, голые table/thead) не задели
   остальные страницы, а его палитра переведена в наши переменные здесь,
   а не в JS: графики читают --fin-* с корня .fin (см. palette() в finance.js).
   ===================================================================== */
.fin {
    --fin-brand: var(--brand);
    --fin-brand-soft: var(--now-tint);
    --fin-green: var(--done);
    --fin-green-text: var(--success-text);
    --fin-green-soft: rgba(34, 163, 93, .16);
    --fin-red: var(--danger-text);
    --fin-red-soft: rgba(220, 38, 38, .16);
    --fin-amber: var(--queue);
    --fin-amber-soft: rgba(224, 169, 43, .18);
    --fin-violet: var(--epic-text);
    --fin-violet-soft: rgba(139, 92, 246, .16);
    --fin-grid: var(--line);
    max-width: 1400px; margin: 0 auto; padding: 18px 18px 40px;
    font-variant-numeric: tabular-nums lining-nums;
}
.fin a { color: var(--brand); text-decoration: none; }
.fin a:hover { text-decoration: underline; }
.fin .fin-empty-text { color: var(--ink-soft); margin: 4px 0 0; line-height: 1.5; }
.fin .fin-h1 { font-size: 24px; font-weight: 700; margin: 0 0 6px; letter-spacing: -.4px; color: var(--ink); text-wrap: balance; }
.fin .fin-h1 .vs { color: var(--brand); }
.fin .fin-h1-period { color: var(--muted); font-weight: 600; }
.fin .subtitle { color: var(--muted); font-size: 13px; margin-bottom: 18px; font-weight: 500; max-width: 900px; line-height: 1.5; }
.fin .section-title {
    font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .8px; color: var(--muted);
    margin: 28px 0 12px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
}
.fin .section-title::before { content: ""; width: 4px; height: 16px; background: var(--fin-brand); border-radius: 2px; flex: none; }
.fin .section-title.deb::before { background: var(--fin-green); }
.fin .section-title .hint { font-weight: 500; text-transform: none; letter-spacing: 0; color: var(--muted); margin-left: auto; font-size: 11.5px; }

/* toolbars, tabs, period */
.fin .toolbar { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; margin-bottom: 12px; }
.fin .tabs { display: inline-flex; background: var(--panel); border: 1px solid var(--line); border-radius: 9px; padding: 3px; gap: 2px; flex-wrap: wrap; }
.fin .tab {
    padding: 6px 12px; background: transparent; border: 0; border-radius: 6px; cursor: pointer;
    color: var(--muted); font-size: 13px; font-weight: 600; font-family: inherit;
}
.fin .tab.active { background: var(--accent-solid); color: #fff; }
.fin .tab:hover:not(.active) { background: var(--hover); color: var(--ink); }
.fin .tab:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.fin .tab small { font-weight: 600; opacity: .75; margin-left: 5px; font-size: 11px; }
.fin .period { display: flex; align-items: center; gap: 8px; background: var(--panel); border: 1px solid var(--line); border-radius: 9px; padding: 3px 3px 3px 10px; }
.fin .period label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .5px; font-weight: 700; }
/* Внутри — общий календарь (.dp-btn из daterange.js); кнопка подстроена под
   высоту соседних вкладок, чтобы строка не «дышала». */
.fin .period .dp-btn { padding: 4px 10px; font-weight: 600; }

/* KPI */
.fin .kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; }
.fin .kpi { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 16px 18px; box-shadow: var(--shadow); position: relative; }
.fin .kpi.cred { border-top: 3px solid var(--fin-brand); }
.fin .kpi.deb { border-top: 3px solid var(--fin-green); }
.fin .kpi.net { border-top: 3px solid var(--fin-violet); }
.fin .kpi .label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .6px; font-weight: 600; }
.fin .kpi .value { font-size: 24px; font-weight: 700; margin-top: 6px; letter-spacing: -.5px; color: var(--ink); }
.fin .kpi .delta { font-size: 12.5px; margin-top: 10px; display: inline-flex; align-items: center; gap: 4px; padding: 3px 9px; border-radius: 6px; font-weight: 700; }
.fin .kpi .delta.brand { background: var(--fin-brand-soft); color: var(--brand); }
.fin .kpi .delta.warn { background: var(--fin-amber-soft); color: var(--fin-amber); }
.fin .kpi .delta.bad { background: var(--fin-red-soft); color: var(--fin-red); }
.fin .kpi .delta.viol { background: var(--fin-violet-soft); color: var(--fin-violet); }
.fin .kpi .delta.flat { background: var(--panel-2); color: var(--muted); border: 1px solid var(--line); }
.fin .kpi .prev { font-size: 11.5px; color: var(--muted); margin-top: 6px; font-weight: 500; }

/* charts */
.fin .charts-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
@media (max-width: 980px) { .fin .charts-grid { grid-template-columns: 1fr !important; } }
.fin .chart-card { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 16px 18px; box-shadow: var(--shadow); min-width: 0; }
.fin .chart-card .chart-title { font-size: 14px; font-weight: 600; color: var(--ink); margin: 0 0 2px; }
.fin .chart-card .chart-sub { font-size: 11.5px; color: var(--muted); margin-bottom: 12px; font-weight: 500; line-height: 1.45; }
.fin .chart-wrap { height: 280px; position: relative; }

/* filters row */
.fin .controls { display: flex; gap: 10px; align-items: center; margin-bottom: 12px; flex-wrap: wrap; }
.fin .controls input, .fin .controls select {
    background: var(--input-bg); border: 1px solid var(--line); border-radius: 7px; padding: 8px 12px;
    color: var(--ink); font-size: 13px; font-family: inherit; font-weight: 500; outline: none;
}
.fin .controls input { min-width: 260px; }
.fin .controls input:focus, .fin .controls select:focus { border-color: var(--brand); }
.fin .controls .count { color: var(--muted); font-size: 12.5px; margin-left: 4px; }

/* tables */
.fin .table-card { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; overflow: hidden; box-shadow: var(--shadow); }
.fin table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.fin thead th {
    background: var(--panel-2); color: var(--muted); font-weight: 600; font-size: 10.5px; text-transform: uppercase;
    letter-spacing: .5px; padding: 10px 14px; text-align: right; border-bottom: 1px solid var(--line); white-space: nowrap;
}
.fin thead th:first-child, .fin th.l, .fin td.l { text-align: left; }
/* Заголовок или подпись KPI с подсказкой — пунктирное подчёркивание и курсор
   «?», как у заголовков таблиц на дашборде: иначе подсказку никто не найдёт. */
.fin th[title], .fin .kpi .label[title] { text-decoration: underline dotted; text-underline-offset: 3px; cursor: help; }
.fin tbody td { padding: 10px 14px; text-align: right; border-bottom: 1px solid var(--line); color: var(--ink-soft); }
.fin tbody td:first-child { text-align: left; color: var(--ink); font-weight: 600; }
.fin tbody tr:last-child td { border-bottom: 0; }
.fin tbody tr:hover td { background: var(--hover); }
.fin tbody tr.clickable { cursor: pointer; }
.fin tbody tr.picked td { background: var(--fin-brand-soft); }
.fin tfoot td { padding: 10px 14px; text-align: right; font-weight: 700; color: var(--ink); border-top: 1px solid var(--line); background: var(--panel-2); }
.fin tfoot td:first-child { text-align: left; }
.fin .name { max-width: 320px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fin .muted { color: var(--muted); font-weight: 500; }
.fin .pid { color: var(--muted); font-size: 11px; font-weight: 500; }
.fin .strong { color: var(--ink); font-weight: 700; }
.fin td.neg { color: var(--fin-red); font-weight: 700; }
.fin .badge { display: inline-block; padding: 1px 8px; border-radius: 6px; font-size: 11px; font-weight: 700; margin-left: 4px; white-space: nowrap; vertical-align: middle; }
.fin .badge.neg { background: var(--fin-red-soft); color: var(--fin-red); }
.fin .badge.pos { background: var(--fin-green-soft); color: var(--fin-green-text); }
.fin .badge.brand { background: var(--fin-brand-soft); color: var(--brand); }
.fin .badge.warn { background: var(--fin-amber-soft); color: var(--fin-amber); }
.fin .badge.viol { background: var(--fin-violet-soft); color: var(--fin-violet); }
.fin .badge.flat { background: var(--panel-2); color: var(--muted); border: 1px solid var(--line); }
.fin .bar { display: inline-block; height: 6px; border-radius: 3px; background: var(--fin-brand); vertical-align: middle; margin-right: 8px; min-width: 2px; }
.fin .bar.green { background: var(--fin-green); }
.fin .bar.viol { background: var(--fin-violet); }

/* partner / advertiser cards (details) */
.fin .partner { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; margin-bottom: 10px; box-shadow: var(--shadow); overflow: hidden; }
.fin .partner summary {
    cursor: pointer; padding: 12px 18px; list-style: none; display: grid;
    grid-template-columns: minmax(220px, 2fr) minmax(140px, 1.1fr) repeat(4, minmax(90px, .8fr)) 18px;
    gap: 14px; align-items: center; user-select: none;
}
.fin .partner summary::-webkit-details-marker { display: none; }
.fin .partner summary:hover { background: var(--hover); }
.fin .partner summary .caret { font-size: 10px; color: var(--muted); text-align: right; }
.fin .partner[open] summary .caret { transform: rotate(180deg); }
.fin .partner .pname { font-weight: 700; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fin .partner .pname .pid { margin-left: 6px; }
.fin .partner .pmeta { font-size: 11.5px; color: var(--muted); font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fin .partner .cell { text-align: right; }
.fin .partner .cell .l { display: block; font-size: 10px; color: var(--muted); text-transform: uppercase; letter-spacing: .5px; font-weight: 600; }
.fin .partner .cell .v { font-weight: 700; color: var(--ink); font-size: 13.5px; }
.fin .partner .cell .v.hot { color: var(--fin-red); }
.fin .partner .cell .v.viol { color: var(--fin-violet); }
.fin .partner .cell .v.muted { color: var(--muted); font-weight: 500; }
.fin .partner .inner { border-top: 1px solid var(--line); }
.fin .partner .inner table thead th, .fin .partner .inner table tbody td { padding: 9px 14px; }
.fin .partner .netline { padding: 10px 18px; font-size: 12px; background: var(--fin-violet-soft); color: var(--fin-violet); font-weight: 600; border-top: 1px solid var(--line); }
.fin .partner .netline a { color: inherit; text-decoration: underline dotted; }
.fin .adv summary { grid-template-columns: minmax(240px, 2fr) minmax(160px, 1.2fr) repeat(3, minmax(100px, .8fr)) 18px; }
.fin .adv2 summary { grid-template-columns: minmax(240px, 2fr) minmax(150px, 1.2fr) repeat(4, minmax(95px, .8fr)) 18px; }
.fin .adv .acc { padding: 12px 18px; border-top: 1px solid var(--line); font-size: 12.5px; }
.fin .adv .acc .row { display: flex; justify-content: space-between; gap: 12px; padding: 6px 0; border-bottom: 1px dashed var(--line); flex-wrap: wrap; }
.fin .adv .acc .row:last-child { border-bottom: 0; }
.fin .adv .acc .row .sum { font-weight: 700; }
.fin .adv .acc .row .sum.muted { font-weight: 500; }
@media (max-width: 900px) {
    .fin .partner summary, .fin .adv summary, .fin .adv2 summary { grid-template-columns: 1fr 1fr; }
    .fin .partner .cell { text-align: left; }
}
.fin .more {
    display: block; width: 100%; padding: 11px; background: var(--panel); border: 1px dashed var(--line); border-radius: 10px;
    color: var(--brand); font-weight: 700; font-family: inherit; font-size: 13px; cursor: pointer; margin-top: 6px;
}
.fin .more:hover { background: var(--hover); }
/* display: block выше перебивает браузерное [hidden]{display:none}, и кнопка
   остаётся на экране, когда показывать уже нечего: клик только уводил счётчик
   в минус («Показать ещё (-22 из -22)»), а список не рос. */
.fin .more[hidden] { display: none; }

/* notes, footer */
.fin .conclusions { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 22px 24px; box-shadow: var(--shadow); }
.fin .conclusions h3 { margin: 0 0 12px; font-size: 15px; font-weight: 700; color: var(--ink); display: flex; align-items: center; gap: 8px; }
.fin .conclusions h3::before { content: ""; width: 7px; height: 7px; background: var(--fin-brand); border-radius: 50%; box-shadow: 0 0 0 3px var(--fin-brand-soft); }
.fin .conclusions h3:not(:first-child) { margin-top: 24px; }
.fin .conclusions p { margin: 0 0 10px; color: var(--ink-soft); line-height: 1.65; max-width: 900px; }
.fin .conclusions b { color: var(--ink); font-weight: 700; }
.fin .conclusions .accent { color: var(--brand); font-weight: 700; }
.fin .fin-footer { padding: 20px 0 0; text-align: center; color: var(--muted); font-size: 11.5px; border-top: 1px solid var(--line); margin-top: 32px; font-weight: 500; }
.fin .fin-footer b { color: var(--brand); }

/* Режимы «Состояние на дату» / «Движение за период» и их календари: сегмент
   общий (.seg из шапки), подсказка справа объясняет, на какой день показан
   отчёт. Пока грузится другой день, страница приглушается, а не мигает. */
.fin .fin-modebar { gap: 14px; }
/* Переключатель режима и его календарь — одна рамка (та же, что у блока
   «Расчётные периоды»): выбор «на какой день / за какой отрезок» читается как
   одно решение, а не как три разрозненных элемента. Сегмент внутри без своей
   рамки, чтобы не было рамки в рамке. */
.fin .fin-when {
    display: inline-flex; align-items: center; gap: 8px;
    background: var(--panel); border: 1px solid var(--line); border-radius: 9px; padding: 3px;
}
.fin .fin-when .seg { border: none; background: var(--panel-2); }
.fin .fin-when .seg-btn { padding: 5px 12px; font-size: 13px; }
.fin .fin-when .seg-btn + .seg-btn { border-left: 1px solid var(--line); }
.fin .fin-when .fin-when-pick { display: inline-flex; }
/* Явно: display выше перебивал бы браузерное [hidden]{display:none}, и оба
   календаря показывались сразу. Виден только календарь текущего режима. */
.fin .fin-when .fin-when-pick[hidden] { display: none; }
.fin .fin-when .dp-btn { padding: 4px 10px; font-weight: 600; }
.fin .fin-modehint { color: var(--muted); font-size: 12.5px; font-weight: 500; }

/* Вкладки страницы («Сводка / Кредиторка / Дебиторка»): полоса с подчёркиванием,
   а не пилюли, чтобы не спорить с валютными вкладками .tabs выше — это разделы
   отчёта, а не фильтр. Счётчик в подписи — сколько партнёров / рекламодателей
   в списке при текущих фильтрах. */
/* Без overflow: кнопки заходят на нижнюю границу на 1px (подчёркивание активной
   вкладки поверх линии), и любой overflow: auto превращал это в вертикальный
   скролл полосы. Три коротких слова на узком экране просто переносятся. */
.fin .fin-tabs { display: flex; flex-wrap: wrap; gap: 4px; border-bottom: 1px solid var(--line); margin: 18px 0 6px; }
.fin .fin-tab-btn {
    background: none; border: 0; border-bottom: 2px solid transparent; margin-bottom: -1px;
    padding: 9px 14px; font: inherit; font-size: 14px; font-weight: 600; color: var(--muted); cursor: pointer; white-space: nowrap;
}
.fin .fin-tab-btn:hover { color: var(--ink); background: var(--hover); border-radius: 6px 6px 0 0; }
.fin .fin-tab-btn.active { color: var(--brand); border-bottom-color: var(--brand); }
.fin .fin-tab-btn small { font-weight: 600; font-size: 11.5px; color: var(--muted); margin-left: 4px; }
.fin .fin-tab-btn.active small { color: var(--brand); opacity: .8; }
.fin .fin-pane[hidden] { display: none; }
.fin.fin-loading #fin-report { opacity: .55; transition: opacity .15s; pointer-events: none; }
.fin #tbl_mv tbody td:first-child, .fin #tbl_mv tfoot td:first-child { font-weight: 600; }

/* =====================================================================
   «Финансы → Счета рекламодателей» (invoices.php / invoices.js, invoice.php /
   invoice.js). Возвращено из стэша «finance-section» (2026-09-09) без формы
   создания и переключателя стендов; .inv-* — список и панель фильтров,
   .ivd-* — карточка счёта.
   ===================================================================== */
.inv-head { flex-wrap: wrap; }
.inv-head h2 { margin: 0; font-size: 17px; }
.inv-head h2 small { color: var(--muted); font-weight: 500; font-size: 13px; }
.inv-tools { display: flex; gap: 8px; align-items: center; }
.inv-btn { display: inline-flex; align-items: center; gap: 6px; }
.inv-btn-ico { font-size: 13px; line-height: 1; opacity: .85; }
/* «Фильтры» — акцентная, как в CRM: это главная кнопка страницы. */
.inv-btn.primary { background: var(--accent-solid); border-color: var(--accent-solid); color: #fff; }
.inv-btn.primary:hover { background: #4a7ce0; border-color: #4a7ce0; }
.inv-btn .cnt { background: rgba(255, 255, 255, .28); color: #fff; border-radius: 10px; font-size: 11px; padding: 0 6px; }
.inv-columns-pop { right: 0; left: auto; min-width: 240px; max-height: none; } /* 17 строк, все должны быть видны без прокрутки внутри поповера */

.inv-table { font-size: 12.5px; }
.inv-table th { white-space: nowrap; }
.inv-table td { vertical-align: middle; padding-top: 7px; padding-bottom: 7px; }
/* Деньги — вправо, табличными цифрами, чтобы разряды стояли столбиком. Общее
   правило .issues td (всё влево) здесь осознанно перебито: колонок с суммами
   три подряд, и слева они не сравниваются глазом. */
.inv-table th.num, .inv-table td.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.inv-table tbody tr:nth-child(even) td { background: var(--zebra); }
.inv-table tbody tr:hover td { background: var(--hover); }
.inv-table .row-inactive td { opacity: .55; }
.inv-link { color: var(--brand); text-decoration: none; }
.inv-link:hover { text-decoration: underline; }

/* Чип статуса: те же переменные, что у .st (colorTone() в ui.js), но в
   обычном регистре и крупнее — это не метка-аббревиатура, а слова. */
.inv-chip {
    display: inline-block; padding: 2px 9px; border-radius: 999px;
    border: 1px solid transparent; white-space: nowrap;
    font-size: 11px; font-weight: 600; line-height: 1.5;
}
[data-theme="dark"] .inv-chip  { color: var(--st-fg-d); background: var(--st-bg-d); border-color: var(--st-bd-d); }
[data-theme="light"] .inv-chip { color: var(--st-fg-l); background: var(--st-bg-l); border-color: var(--st-bd-l); }

.inv-error {
    margin: 0 0 10px; padding: 10px 14px; border-radius: 8px; font-size: 13px;
    background: #351417; border: 1px solid #6e2a30; color: #f5959a;
}
:root[data-theme="light"] .inv-error { background: #fdecee; border-color: #f0b6bc; color: #9a2530; }
.inv-error.hidden { display: none; }

.inv-foot {
    display: flex; justify-content: space-between; align-items: center; gap: 12px;
    padding-top: 10px; margin-top: 4px; border-top: 1px solid var(--line);
    font-size: 12px; color: var(--muted);
}
.inv-pager { display: flex; align-items: center; gap: 6px; }
.inv-pages { font-variant-numeric: tabular-nums; }
.inv-pager .btn:disabled { opacity: .4; cursor: default; }

/* --- выезжающая панель фильтров ------------------------------------------
   Форма и тень как у drill-панели, но без её шапки на --bar: это форма, а не
   отчёт, и заголовок «Фильтры» на цветной полосе спорил бы с полями под ним. */
.inv-drawer {
    position: fixed; top: 0; right: 0; bottom: 0; width: min(340px, 94vw);
    background: var(--panel); z-index: 61; display: flex; flex-direction: column;
    box-shadow: -6px 0 24px rgba(0, 0, 0, .5); border-left: 1px solid var(--line);
    animation: slide-in .18s ease;
}
:root[data-theme="light"] .inv-drawer { box-shadow: -6px 0 24px rgba(20, 30, 50, .18); }
.inv-drawer.hidden { display: none; }
.inv-drawer-head {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    padding: 14px 18px 10px;
}
.inv-drawer-head h3 { margin: 0; font-size: 16px; font-weight: 700; }
.inv-drawer-head .drill-close { color: var(--muted); }
.inv-drawer-head .drill-close:hover { color: var(--ink); }
/* overflow: visible, а не auto — поповеры мультиселектов абсолютные и
   вылезают за поле; в прокручиваемом контейнере их бы обрезало. Полей
   немного, панель помещается в высоту экрана и без прокрутки. */
.inv-drawer-body { flex: 1; padding: 4px 18px 16px; display: flex; flex-direction: column; gap: 10px; overflow: visible; }
.inv-drawer-body .ms, .inv-drawer-body .ms-btn { width: 100%; }
.inv-drawer-body .ms-pop { width: 100%; }
.inv-field-label { display: block; font-size: 12px; color: var(--muted); margin-bottom: 5px; }
.inv-period-row { display: flex; align-items: center; gap: 6px; }
.inv-period-clear {
    background: none; border: none; cursor: pointer; font-size: 13px; opacity: .6; padding: 2px 4px;
}
.inv-period-clear:hover { opacity: 1; }
.inv-period-summary { margin-top: 5px; font-size: 12px; color: var(--ink-soft); }
.inv-check { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--ink-soft); cursor: pointer; }
.inv-drawer-foot {
    display: flex; justify-content: flex-end; gap: 8px;
    padding: 12px 18px; border-top: 1px solid var(--line); background: var(--panel-2);
}
.inv-drawer-foot .btn:last-child { background: var(--accent-solid); border-color: var(--accent-solid); color: #fff; }
.inv-drawer-foot .btn:last-child:hover { background: #4a7ce0; }

/* Предупреждение (жёлтое): тестовый режим CRM. Пара к .flash-ok / .flash-err;
   палитра та же, что у .banner. */
.flash-warn { background: #3a2f12; border: 1px solid #6b5520; color: #f2d494; }
.flash-warn b { color: #fff; font-weight: 600; }
:root[data-theme="light"] .flash-warn { background: #fff6e5; border-color: #f0d089; color: #7a5a10; }
:root[data-theme="light"] .flash-warn b { color: #4a3607; }
/* В панели фильтров кнопка календаря растягивается на всю ширину, как
   мультиселекты над ней. */
.inv-drawer-body .dp { flex: 1; min-width: 0; }
.inv-drawer-body .dp-btn { width: 100%; text-align: left; }
.inv-drawer-body .dp-pop { z-index: 62; }

/* Колонка, только что включённая в «Колонках»: таблица прокручивается к ней, а сама она
   на секунду подсвечивается (revealColumn в invoices.js). Без этого новая колонка
   появлялась за правым краем широкой таблицы и выглядела как «ничего не произошло». */
.inv-table .inv-col-flash { background: var(--now-tint) !important; transition: background .6s ease; }
.inv-panel .table-scroll { scroll-behavior: smooth; }

/* =========================================================================
   «Финансы → Счета → счёт» (invoice.php / invoice.js) — по макету Figma
   «CRM / Просмотр счёта»: вкладки, строка заголовка со статусом, две карточки
   («Основное» и «Итоговая сводка»), затем заказы, услуги и взаимозачёт.
   Макет светлый; здесь он переложен на токены обеих тем.
   ========================================================================= */
.ivd-main { max-width: 1600px; }

/* Вкладки страницы («Основное» | «Статистика»): подчёркивание активной. */
.ivd-tabs { display: flex; gap: 22px; border-bottom: 1px solid var(--line); margin: 4px 0 14px; }
.ivd-tab {
    padding: 8px 0 10px; font-size: 13px; font-weight: 600; color: var(--muted);
    text-decoration: none; border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.ivd-tab.active { color: var(--brand); border-bottom-color: var(--brand); }
.ivd-tab.disabled { cursor: default; opacity: .6; }

.ivd-title-row { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; flex-wrap: wrap; }
.ivd-back {
    color: var(--ink-soft); text-decoration: none; font-size: 26px; line-height: 1;
    padding: 0 4px; border-radius: 6px;
}
.ivd-back:hover { background: var(--hover); color: var(--ink); }
.ivd-title { margin: 0; font-size: 20px; font-weight: 700; letter-spacing: .1px; }
.ivd-status-chip { font-size: 12px; padding: 3px 11px; }

.ivd-loading { color: var(--muted); font-style: italic; padding: 24px 0; }

/* Блок = подпись над карточкой + карточка. */
.ivd-block { margin-bottom: 18px; }
.ivd-block-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 8px; }
.ivd-h2 { margin: 0; font-size: 15px; font-weight: 700; }
.ivd-h3 { margin: 0 0 10px; font-size: 13.5px; font-weight: 700; }
.ivd-h3-sub { margin-top: 16px; }
.ivd-card {
    background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
    box-shadow: var(--shadow); padding: 16px 18px;
}

/* Две карточки рядом; на узком экране — столбиком. */
.ivd-two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr); gap: 18px; align-items: start; }
@media (max-width: 1100px) { .ivd-two { grid-template-columns: 1fr; } }

/* «Основное»: подпись слева, значение справа, как в макете. */
.ivd-kv { display: grid; grid-template-columns: 150px 1fr; gap: 12px 24px; margin: 0; font-size: 13.5px; }
.ivd-kv dt { color: var(--ink); font-weight: 600; }
.ivd-kv dd { margin: 0; color: var(--ink); min-width: 0; overflow-wrap: anywhere; }
.ivd-time { color: var(--muted); }
.ivd-adv { display: inline-flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* Идентификатор в светло-синей плашке — как ID оффера/цели/счёта в макете. */
.ivd-idchip {
    display: inline-block; padding: 2px 9px; border-radius: 6px; font-size: 12px; font-weight: 600;
    color: var(--brand); background: var(--now-tint); font-variant-numeric: tabular-nums; white-space: nowrap;
}
.ivd-brand { color: var(--brand); }
.ivd-clamp { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }

/* «Итоговая сводка»: две колонки цифр через вертикальную линию. */
.ivd-summary { display: grid; grid-template-columns: minmax(0, 1fr) 1px minmax(0, 1fr); gap: 0 26px; }
.ivd-vsep { background: var(--line); }
.ivd-figures { display: flex; flex-direction: column; gap: 11px; font-size: 13.5px; }
.ivd-fig { display: flex; justify-content: space-between; gap: 12px; align-items: baseline; }
.ivd-fig-k { color: var(--ink); font-weight: 600; }
.ivd-fig-v { font-variant-numeric: tabular-nums; white-space: nowrap; }
.ivd-figures-strong .ivd-fig-v { font-weight: 700; font-size: 14.5px; }
.ivd-danger, .ivd-danger .ivd-fig-k, .ivd-danger .ivd-fig-v { color: var(--danger-text); }
.ivd-ok .ivd-fig-v { color: var(--success-text); }
.ivd-warn { color: var(--queue); }
@media (max-width: 700px) {
    .ivd-summary { grid-template-columns: 1fr; gap: 14px; }
    .ivd-vsep { display: none; }
}
.ivd-copy {
    background: none; border: none; color: var(--brand); font: inherit; font-size: 12.5px; font-weight: 600;
    cursor: pointer; padding: 2px 4px; border-radius: 6px;
}
.ivd-copy:hover { background: var(--hover); }

/* Таблицы внутри карточек. */
.ivd-table { font-size: 12.5px; }
.ivd-table th { white-space: nowrap; }
.ivd-table td { vertical-align: middle; padding-top: 7px; padding-bottom: 7px; }
/* Текстовые ячейки переносятся, цифры — нет: длинные названия услуг в счетах на
   оплату иначе выталкивали таблицу за край и давали горизонтальный скролл.
   Строка переносится (и при нужде клампится в 2 строки, .ivd-clamp), а не
   прокручивается: на странице счёта нечего скрывать за краем. */
.ivd-table td:not(.num) { white-space: normal; overflow-wrap: break-word; }
/* Колонкам с названиями — минимум ширины: без него auto-layout при переносе
   сжимал «Цель» до 30px и резал слова посимвольно. */
.ivd-table .ivd-col-text { min-width: 180px; }
.ivd-table .ivd-clamp { min-width: 0; }
.ivd-table th:not(.num) { white-space: normal; }
.ivd-block .table-scroll { overflow-x: visible; }
.ivd-table { table-layout: auto; width: 100%; }
.ivd-table th.num, .ivd-table td.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.ivd-table tbody tr:nth-child(even) td { background: var(--zebra); }
.ivd-table tbody tr:hover td { background: var(--hover); }
.ivd-table tfoot td { font-weight: 700; border-top: 1px solid var(--line); padding-top: 9px; }
.ivd-table-foot { margin-top: 8px; font-size: 12px; color: var(--muted); }

/* Вкладки заказов («Все заказы» | ЛДНФ-000098 | …). */
.ivd-subtabs { display: flex; gap: 18px; flex-wrap: wrap; border-bottom: 1px solid var(--line); margin-bottom: 12px; }
.ivd-subtab {
    background: none; border: none; border-bottom: 2px solid transparent; margin-bottom: -1px;
    padding: 4px 0 9px; font: inherit; font-size: 13px; font-weight: 600; color: var(--muted); cursor: pointer;
}
.ivd-subtab:hover { color: var(--ink); }
.ivd-subtab.active { color: var(--brand); border-bottom-color: var(--brand); }

/* «Прочие услуги» сворачиваются, таблица у них узкая — как в макете. */
.ivd-others { margin-top: 18px; }
.ivd-others > summary {
    list-style: none; cursor: pointer; font-size: 13.5px; font-weight: 700; display: inline-flex; align-items: center; gap: 8px;
    user-select: none;
}
.ivd-others > summary::-webkit-details-marker { display: none; }
.ivd-caret { display: inline-block; width: 7px; height: 7px; border-right: 2px solid var(--muted); border-bottom: 2px solid var(--muted); transform: rotate(45deg); margin-top: -3px; }
.ivd-others[open] .ivd-caret { transform: rotate(-135deg); margin-top: 3px; }
.ivd-others-body { max-width: 520px; margin-top: 10px; }
.ivd-grand { margin-top: 16px; font-size: 14px; font-weight: 600; }
.ivd-grand b { font-size: 15px; margin-left: 6px; }

/* --- вкладка «Статистика» (задача на статистику, Figma invoice_statistics) --- */
.ivd-stat-card { padding-top: 14px; }
/* Шапка задачи: четыре пары «подпись — значение» в одну строку. */
.ivd-stat-head { display: flex; flex-wrap: wrap; gap: 10px 40px; align-items: center; font-size: 13.5px; margin-bottom: 14px; }
.ivd-stat-kv { display: inline-flex; align-items: center; gap: 10px; }
.ivd-stat-kv b { font-weight: 600; }
.ivd-file-tabs { margin-bottom: 16px; }
.ivd-file { padding-top: 4px; }
.ivd-file-divided { padding-bottom: 18px; margin-bottom: 18px; border-bottom: 1px solid var(--line); }
.ivd-file-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 10px; }
.ivd-file-name { margin: 0; overflow-wrap: anywhere; }
.ivd-file-size { font-size: 12px; color: var(--muted); white-space: nowrap; }
/* Строка оффера — группа; строки целей под ней с отступом первой колонки. */
.ivd-stat-table .ivd-offer-row td { background: var(--panel-2); font-weight: 600; }
.ivd-stat-table .ivd-offer-row td .ivd-brand { font-weight: 500; }
.ivd-stat-table tbody tr.ivd-goal-row:hover td { background: var(--hover); }
.ivd-stat-table tbody tr:nth-child(even) td { background: transparent; }
.ivd-stat-table tbody tr.ivd-offer-row:nth-child(even) td { background: var(--panel-2); }
.ivd-stat-table td .inv-chip { font-weight: 600; }
/* Комментарии под таблицей файла: автор и дата слева, текст справа, как в макете. */
.ivd-comments { margin-top: 14px; }
.ivd-comments > summary {
    list-style: none; cursor: pointer; font-size: 13.5px; font-weight: 700; display: inline-flex; align-items: center; gap: 8px;
    user-select: none;
}
.ivd-comments > summary::-webkit-details-marker { display: none; }
.ivd-comments-body { margin-top: 12px; display: flex; flex-direction: column; gap: 14px; }
.ivd-comment { display: grid; grid-template-columns: 200px minmax(0, 1fr); gap: 16px; font-size: 13px; }
.ivd-comment-who { display: flex; flex-direction: column; gap: 3px; }
.ivd-comment-when { font-size: 12px; color: var(--muted); }
.ivd-comment-text { white-space: pre-wrap; overflow-wrap: anywhere; line-height: 1.5; max-width: 760px; }
@media (max-width: 700px) { .ivd-comment { grid-template-columns: 1fr; gap: 6px; } }

/* Подсказка у заголовка блока («Услуги за период»): кружок с «?», текст — в title. */
.ivd-h2-wrap { display: inline-flex; align-items: center; gap: 8px; }
.ivd-hint {
    display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px;
    border-radius: 50%; border: 1px solid var(--muted); color: var(--muted); font-size: 11px; font-weight: 700;
    cursor: help; user-select: none; line-height: 1;
}
.ivd-hint:hover, .ivd-hint:focus { color: var(--brand); border-color: var(--brand); outline: none; }

/* Услуги выбранного заказа и его счетов на оплату (только на вкладке заказа). */
.ivd-lines { display: flex; flex-direction: column; gap: 12px; }
.ivd-lines-others { max-width: 640px; margin-top: 0; }
.ivd-pi-services { margin-top: 14px; padding-left: 14px; border-left: 2px solid var(--line); }
.ivd-h4 { margin: 0 0 8px; font-size: 13px; font-weight: 600; color: var(--ink-soft); }
.ivd-h4-gap { margin-top: 14px; }
.ivd-file-tools { display: inline-flex; align-items: center; gap: 10px; white-space: nowrap; }
.ivd-download:disabled { opacity: .6; cursor: default; }

/* =========================================================================
   «Финансы → Счета → Создание счёта» (invoice-new.php / invoice-new.js).
   Карточки, таблицы и плашки — те же .ivd-*, что на странице счёта; здесь
   только поля формы, поповер поиска рекламодателя и липкий низ.
   ========================================================================= */

/* Вкладка «Конверсии и партнёры» карточки счёта (invoice.js → renderLinks). */
.ivl-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; margin: 4px 0 12px; }
.ivl-head .ivl-text { margin: 0; flex: 1; }
.ivl-notes { display: flex; flex-direction: column; gap: 6px; margin: 0 0 14px; }
.ivl-note { font-size: 12.5px; color: var(--queue); background: rgba(224, 169, 43, .12); border: 1px solid rgba(224, 169, 43, .35); border-radius: 8px; padding: 7px 10px; }
.ivl-flow { display: grid; grid-template-columns: 1fr auto 1fr; gap: 18px; align-items: stretch; }
.ivl-flow-col { min-width: 0; }
.ivl-flow-title { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .5px; color: var(--muted); margin-bottom: 8px; }
.ivl-flow-foot { margin-top: 8px; font-size: 12.5px; color: var(--ink-soft); display: flex; align-items: center; gap: 6px; }
.ivl-flow-arrow { display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 0 12px; color: var(--muted); font-size: 12px; border-left: 1px dashed var(--line); border-right: 1px dashed var(--line); min-width: 150px; }
.ivl-flow-arrow-n { font-size: 26px; font-weight: 700; color: var(--brand); line-height: 1.1; }
.ivl-flow-arrow-sub { margin-top: 6px; color: var(--ink-soft); }
.ivl-wide .ivd-card { padding: 0; overflow: hidden; }
.ivl-wide .ivd-table { font-size: 12.5px; }
.ivl-small { font-size: 11.5px; }
/* «Проверка счёта»: service lines against approved conversions */
.ivl-check-sum { display: flex; flex-wrap: wrap; gap: 6px 14px; align-items: center; margin: 0 0 10px; font-size: 12.5px; font-variant-numeric: tabular-nums; }
.ivl-check-sum-item { display: inline-flex; align-items: center; gap: 4px; font-weight: 600; }
.ivl-check-list { list-style: none; margin: 0 0 10px; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.ivl-check-item { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; padding: 8px 10px; border: 1px solid var(--line); border-radius: 8px; background: var(--panel-2); font-size: 12.5px; }
.ivl-check-item .ivl-check-what { flex: 1 1 100%; color: var(--ink-soft); line-height: 1.45; }
.ivl-check-ok { color: var(--success-text); font-weight: 600; font-size: 13px; margin: 0 0 8px; }
.ivl-prices { display: flex; flex-direction: column; gap: 2px; align-items: flex-end; font-variant-numeric: tabular-nums; white-space: nowrap; }
.ivl-mono { font-family: ui-monospace, Consolas, monospace; font-size: 11.5px; }
.ivl-rows { margin: 14px 0; }
.ivl-rows > summary { cursor: pointer; color: var(--brand); font-weight: 600; padding: 6px 0; list-style: none; }
.ivl-rows > summary::-webkit-details-marker { display: none; }
.ivl-rows[open] > summary { margin-bottom: 8px; }
@media (max-width: 900px) { .ivl-flow { grid-template-columns: 1fr; } .ivl-flow-arrow { border: none; border-top: 1px dashed var(--line); border-bottom: 1px dashed var(--line); padding: 10px 0; } }
/* Поясняющий текст вкладки — абзац, а не .ivd-hint (тот — круглый маркер «?»
   в 16px, из-за него подсказки рисовались как линия с текстом). */
.ivl-text { font-size: 12.5px; color: var(--muted); line-height: 1.5; margin: 8px 0 0; }
/* «Файлы против трекера»: подписи длинные, число справа — сетка «подпись | число»
   вместо фиксированных 150px первой колонки общего .ivd-kv. */
.ivl-cmp .ivd-kv { grid-template-columns: minmax(0, 1fr) auto; gap: 8px 18px; }
.ivl-cmp .ivd-kv dd { text-align: right; font-variant-numeric: tabular-nums; }
/* Пояснение под подписью в «Файлы против трекера»: откуда взято число. */
.ivl-kv-sub { font-size: 11.5px; color: var(--muted); font-weight: 400; line-height: 1.4; margin-top: 2px; }
.ivl-cmp .ivd-kv dt { font-weight: 600; }
.ivl-cmp .ivd-kv dd { align-self: start; padding-top: 1px; font-weight: 600; }
/* Панель над списком конверсий: фильтр по статусу, поиск, счётчик. */
.ivl-toolbar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding: 10px 14px; border-bottom: 1px solid var(--line); }
.ivl-filter .seg-btn { padding: 5px 10px; font-size: 12.5px; }
.ivl-search { background: var(--input-bg); border: 1px solid var(--line); border-radius: 7px; padding: 6px 10px; color: var(--ink); font: inherit; font-size: 12.5px; min-width: 260px; outline: none; }
.ivl-search:focus { border-color: var(--brand); }
.ivl-when { white-space: nowrap; font-variant-numeric: tabular-nums; }
.ivl-missing { margin: 12px 0 0; }
.ivl-missing .table-scroll { max-height: 420px; overflow: auto; }

/* =====================================================================
   «Финансы → Закрытие месяца» (reconcile.php + reconcile.js)
   Раскладка артефакта операционного директора «Сверка ЦРМ и счетов РФ».
   Всё под .rec — его палитра переведена в наши переменные здесь, а не в JS:
   графики читают --rec-* с корня .rec (см. palette() в reconcile.js).
   ===================================================================== */
.rec {
    --rec-brand: var(--brand);
    --rec-green: var(--done);
    --rec-red: var(--danger-text);
    --rec-amber: var(--queue);
    --rec-violet: var(--epic-text);
    --rec-grid: var(--line);
    /* Шире, чем остальные страницы (1400px): в таблице 17 колонок, её
       естественная ширина ~1650px, и на широком мониторе горизонтальный
       скролл появлялся при пустых полях по краям. Предел всё же есть —
       дальше растягивать нечего, таблица шире не становится. */
    max-width: min(1760px, 100%); margin: 0 auto; padding: 18px 18px 40px;
    font-variant-numeric: tabular-nums lining-nums;
}
.rec a { color: var(--brand); text-decoration: none; }
.rec a:hover { text-decoration: underline; }
.rec .rec-h1 { font-size: 24px; font-weight: 700; margin: 0 0 6px; letter-spacing: -.4px; color: var(--ink); text-wrap: balance; }
.rec .rec-h1 .vs { color: var(--brand); }
.rec .rec-sub { color: var(--muted); font-size: 13px; margin-bottom: 18px; font-weight: 500; max-width: 980px; line-height: 1.5; }
.rec .section-title {
    font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .8px; color: var(--muted);
    margin: 26px 0 12px; display: flex; align-items: center; gap: 10px;
}
.rec .section-title::before { content: ""; width: 4px; height: 16px; background: var(--rec-brand); border-radius: 2px; flex: none; }

/* панель месяца */
.rec .rec-bar { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; margin-bottom: 12px; }
.rec .rec-month { display: inline-flex; align-items: center; gap: 2px; background: var(--panel); border: 1px solid var(--line); border-radius: 9px; padding: 3px; }
.rec .rec-month button {
    width: 28px; height: 28px; border: 0; background: transparent; border-radius: 6px; cursor: pointer;
    color: var(--muted); font-size: 16px; font-family: inherit; line-height: 1;
}
.rec .rec-month button:hover { background: var(--hover); color: var(--ink); }
.rec .rec-month input { background: transparent; border: 0; color: var(--ink); font: inherit; font-weight: 600; padding: 4px 6px; outline: none; }
.rec .rec-status { font-size: 12.5px; color: var(--muted); font-weight: 600; }
.rec .rec-status.busy { color: var(--queue); }
.rec .rec-status.err { color: var(--danger-text); }
.rec .rec-status.ok { color: var(--success-text); }
.rec .rec-notice { border: 1px solid var(--line); background: var(--panel-2); border-radius: 9px; padding: 10px 14px; font-size: 13px; color: var(--ink-soft); margin-bottom: 14px; }
.rec .rec-notice.err { border-color: var(--danger-text); color: var(--danger-text); }
.rec .rec-notice.warn { border-color: var(--queue); }

/* KPI */
.rec .rec-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 12px; }
.rec .rec-kpi { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 14px 16px; }
/* Раскладка расхождения. Плашка, а не карточки: это ОДНО число, разобранное на
   слагаемые, и читать его надо слева направо, а не как четыре независимых KPI. */
.rec .rec-split { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 12px 16px 14px; margin-top: 12px; }
.rec .rec-split:empty { display: none; }
.rec .rec-split-h { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .5px; font-weight: 700; display: flex; gap: 8px; align-items: baseline; }
.rec .rec-split-h .rec-mut { text-transform: none; letter-spacing: 0; font-weight: 600; font-size: 12.5px; }
.rec .rec-split-row { display: flex; flex-wrap: wrap; gap: 8px 28px; margin-top: 8px; }
.rec .rec-split-i { min-width: 150px; }
.rec .rec-split-l { font-size: 12px; color: var(--muted); }
.rec .rec-split-v { font-size: 15.5px; font-weight: 700; font-variant-numeric: tabular-nums; margin-top: 2px; }
.rec .rec-kpi-l { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .6px; color: var(--muted); }
.rec .rec-kpi-v { font-size: 22px; font-weight: 700; margin: 6px 0 4px; letter-spacing: -.5px; }
.rec .rec-kpi-h { font-size: 11.5px; color: var(--muted); line-height: 1.45; }
.rec .rec-kpi.ok .rec-kpi-v { color: var(--success-text); }
.rec .rec-kpi.warn .rec-kpi-v { color: var(--queue); }
.rec .rec-kpi.neg .rec-kpi-v { color: var(--danger-text); }

/* графики */
.rec .rec-charts { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.rec .rec-chart { padding: 14px 16px; }
.rec .rec-chart-title { font-size: 13.5px; font-weight: 700; }
.rec .rec-chart-sub { font-size: 11.5px; color: var(--muted); margin: 2px 0 10px; line-height: 1.4; }
.rec .rec-canvas { position: relative; height: 240px; }
.rec .rec-canvas.tall { height: 340px; }
@media (max-width: 1100px) { .rec .rec-charts { grid-template-columns: 1fr; } }

/* таблица */
.rec .rec-panel { padding: 0; overflow: hidden; }
.rec .rec-controls { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; padding: 12px 14px; border-bottom: 1px solid var(--line); }
.rec .rec-search { background: var(--input-bg); border: 1px solid var(--line); border-radius: 7px; padding: 6px 10px; color: var(--ink); font: inherit; font-size: 12.5px; min-width: 300px; outline: none; }
.rec .rec-search:focus { border-color: var(--brand); }
.rec .rec-tabs { display: inline-flex; background: var(--panel-2); border: 1px solid var(--line); border-radius: 9px; padding: 3px; gap: 2px; flex-wrap: wrap; }
.rec .rec-tab { padding: 5px 10px; background: transparent; border: 0; border-radius: 6px; cursor: pointer; color: var(--muted); font-size: 12.5px; font-weight: 600; font-family: inherit; }
.rec .rec-tab.active { background: var(--accent-solid); color: #fff; }
.rec .rec-tab:hover:not(.active) { background: var(--hover); color: var(--ink); }
.rec .rec-count { margin-left: auto; color: var(--muted); font-size: 12px; font-weight: 600; }
.rec .rec-legend { display: flex; flex-wrap: wrap; gap: 6px 18px; padding: 10px 14px; border-bottom: 1px solid var(--line); font-size: 11.5px; color: var(--muted); }
.rec .rec-i { display: inline-block; width: 9px; height: 9px; border-radius: 3px; margin-right: 6px; }
.rec .rec-i.brand { background: var(--rec-brand); }
.rec .rec-i.green { background: var(--rec-green); }
.rec .rec-i.amber { background: var(--rec-amber); }
.rec .rec-i.violet { background: var(--rec-violet); }
.rec .rec-table th { white-space: nowrap; font-size: 11px; text-transform: uppercase; letter-spacing: .5px; }
.rec .rec-table th.r, .rec .rec-table td.r { text-align: right; }
.rec .rec-table th.l, .rec .rec-table td.l { text-align: left; }
.rec .rec-table th.sortable { cursor: pointer; user-select: none; }
.rec .rec-table th.sorted { color: var(--brand); }
.rec .rec-table td { font-size: 12.5px; }
/* Имена вроде «ООО МКК Опора/МКК ДНД/МКК Блисс/МКК РОНА/ООО Бриз» тянули
   первую колонку на 370px и в одиночку выталкивали таблицу в скролл. Ограничим
   и перенесём в две строки — обрезать нельзя, названия различаются хвостом. */
.rec .rec-table th:first-child, .rec .rec-table td:first-child { max-width: 300px; white-space: normal; }
.rec .rec-row { cursor: pointer; }
.rec .rec-row:hover td { background: var(--hover); }
.rec .rec-row.open td { background: var(--panel-2); font-weight: 600; }
.rec .rec-mut { color: var(--muted); font-weight: 400; }
.rec .rec-pos { color: var(--success-text); }
.rec .rec-neg, .rec .rec-red { color: var(--danger-text); }
.rec .rec-green { color: var(--success-text); }
.rec .rec-amber { color: var(--queue); }
.rec .rec-violet { color: var(--epic-text); }
.rec .rec-accent { color: var(--brand); font-weight: 700; }
/* Колонка, по которой для этого рекламодателя и надо сравнивать (с НДС или
   без), подчёркнута пунктиром — вторая остаётся для проверки. */
.rec .rec-table td .rec-best { text-decoration: underline; text-decoration-style: dotted; text-underline-offset: 3px; }
.rec .rec-table td span small { display: block; font-size: 10.5px; opacity: .75; font-weight: 500; }
.rec .rec-chip { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 11px; font-weight: 700; border: 1px solid var(--line); }
.rec .rec-chip.pos { color: var(--success-text); border-color: var(--success-text); }
.rec .rec-chip.warn { color: var(--queue); border-color: var(--queue); }
.rec .rec-chip.neg { color: var(--danger-text); border-color: var(--danger-text); }
.rec .rec-chip.violet { color: var(--epic-text); border-color: var(--epic-text); }
.rec .rec-more td { text-align: center; padding: 10px; }

/* раскрытая строка */
.rec .rec-detail > td { background: var(--panel-2); padding: 14px 16px; }
.rec .rec-detail-grid { display: grid; grid-template-columns: minmax(0, 2.2fr) minmax(0, 1fr); gap: 18px; }
.rec .rec-detail-h { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .6px; color: var(--muted); margin: 0 0 6px; }
.rec .rec-detail-side .rec-detail-h { margin-top: 12px; }
.rec .rec-detail-side .rec-detail-h:first-child { margin-top: 0; }
.rec .rec-sub { width: 100%; border-collapse: collapse; font-size: 12px; }
.rec .rec-sub th { text-align: left; color: var(--muted); font-weight: 600; font-size: 11px; padding: 4px 8px; border-bottom: 1px solid var(--line); }
.rec .rec-sub td { padding: 4px 8px; border-bottom: 1px solid var(--line); }
.rec .rec-sub th.r, .rec .rec-sub td.r { text-align: right; }
/* Таблица целей широкая и живёт под сеткой во всю ширину. Свой скролл-контейнер
   обязателен: без него восемь колонок распирали страницу (та же ловушка, что в §9),
   а длинные названия целей («…скорбалл 71-80 при займе от 20 000 руб.») переносятся. */
.rec .rec-subwrap { overflow-x: auto; }
.rec .rec-subwrap .rec-sub td.l { white-space: normal; overflow-wrap: anywhere; min-width: 260px; }
.rec .rec-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.rec .rec-tag { background: var(--panel); border: 1px solid var(--line); border-radius: 6px; padding: 2px 8px; font-size: 11.5px; }
@media (max-width: 1100px) { .rec .rec-detail-grid { grid-template-columns: 1fr; } }

/* выводы */
/* Выводы — сплошной текст: на широком экране строка в 1700px не читается,
   поэтому колонка остаётся книжной шириной, а панель тянется. */
.rec .rec-concl { padding: 4px 20px 18px; font-size: 13px; line-height: 1.6; color: var(--ink-soft); }
.rec .rec-concl h3, .rec .rec-concl ol, .rec .rec-concl ul { max-width: 1100px; }
.rec .rec-concl h3 { font-size: 13px; font-weight: 700; color: var(--ink); margin: 18px 0 6px; }
.rec .rec-concl ol, .rec .rec-concl ul { margin: 0; padding-left: 20px; }
.rec .rec-concl li { margin-bottom: 6px; }
.rec .rec-concl b { color: var(--ink); }
/* Показывается только с классом .on: собственный display перебил бы атрибут
   hidden, и текст «расхождений нет» оставался бы в потоке поверх таблицы. */
.rec .rec-chart-empty { display: none; align-items: center; justify-content: center; height: 100%; color: var(--muted); font-size: 12.5px; text-align: center; padding: 0 20px; }
.rec .rec-chart-empty.on { display: flex; }
/* Строка, к которой перешли кликом по столбцу графика: подсветка гаснет сама,
   чтобы взгляд нашёл её после прокрутки. */
.rec .rec-row.rec-flash > td { animation: rec-flash 1.8s ease-out; }
@keyframes rec-flash {
    0%, 30% { background: var(--now-tint); box-shadow: inset 2px 0 0 var(--rec-brand); }
    100% { background: transparent; box-shadow: none; }
}
@media (prefers-reduced-motion: reduce) {
    .rec .rec-row.rec-flash > td { animation: none; background: var(--now-tint); }
}
/* Пунктир под названием колонки — признак того, что у неё есть подсказка
   (title): названия вроде «База CPA» без объяснения не читаются. */
.rec .rec-table th .rec-th-hint { border-bottom: 1px dotted var(--muted); padding-bottom: 1px; }
.rec .rec-table th:hover .rec-th-hint { border-bottom-color: var(--brand); }


/* ------------------------------------------------ «Маркетинг → Бот регистраций» (bot.php) */
.bot .flash { margin: 0 0 12px; }
.bot code { background: var(--hover); padding: 0 4px; border-radius: 3px; font-size: 12px; }
.bot-event-pick select { font-size: 14px; font-weight: 600; padding: 7px 10px; min-width: 280px; max-width: 100%; }
.bot-event-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 18px; font-size: 13px; color: var(--ink-soft); margin-bottom: 8px; }
.bot-event-actions { display: inline-flex; gap: 6px; margin-left: auto; }
.bot-badge { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .4px; padding: 2px 8px; border-radius: 10px; }
.bot-badge.on { background: var(--accent-solid); color: #fff; }
.bot-tabs { margin-bottom: 4px; }
.bot-tabs .seg-btn, .bot-filter .seg-btn { text-decoration: none; display: inline-block; }
.bot-filter { flex-wrap: wrap; justify-content: flex-start; }
.bot-search { margin-left: auto; }
.bot-search input { width: 260px; max-width: 100%; }
.bot-table td { vertical-align: middle; }
.bot-table td.nowrap, .bot .nowrap { white-space: nowrap; }
.bot-status { font-size: 12px; font-weight: 600; padding: 2px 8px; border-radius: 10px; white-space: nowrap; background: var(--hover); color: var(--ink-soft); }
.bot-status.st-confirmed { color: var(--success-text); background: rgba(78, 194, 122, .12); }
.bot-status.st-direct { color: var(--brand); background: rgba(111, 168, 255, .12); }
.bot-status.st-pending { color: var(--queue); background: rgba(224, 169, 43, .12); }
.bot-status.st-blocked { color: var(--danger-text); background: rgba(242, 104, 109, .12); }
.bot-msg-form textarea { width: 100%; font-size: 14px; line-height: 1.45; padding: 9px 11px; resize: vertical; }
.bot-msg-form input[type=text] { width: 100%; padding: 8px 10px; }
.bot-row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); gap: 12px; }
@media (max-width: 700px) { .bot-row { grid-template-columns: minmax(0, 1fr); } }
.bot-note { margin: 0 0 12px; font-size: 13px; }
.bot-bc { border-top: 1px solid var(--line); padding: 8px 0; font-size: 13px; }
.bot-bc summary { cursor: pointer; }
.bot-bc-text { margin: 8px 0; padding: 10px 12px; background: var(--panel-2); border-radius: 8px; line-height: 1.5; color: var(--ink-soft); }
.bot-check { display: flex; gap: 8px; align-items: flex-start; font-size: 13px; color: var(--ink-soft); }
.modal-body input[type=datetime-local] {
    width: 100%; padding: 9px 11px; font-size: 14px;
    background: var(--input-bg); color: var(--ink); border: 1px solid var(--line); border-radius: 6px;
    color-scheme: dark;
}
:root[data-theme="light"] .modal-body input[type=datetime-local] { color-scheme: light; }
/* bot.php — broadcasts */
.bot-bc-editor { border-bottom: 1px solid var(--line); padding-bottom: 14px; margin-bottom: 14px; }
.bot-bc-editor select { width: 100%; padding: 8px 10px; }
.bot-bc-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 10px 0; }
.bot-bc-test { display: inline-flex; gap: 6px; }
.bot-bc-test input { width: 190px; padding: 7px 9px; }
.bot-bc-actions .bot-send { margin-left: auto; background: var(--accent-solid); border-color: var(--accent-solid); color: #fff; }
.bot-bc-delete { display: block; margin: -6px 0 14px; }
#bc-len.over { color: var(--danger-text); font-weight: 700; }
.bot-bc-card { border-bottom: 1px solid var(--line); padding-bottom: 12px; margin-bottom: 14px; font-size: 13px; }
.bot-bc-head { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-bottom: 10px; font-size: 14px; }
.bot-progress { height: 8px; border-radius: 4px; background: var(--hover); overflow: hidden; margin: 6px 0 8px; }
.bot-progress > div { height: 100%; background: var(--accent-solid); transition: width .3s; }
.bot-bc-stats { display: flex; flex-wrap: wrap; gap: 6px 16px; font-size: 13px; color: var(--ink-soft); }
.bot-bc-stats .dl-sent b { color: var(--success-text); }
.bot-bc-stats .dl-failed b, .bot-bc-stats .dl-blocked b { color: var(--danger-text); }
.bot-bcst { font-size: 12px; font-weight: 600; padding: 2px 8px; border-radius: 10px; white-space: nowrap; background: var(--hover); color: var(--ink-soft); }
.bot-bcst.st-done { color: var(--success-text); background: rgba(78, 194, 122, .12); }
.bot-bcst.st-sending, .bot-bcst.st-queued { color: var(--brand); background: rgba(111, 168, 255, .12); }
.bot-bcst.st-cancelled { color: var(--danger-text); background: rgba(242, 104, 109, .12); }
.bot-bc-list tr.is-current td { background: var(--hover); }
.bot-bc-list a { color: var(--brand); text-decoration: none; }
.bot-bc-list a:hover { text-decoration: underline; }
.bot-bc-errors { margin: 8px 0 0; padding-left: 18px; font-size: 12px; color: var(--danger-text); }

/* bot.php — compact layout (overrides the first bot block above) */
.bot-eventbar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 18px; }
.bot-event-pick { display: flex; align-items: center; gap: 8px; margin: 0; }
.bot-event-pick label { font-size: 12px; color: var(--muted); }
.bot-event-pick select { min-width: 240px; max-width: 420px; font-size: 13px; font-weight: 600; padding: 6px 8px; }
.bot-eventbar .bot-event-meta { display: inline-flex; gap: 12px; margin: 0; font-size: 12px; color: var(--muted); }
.bot-eventbar .bot-event-actions { margin-left: 0; }
.bot-event-new { margin-left: auto; }
.bot-eventbar-panel { padding: 10px 14px; }
.bot-eventbar-panel details.help { margin: 8px 0 0; }
.bot .auth-field { margin-bottom: 10px; }
.bot .auth-field > span { font-size: 11px; margin-bottom: 3px; }
.bot .field-hint { font-size: 11px; }
.bot-msg-form input[type=text], .bot-bc-editor select { padding: 6px 9px; font-size: 13px; }
.bot-msg-form textarea { font-size: 13px; padding: 7px 9px; }
.bot-row { grid-template-columns: minmax(0, 2fr) minmax(0, 3fr); gap: 10px; }
.bot-bc-test input { width: 170px; padding: 5px 8px; font-size: 12px; }
.bot-bc-actions .btn { padding: 5px 11px; font-size: 12px; }

/* form + Telegram-like preview side by side; the form never grows past 680px */
.bot-compose { display: grid; grid-template-columns: minmax(0, 680px) minmax(240px, 340px); gap: 18px; align-items: start; }
@media (max-width: 900px) { .bot-compose { grid-template-columns: minmax(0, 1fr); } }
.bot-msg-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 820px), 1fr)); gap: 14px; align-items: start; }
.bot-msg-grid > .panel { margin-bottom: 0; }

.bot-bc-layout.split { display: grid; grid-template-columns: minmax(0, 1100px) minmax(380px, 1fr); gap: 14px; align-items: start; }
.bot-bc-layout.split > .panel { margin-bottom: 0; }
@media (max-width: 1500px) { .bot-bc-layout.split { grid-template-columns: minmax(0, 1fr); } .bot-bc-layout.split > .panel { margin-bottom: 14px; } }
.bot-bc-side h2 { margin-bottom: 8px; }
.bot-bc-side .bot-table { font-size: 12px; }
.bot-bc-card { max-width: 1040px; }
.bot-bc-text { max-width: 680px; }

.bot-preview { position: sticky; top: 64px; background: #0e1621; border-radius: 10px; padding: 10px 12px 12px; }
.bot-preview-cap { font-size: 11px; color: #7f91a4; margin-bottom: 6px; }
.tg-bubble { background: #182533; color: #e9eef5; border-radius: 12px 12px 12px 4px; overflow: hidden; font-size: 13px; line-height: 1.4; }
.tg-bubble img { display: block; width: 100%; max-height: 320px; object-fit: cover; }
.tg-text { padding: 7px 10px 8px; overflow-wrap: anywhere; }
.tg-text a { color: #6ab3f3; }
.tg-text code { background: rgba(255, 255, 255, .08); padding: 0 3px; border-radius: 3px; font-size: 12px; }
.tg-empty { color: #6d7f92; font-style: italic; }
.tg-doc { padding: 10px; background: rgba(255, 255, 255, .05); font-size: 12px; color: #b9c6d4; }
.tg-btn { margin-top: 4px; background: rgba(24, 37, 51, .85); color: #fff; text-align: center; padding: 7px 10px; border-radius: 8px; font-size: 13px; }
.tg-btn-bad { color: #f2686d; }
:root[data-theme="light"] .bot-preview { background: #d9e3ec; }
:root[data-theme="light"] .bot-preview-cap { color: #5d6f82; }
:root[data-theme="light"] .tg-bubble { background: #fff; color: #000; }
:root[data-theme="light"] .tg-text a { color: #168acd; }
:root[data-theme="light"] .tg-doc { background: #f1f4f7; color: #3b4a5a; }
:root[data-theme="light"] .tg-btn { background: rgba(80, 104, 128, .45); }
@media (max-width: 600px) {
    .bot-event-pick { flex: 1 1 100%; min-width: 0; }
    .bot-event-pick select { min-width: 0; flex: 1 1 auto; width: 100%; }
    .bot-event-new { margin-left: 0; }
}
.bot details.help code { overflow-wrap: anywhere; }
.bot-idle-note { margin-top: 8px; padding: 7px 10px; border-radius: 6px; font-size: 12px; background: rgba(224, 169, 43, .1); border: 1px solid rgba(224, 169, 43, .35); color: var(--ink-soft); }
.bot-idle-note a { color: var(--brand); margin-left: 4px; }
.bot-idle { border-style: dashed; }
.bot-topbar-link { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 600; color: var(--bar-ink); text-decoration: none; padding: 4px 10px; border-radius: 14px; background: rgba(42, 171, 238, .18); border: 1px solid rgba(42, 171, 238, .45); }
.bot-topbar-link:hover { background: rgba(42, 171, 238, .3); }
.bot-tg-icon { display: inline-grid; place-items: center; width: 18px; height: 18px; border-radius: 50%; background: #2aabee; color: #fff; font-size: 10px; }
