/* ============ ИКОНКИ ============
   Спрайт Lucide, инлайнится в index.php. Иконки контурные, цвет берется
   от CSS-свойства color через stroke="currentColor" - красить надо color,
   НЕ fill. За границу <use> наследуются только color, stroke-width и
   stroke-linecap/linejoin, покрасить отдельный path внутри нельзя.

   stroke-width снижен со штатных 2 до 1.6: в паке иконки нарисованы под
   24px, а у нас они живут на 15-16px в плотном интерфейсе, где двойка
   читается заметно жирнее текста рядом и начинает с ним спорить. */
.ico {
    width: 16px;
    height: 16px;
    flex: none;
    stroke-width: 1.6;
    vertical-align: -0.15em;
}

/* Иконка - спутник текста, а не его соперник. Приглушена на ступень
   относительно заголовка: ведет взгляд к строке, но читается строка. */
.panel-title .ico,
.tab .ico {
    color: var(--muted);
    transition: color var(--transition);
}

.tab:hover .ico { color: var(--dim); }
.tab.active .ico { color: var(--accent); }

/* У главной панели раздела иконка идет в акцент вместе с заголовком */
.v3-lead .panel-title .ico { color: var(--accent); }

.panel-head .ico { margin-right: var(--sp-2); }

/* Шеврон аккордеона: поворачивается при раскрытии, направление = состояние */
.v3-fold .ico-fold {
    margin-left: var(--sp-2);
    color: var(--muted);
    transition: transform var(--transition), color var(--transition);
}

.v3-fold > details[open] .ico-fold { transform: rotate(180deg); }
.v3-fold > details:hover .ico-fold { color: var(--dim); }

@media (prefers-reduced-motion: reduce) {
    .v3-fold .ico-fold { transition: none; }
}

/* === COMPONENTS.CSS === */
/* demo/assets/css/components.css */
/* НАЗНАЧЕНИЕ: панели, KPI со сравнением к прошлому периоду, индикаторы,
   таблицы-рейтинги, воронка, графики, карта локаций, чат */
/* СВЯЗИ: variables.css (токены), main.css, views.js, charts.js, chat.js */
/* РАЗМЕР: ~760 строк */

/* ============ ПАНЕЛЬ ============ */

/* Карточка на светлом фоне держится тенью, рамка только чтобы не расплывалась
   на белой панели поверх белой панели. Отступ 18px вместо 14: демо показывают
   с проектора, тесная карточка там читается как сплошное пятно. */
/* Тени на панелях СНЯТЫ. В системе Apple тень в интерфейсе ровно одна и
   лежит под тем, что действительно парит над плоскостью (у нас это точки
   карты и их подписи). Разделение соседних блоков делается волосяной
   рамкой, а не тенью: восемь карточек, каждая со своей тенью, дают рябь
   по всей странице и читаются как набор наклеек, а не как один документ. */
.panel {
    padding: var(--sp-4) 18px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-3);
}

/* ---- ДВА ПЛАНА КАРТОЧЕК ----
   Одинаковость - главный маркер сгенерированной верстки: восемь панелей
   одного веса, с одной рамкой, одной тенью и одним синим капсом сверху
   не дают взгляду точки входа, и страница читается как таблица таблиц.
   Вводим ровно два уровня, различие сдержанное - вес, не цвет:

   первый план  - панель, ради которой открыли раздел (находки системы,
                  главная таблица раздела): белая поверхность, тень, рамка;
   второй план  - справочная панель (индикаторы раздела, содержимое
                  свернутых блоков): та же геометрия, но без тени и на
                  --surface-2. Она не всплывает над фоном, а лежит в нем.

   Классов для этого не заводим: разметка чужая (views.js, зона W3),
   а структура уже несет смысл - .section-break стоит на справочных
   панелях индикаторов, <details> на свернутом справочном материале. */
.panel.section-break,
/* Свернутый аккордеон = карточка, а не голая строка.
   Пока эти блоки лежали по одному на всю ширину, отсутствие рамки читалось
   как разделитель внизу страницы. Поставленные в ряд по двое, они слиплись:
   заголовок второго начинался сразу за словом «развернуть» первого, и пара
   читалась как одна строка текста. Рамка возвращает границу блока.
   Уровень - второй план: без тени, на приглушенном фоне. */
.v3-fold > details {
    height: 100%;
    padding: var(--sp-2) var(--sp-3);
    background: var(--surface-2);
    border: 1px solid color-mix(in oklab, var(--border) 60%, transparent);
    border-radius: var(--r-3);
    transition: border-color var(--transition);
}

.v3-fold > details:hover { border-color: var(--border); }

/* Раскрытый занимает обе колонки (правило в main.css) и поднимается
   на первый план: внутри уже содержательная таблица, а не подпись. */
.v3-fold > details[open] {
    background: var(--surface);
    border-color: var(--border);
}

/* Слово «развернуть» меняется на «свернуть» без JS */
.v3-fold > details > summary .v3-toggle::after { content: 'развернуть'; }
.v3-fold > details[open] > summary .v3-toggle::after { content: 'свернуть'; }

details .panel {
    background: var(--surface-2);
    border-color: color-mix(in oklab, var(--border) 60%, transparent);
    box-shadow: none;
}

/* Заголовок и подпись рядом не должны слипаться - gap нужен у родителя.
   Отступ до тела 12, а не 16: шапка панели - часть той же группы, что и
   ее содержимое, а 16 уже читается как расстояние между блоками. */
.panel-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--sp-3);
    margin-bottom: var(--gap);
}

.panel-title {
    font-size: var(--fs-5);
    font-weight: 700;
}

/* Заголовок панели. Был 12px/700 uppercase цветом акцента на КАЖДОЙ панели -
   и на экране Обзора получалось пять синих капсов, конкурирующих между собой
   и с цифрами KPI. У Strimix акцент в интерфейсе один (активная вкладка),
   а заголовок панели живет на служебном уровне.
   Стало: рядовой заголовок - обычный текст 15px/600 приглушенного цвета,
   без капса и без акцента. Капс дороже обычного текста по вниманию, тратить
   его восемь раз на одном экране нельзя. */
.panel-title.is-label {
    font-size: var(--fs-5);
    font-weight: 600;
    line-height: 1.35;
    letter-spacing: -0.005em;
    text-transform: none;
    color: var(--dim);
}

/* Акцентный капс остается РОВНО на одной панели раздела - той, что помечена
   классом .v3-lead в разметке. Это «что система нашла сама»: то, ради чего
   открывают дашборд. Один акцент на экран работает как указатель
   «начни отсюда»; восемь одинаковых акцентов не работают никак.
   Привязка именно к КЛАССУ, а не к позиции в сетке: структурный селектор
   молча переставал попадать в панель при любой перестановке блоков. */
.v3-lead > .panel-head .panel-title.is-label {
    font-size: 12px;
    font-weight: 700;
    line-height: 1.4;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--accent);
}

/* У панели второго плана заголовок еще на ступень тише: это подпись
   к справочному материалу, а не название блока */
.panel.section-break > .panel-head .panel-title.is-label,
details .panel > .panel-head .panel-title.is-label {
    font-size: var(--fs-4);
    color: var(--muted);
}

.panel-sub {
    color: var(--muted);
    font-size: var(--fs-3);
    font-weight: 400;
}

/* ---- ДЛИНА СТРОКИ В АБЗАЦАХ ПАНЕЛИ ----
   Замер до правки: пояснение под «Утренней сводкой» шло строками по 191 знаку,
   вводный абзац находок - по 118. Читать такое нельзя: на строке длиннее
   ~85 знаков возврат каретки промахивается, и абзац перечитывают дважды.
   Ограничение ставим в ch (ширина знака), а не в px - переживет смену шрифта.
   72ch для вводного абзаца 15px (он крупнее, знаков в строке должно быть
   меньше) и 78ch для служебных пояснений 11-13px. */
.panel > p {
    max-width: 72ch;
}

.panel > p.muted,
.panel p.footnote {
    max-width: 78ch;
}

/* ============ KPI ============ */

/* 240px вместо 210: при 210 крупная цифра с единицей не влезала в колонку
   на 1280 и 768, "грн" падало на вторую строку и карточка вырастала
   до 71px против 44 у соседей - подписи теряли общую базовую линию. */
.kpi-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: var(--gap);
    align-items: stretch;
}

/* Пять карточек в ряду, где помещается четыре, давали одинокую пятую
   во всю ширину - она читалась как отдельный блок, а не как часть ряда.
   auto-fill вместо auto-fit оставляет пустые колонки: последняя карточка
   встает по сетке и сохраняет ширину соседей. */
@media (min-width: 781px) and (max-width: 1500px) {
    .kpi-row { grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); }
}

/* KPI-карточка. Белая поверхность вместо серой: на светлой теме серая плитка
   на сером фоне сливается, а тень отделяет ее без лишней рамки. */
.kpi-card {
    position: relative;
    padding: 18px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-3);
    transition: border-color var(--transition), background var(--transition);
}

/* Отклик на наведение - не подъем тенью, а уплотнение рамки: карточка
   остается в плоскости страницы, но показывает, что она живая. */
.kpi-card:hover { border-color: var(--border-strong); }

/* Цветные рамки по статусу СНЯТЫ намеренно (эталон Strimix, раздел 10 п.8).
   Было: зеленая рамка у выручки, красная у дебиторки, красная цифра, плюс
   красные строки в 11 случаях из 15 ниже. Когда красным помечено все,
   не помечено ничего - рамка переставала работать как сигнал и оставалась
   просто шумом по периметру. У Strimix карточки одинаковые, а статус несет
   дельта: она и так стоит в каждой карточке и говорит направление.
   Цвет цифры оставлен: это ОДИН сигнал на карточку, а не два. */
.kpi-card.is-main,
.kpi-card.is-warn,
.kpi-card.is-alert { border-color: var(--border); }

/* Цифра и ее единица - всегда одна строка. flex-wrap отсюда убран
   намеренно: перенос ломал общую базовую линию всего ряда KPI.
   Размер текучий (--fs-7 = clamp), поэтому вместо переноса цифра
   ужимается вместе с шириной колонки. */
.kpi-value {
    display: flex;
    align-items: baseline;
    gap: 6px;
    min-height: calc(var(--fs-7) * 1.25);
    font-family: var(--font-mono);
    font-size: var(--fs-7);
    font-weight: 600;
    line-height: 1.2;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    /* Разделитель разрядов в ru-RU - неразрывный пробел, а в моноширинном
       он занимает целый знак: "17 106 400" разваливалось на три отдельных
       числа. Сжимаем именно пробел, цифры и их выравнивание не трогаем. */
    word-spacing: -0.22em;
}

.kpi-card.is-main .kpi-value { color: var(--ok); }
.kpi-card.is-alert .kpi-value { color: var(--err); }

.kpi-cur {
    flex: 0 0 auto;
    font-size: var(--fs-3);
    font-weight: 400;
    color: var(--muted);
    white-space: nowrap;
}

/* ---- РИТМ ВНУТРИ KPI-КАРТОЧКИ: ТРИ СТУПЕНИ, НЕ ОДНА ----
   Было 8 / 6 / 5 px - три произвольных числа, которые глаз читает как один
   и тот же отступ, то есть как четыре равнозначные строки без иерархии.
   Стало по лестнице токенов: 4 внутри пары «подпись-цифра», 8 между цифрой
   и ее сравнением (это одна смысловая группа: сколько и относительно чего),
   12 перед пояснением - оно уже другая группа, дополнительные факты.
   Ровно этим Strimix и отличается: 12 внутри группы, 16 в сетке, 20 между
   блоками. Один отступ на все уровни - маркер набора по месту. */
.kpi-label {
    margin-top: 0;   /* метка - первый элемент карточки, отступ дает padding */
    font-size: var(--fs-4);
}

/* Пара «метка - цифра» держится вместе: самая тесная ступень лестницы */
.kpi-value { margin-top: var(--gap-xs); }

.kpi-sub {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--gap-s);
    margin-top: var(--gap);
    color: var(--muted);
    font-size: var(--fs-3);
}

/* ---- Сравнение с прошлым периодом ----
   Ключевой прием Strimix: цифра сама по себе ничего не говорит, пока рядом нет
   ответа «по сравнению с чем». Одна строка: бейдж дельты + база сравнения.
   Ставится под .kpi-value вместо .kpi-sub или вместе с ним. */

.kpi-compare {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--gap-xs) var(--gap-s);
    margin-top: var(--gap-s);
    min-height: 22px;
}

/* База сравнения. Серым и мельче: это справка, а не показатель.
   Цифра моноширинная, чтобы выравнивалась с цифрой выше по разрядам. */
.kpi-was {
    color: var(--muted);
    font-size: var(--fs-2);
    white-space: nowrap;
}

.kpi-was b {
    font-family: var(--font-mono);
    font-weight: 500;
    font-variant-numeric: tabular-nums;
    color: var(--dim);
}

/* Слово "было" перед базой сравнения: без него цифра рядом с дельтой
   читается как второй показатель, а не как точка отсчета */
.kpi-was::before {
    content: 'было ';
    color: var(--muted);
    font-weight: 400;
}

/* Когда база не влезает в строку с дельтой - переносится целиком,
   а не рвется посередине числа. Поэтому white-space:nowrap выше. */
@media (max-width: 400px) {
    .kpi-compare { gap: var(--sp-1) var(--sp-2); }
}

/* ============ ДЕЛЬТА ============ */

.delta {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    padding: 2px 7px;
    border-radius: var(--r-1);
    font-family: var(--font-mono);
    font-size: var(--fs-2);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.delta.up { background: var(--ok-soft); color: var(--ok); }
.delta.down { background: var(--err-soft); color: var(--err); }
.delta.flat { background: var(--surface-hover); color: var(--muted); }

/* Статус внутри таблицы - цветная точка плюс текст, а не залитая плашка.
   Та же причина, что и у дельт (эталон п.8): в колонке из десяти строк
   десять заливок весят больше, чем данные рядом. Точка несет цвет,
   текст остается спокойным. */
.rank .badge {
    padding: 0;
    background: none;
    color: var(--dim);
    font-weight: 500;
    gap: 6px;
}

.rank .badge::before {
    content: '';
    width: 7px;
    height: 7px;
    border-radius: 50%;
    flex: 0 0 auto;
    background: currentColor;
}

.rank .badge.ok::before { background: var(--ok); }
.rank .badge.warn::before { background: var(--warn); }
.rank .badge.alert::before { background: var(--err); }
.rank .badge.alert { color: var(--err); font-weight: 600; }

/* Внутри таблицы дельта - цветной текст, а не плашка. В колонке из десяти
   строк десять залитых плашек превращают столбец в светофор и весят больше,
   чем сами цифры рядом. Плашка остается там, где дельта одна на блок:
   в KPI-карточке и в заголовках. */
.rank .delta {
    background: none;
    padding: 0;
    font-weight: 600;
}

/* Дельта внутри строки сравнения крупнее обычной: это второе по важности
   число на карточке после самого показателя */
.kpi-compare .delta {
    padding: 3px 8px;
    font-size: var(--fs-3);
}

/* ============ ЧИПЫ ============ */

.chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
}

.chip {
    flex: 0 0 auto;
    white-space: nowrap;
    display: flex;
    align-items: baseline;
    gap: 6px;
    padding: 9px 12px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    font-size: var(--fs-4);
}

.chip-val {
    font-family: var(--font-mono);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    word-spacing: -0.2em;
}

/* Сжатие пробела нужно ЧИСЛАМ, но .chip-val переиспользуют и под обычный
   текст - в блоке «что еще умеет второй мозг» заголовки схлопывались в
   «Транскрибациязвонков». Класс .br-skill-t помечает текстовый случай.
   Тот же прием уже стоит на .rank .nm для имен в таблицах. */
.br-skill-t {
    font-family: var(--font-display);
    word-spacing: normal;
    font-weight: 700;
    color: var(--text);
}

.chip-label {
    color: var(--muted);
    font-size: var(--fs-2);
}

/* ============ БЕЙДЖ ============ */

.badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 8px;
    border-radius: var(--r-pill);
    font-size: var(--fs-2);
    font-weight: 600;
    white-space: nowrap;
}

.badge.ok { background: var(--ok-soft); color: var(--ok); }
.badge.warn { background: var(--warn-soft); color: var(--warn); }
.badge.alert { background: var(--err-soft); color: var(--err); }
.badge.neutral { background: var(--surface-hover); color: var(--muted); }

/* ============ ИНДИКАТОРЫ ============ */

.ind-list {
    display: flex;
    flex-direction: column;
    gap: 0;
}

/* Строка индикатора. Была карточкой внутри карточки: своя рамка, свой фон,
   свое скругление - и все это лежало внутри .panel, которая уже карточка.
   Плюс цветная полоска слева 3px, повторенная около двадцати раз подряд.
   Стало: строка списка без рамки и фона, разделенная линией от соседней.
   Уровень показывает цветная точка слева и цвет числа справа - двух
   сигналов достаточно, третий (полоска) был шумом. */
/* align-items: start вместо center. Пока текст находки был однострочным,
   разницы не было; после ограничения строки в 78ch находка занимает три-четыре
   строки, и центрирование уводило точку уровня и число на середину абзаца -
   строка переставала читаться как «заголовок, пояснение, число».
   Ступени отступа: 12 по вертикали между строками списка (это соседние
   элементы одной группы), 8 по горизонтали до края панели. */
.ind {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: start;
    gap: var(--gap-s) var(--gap);
    padding: var(--gap) var(--gap-s);
    border-radius: var(--r-1);
    transition: background var(--transition);
}

/* Точка уровня стоит на базовой линии названия, а не в самом верху строки */
.ind > .ind-dot { margin-top: 6px; }

.ind + .ind { box-shadow: 0 -1px 0 var(--border); }

.ind:hover { background: var(--surface-2); }

/* Критичное вытаскиваем фоном, а не рамкой: заливка работает и в списке,
   и в темной теме, и не добавляет еще одну карточку в карточку.
   Заливка снята совсем (эталон п.8: когда красным помечено все, не помечено
   ничего). В списке из 15 строк 11 критичных - сплошная заливка красила
   три четверти блока и переставала быть сигналом. Красное осталось там, где
   оно единично и по делу: цифра справа и точка слева. */
.ind.is-alert { background: none; }
.ind.is-alert:hover { background: var(--surface-2); }

/* Вторая и последующие находки в списке. Первая находка - главная, она
   может нести акцент; остальные идут ровным списком. Правило живет здесь,
   чтобы views.js не гасил фон инлайном ради перебивания моей специфичности. */
.ind.is-flat,
.ind.is-alert.is-flat { background: none; }



.ind-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    flex: 0 0 auto;
}

.ind.is-ok .ind-dot { background: var(--ok); box-shadow: 0 0 0 3px var(--ok-soft); }
.ind.is-warn .ind-dot { background: var(--warn); box-shadow: 0 0 0 3px var(--warn-soft); }
/* Пульсация СНЯТА. Четыре мигающие точки подряд шумят ровно там, где мы
   убирали крик: в списке находок критичных строк большинство, и пульсировали
   они все сразу. На статике и на скриншоте эффект не виден вовсе, а в живом
   показе с проектора отвлекает от чтения текста находки.
   Цвет точки остается - он и есть сигнал. */
.ind.is-alert .ind-dot {
    background: var(--err);
    box-shadow: 0 0 0 3px var(--err-soft);
}

.ind-body { min-width: 0; }

.ind-name {
    font-size: var(--fs-4);
    font-weight: 600;
}

/* Текст находки. Главная правка: длина строки.
   На контейнере 1320px абзац находки шел строками по 95-100 знаков - глаз,
   дочитав до правого края, не находит начало следующей строки и перечитывает
   предыдущую. Типографическая норма 60-80 знаков; берем 78ch как верхнюю
   границу, чтобы короткие находки не превращались в узкую колонку.
   Мера в ch, а не в px: ch привязан к ширине знака текущего шрифта, поэтому
   при смене Manrope на любой другой длина строки останется той же в знаках.
   Интерлиньяж поднят с 1.4 до 1.55 - на трех-четырех строках подряд 1.4
   склеивает абзац в серое пятно. */
.ind-text {
    display: block;
    max-width: 78ch;
    margin-top: 3px;
    color: var(--muted);
    font-size: var(--fs-3);
    line-height: 1.55;
}

.ind-num {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    word-spacing: -0.2em;
}

/* Число индикатора - вторая по величине цифра на странице после KPI.
   Apple HIG: трекинг обратно пропорционален кеглю, у крупного жирного
   набора межбуквенное растет оптически и его надо подбирать. При 15px/700
   в моноширинном без отрицательного трекинга цифры стоят разреженно и
   строка читается как таблица кодов, а не как показатель. */
.ind-cur {
    font-size: var(--fs-5);
    font-weight: 700;
    letter-spacing: -0.015em;
}

.ind.is-alert .ind-cur { color: var(--err); }
.ind.is-warn .ind-cur { color: var(--warn); }
.ind.is-ok .ind-cur { color: var(--ok); }

/* Подпись нормы, 11px. По той же шкале - у мелкого кегля трекинг
   положительный, иначе знаки слипаются */
.ind-norm {
    color: var(--muted);
    font-size: var(--fs-2);
    letter-spacing: 0.01em;
}

/* ============ РЕЙТИНГ-ТАБЛИЦА ============ */

.rank {
    width: 100%;
    border-collapse: collapse;
}

/* Разделитель ТОЛЬКО снизу и только у ячеек. Раньше линия висела на <tr>,
   из-за чего у последней строки ее приходилось снимать отдельным правилом. */
.rank tr { border-bottom: none; }

/* Зебры нет. У Strimix ее нет ни в одной таблице: чередование фона - это
   второй разделитель поверх линии, глаз начинает читать полосы вместо
   данных. Строку подсвечиваем только под курсором, и то едва заметно. */
.rank tbody tr {
    transition: background var(--transition);
    background: none;
}

.rank tbody tr:hover { background: var(--surface-2); }

.rank th {
    height: 40px;
    padding: 8px 14px;
    border-bottom: 1px solid var(--border);
    color: var(--muted);
    font-size: 11px;
    font-weight: 700;
    line-height: 1.5;
    text-align: left;
    text-transform: uppercase;
    letter-spacing: 0.06em;   /* 0.66px при 11px, но переживет смену кегля */
    white-space: nowrap;
}

/* Строка ровно 40px. height на td при border-collapse работает как
   минимальная высота строки - именно то, что нужно: перенос в длинном
   названии строку растянет, короткая останется 40. */
.rank td {
    height: 40px;
    padding: 7px 14px;
    border-bottom: 1px solid var(--border);
    vertical-align: middle;
    font-size: var(--fs-3);
    color: var(--dim);
}

.rank tbody tr:last-child td { border-bottom: none; }

/* Крайние ячейки прижимаем к краю панели, чтобы padding 14px не давал
   двойного отступа рядом с padding самой панели */
.rank th:first-child, .rank td:first-child { padding-left: 0; }
.rank th:last-child, .rank td:last-child { padding-right: 0; }

.rank .rk {
    width: 24px;
    padding-right: 0;
    color: var(--muted);
    font-size: var(--fs-2);
    text-align: right;
}

.rank .nm {
    font-size: var(--fs-3);
    font-weight: 500;
    color: var(--text);
}

/* Вторая строка в ячейке имени. Плотная line-height нужна, чтобы строка
   с подписью укладывалась в 44-48px, а не разъезжалась до 56: эталонные
   40px держат только однострочные таблицы, двухстрочная честно выше. */
.rank .nm .cnt {
    display: block;
    margin-top: 1px;
    color: var(--muted);
    font-size: var(--fs-2);
    line-height: 1.3;
    font-weight: 400;
}

.rank .nm { line-height: 1.3; }

.rank .val {
    font-family: var(--font-mono);
    letter-spacing: -0.01em;
    word-spacing: -0.2em;
    position: relative;
    min-width: 108px;
    padding-right: 8px;
    font-weight: 700;
    text-align: right;
    white-space: nowrap;
}

.rank .val .bar {
    display: block;
    height: 3px;
    margin-top: 4px;
    margin-left: auto;
    border-radius: var(--r-1);
    background: var(--accent);
    opacity: 0.55;
    transition: width 600ms var(--ease-out);
}

/* Итоговая строка. Отделена двойным воздухом сверху и линией потолще:
   это не очередная строка данных, а сумма над ними. Фона нет намеренно -
   заливка вернула бы зебру, от которой только что избавились. */
.rank tfoot td,
.rank tr.is-total td {
    height: 44px;
    padding-top: 10px;
    border-top: 2px solid var(--border-strong);
    border-bottom: none;
    color: var(--text);
    font-weight: 700;
}

.rank tfoot tr:hover,
.rank tr.is-total:hover { background: none; }

/* У предпоследней строки своя линия не нужна: над итогом уже есть своя */
.rank tbody tr:last-child td { border-bottom: none; }

.rank .r {
    text-align: right;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    word-spacing: -0.2em;
}

/* Имя в таблице - обычный шрифт. Это текст, а не показание прибора */
.rank .nm, .rank th { font-family: var(--font-display); word-spacing: normal; }

.tbl-wrap {
    overflow-x: auto;
    max-width: 100%;
}

/* ============ ВОРОНКА ============ */

.funnel { display: flex; flex-direction: column; gap: var(--sp-1); }

/* Колонки: подпись / полоса / цифры. minmax(0,1fr) на полосе обязателен -
   с голым 1fr длинная подпись справа ("55,6% при норме 68%") выдавливала
   дорожку в ноль, и на 390px этап Переговоры оставался вообще без полосы
   (замерено: trackW 0 при fillW 18). minmax запрещает колонке схлопнуться
   ниже заданного минимума. */
.funnel-step {
    display: grid;
    grid-template-columns: minmax(96px, 160px) minmax(60px, 1fr) auto;
    align-items: center;
    gap: var(--sp-3);
    padding: 5px 0;
}

/* Хвост с нормой переносить нельзя: он должен читаться одной мыслью */
.funnel-conv .norm {
    display: block;
    color: var(--muted);
}

.funnel-label { font-size: var(--fs-4); color: var(--dim); }

.funnel-track {
    height: 28px;
    background: var(--surface-2);
    border-radius: var(--radius);
    overflow: hidden;
}

.funnel-fill {
    word-spacing: -0.2em;
    display: flex;
    align-items: center;
    height: 100%;
    padding: 0 9px;
    border-radius: var(--radius);
    background: var(--accent);
    color: var(--on-accent);
    font-family: var(--font-mono);
    font-size: var(--fs-4);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    transition: width 700ms var(--ease-out);
}

.funnel-fill.is-alert { background: var(--err); color: var(--on-err); }

.funnel-conv {
    font-family: var(--font-mono);
    font-size: var(--fs-3);
    font-variant-numeric: tabular-nums;
    text-align: right;
    white-space: nowrap;
}

/* ============ СТОЛБЧАТЫЙ ГРАФИК (без библиотеки) ============ */

.chart {
    display: flex;
    align-items: flex-end;
    gap: var(--sp-1);
    height: 170px;
    padding-top: var(--sp-3);
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: thin;
}

.chart-col {
    flex: 1 1 0;
    min-width: 34px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-end;
    height: 100%;
    gap: var(--sp-1);
}

.chart-bar {
    width: 100%;
    max-width: 46px;
    border-radius: var(--r-1) var(--r-1) 0 0;
    background: var(--accent);
    transition: height 700ms var(--ease-out), opacity var(--transition);
}

.chart-col:hover .chart-bar { opacity: 0.8; }
.chart-bar.is-alert { background: var(--err); }
.chart-bar.is-warn { background: var(--warn); }

.chart-val {
    font-family: var(--font-mono);
    font-size: var(--fs-1);
    color: var(--dim);
    font-variant-numeric: tabular-nums;
}

.chart-lbl {
    font-family: var(--font-mono);
    word-spacing: -0.15em;
    font-size: var(--fs-1);
    color: var(--muted);
    white-space: nowrap;
}

/* Canvas-график.
   max-width обязателен: Chart.js меряет холст в момент отрисовки, а разделы
   кроме активного лежат в display:none. В скрытом разделе ширина родителя
   равна нулю, библиотека ставит на canvas свою inline-ширину от последнего
   известного размера - и при показе раздела холст оказывается шире экрана
   (замерено 566px на телефоне 390px). responsive:true это не лечит,
   лечит потолок в CSS. */
.chart-box {
    position: relative;
    width: 100%;
    max-width: 100%;
    height: 240px;
    overflow: hidden;
}

.chart-box.sm { height: 180px; }

.chart-box > canvas,
canvas {
    max-width: 100% !important;
}

/* Легенда */
.legend {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-3);
    margin-top: var(--sp-2);
}

.legend-item {
    display: flex;
    align-items: center;
    gap: 5px;
    color: var(--muted);
    font-size: var(--fs-2);
}

.legend-dot {
    width: 8px;
    height: 8px;
    border-radius: 2px;
}

/* ============ ЧАТ ============ */

.chat {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
    height: calc(100vh - 230px);
    min-height: 440px;
}

.chat-log {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    padding: var(--sp-4);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow-y: auto;
}

.msg {
    display: flex;
    flex-direction: column;
    max-width: 82%;
    padding: 9px 11px;
    border-radius: var(--radius);
    animation: msgIn 220ms var(--ease-out) both;
}

@keyframes msgIn {
    from { opacity: 0; transform: translateY(5px); }
    to { opacity: 1; transform: none; }
}

/* Вопрос владельца - справа, ответ мозга - слева */
.msg-q {
    align-self: flex-end;
    background: var(--accent);
    color: var(--on-accent);
    border-bottom-right-radius: 2px;
}

.msg-a {
    align-self: flex-start;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-bottom-left-radius: 2px;
    max-width: 88%;
}

.msg-sender {
    margin-bottom: 3px;
    font-size: var(--fs-1);
    opacity: 0.6;
}

.msg-text {
    font-size: var(--fs-4);
    line-height: 1.55;
    white-space: pre-wrap;
}

.msg-src {
    margin-top: 6px;
    padding-top: 5px;
    border-top: 1px solid var(--border);
    color: var(--muted);
    font-family: var(--font-mono);
    font-size: var(--fs-1);
}

/* Поле вопроса - главный элемент раздела: ради него сюда и заходят.
   Было 42px и мелким шрифтом, в ряду прочих блоков оно читалось как
   служебная строка поиска и терялось. Подняли до 56px, шрифт на две
   ступени крупнее, рамка в два пикселя и подложка акцентом - блок
   сам говорит «печатать здесь», без подписи-инструкции. */
.chat-input {
    display: flex;
    gap: var(--sp-3);
    padding: var(--sp-3);
    background: var(--accent-soft);
    border: 1px solid var(--accent-line);
    border-radius: var(--r-3);
}

.chat-field {
    flex: 1 1 auto;
    min-height: 56px;
    padding: 16px 18px;
    background: var(--surface);
    border: 2px solid var(--border-strong);
    border-radius: var(--r-2);
    color: var(--text);
    font-size: var(--fs-5);
    line-height: 1.4;
    resize: none;
    transition: border-color var(--transition), box-shadow var(--transition);
}

.chat-field::placeholder { color: var(--muted); }

.chat-field:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 4px var(--accent-soft);
}

/* На акцентной заливке иконка идет цветом текста кнопки, а не приглушенным:
   правило .panel-title .ico / .tab .ico сюда не достает, но подстрахуемся явно */
.btn .ico { color: currentColor; }

/* Кнопка догоняет поле по росту, иначе пара смотрится ступенькой */
.chat-input .btn {
    /* height:auto + align-items:stretch у флекса: кнопка тянется по фактической
       высоте поля. Фиксированные 56px давали ступеньку, когда поле в две строки
       выросло до 78px. */
    height: auto;
    align-self: stretch;
    padding: 0 26px;
    font-size: var(--fs-5);
}

@media (max-width: 560px) {
    .chat-input { flex-direction: column; }
    .chat-input .btn { width: 100%; }
}

.btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 42px;
    padding: 0 18px;
    background: var(--accent);
    border: 1px solid var(--accent);
    border-radius: var(--radius);
    color: var(--on-accent);
    font-size: var(--fs-4);
    font-weight: 700;
    transition: opacity var(--transition);
}

.btn:hover { opacity: 0.88; }
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.btn.ghost {
    background: var(--surface-2);
    border-color: var(--border);
    color: var(--dim);
    font-weight: 400;
}

.btn.ghost:hover { background: var(--surface-hover); color: var(--text); }

.suggests { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

.suggest {
    padding: 6px 11px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--r-pill);
    color: var(--dim);
    font-size: var(--fs-3);
    text-align: left;
    transition: background var(--transition), color var(--transition), border-color var(--transition);
}

.suggest:hover {
    background: var(--accent-soft);
    border-color: var(--accent-line);
    color: var(--text);
}

.typing { display: inline-flex; gap: 4px; padding: 3px 0; }

.typing i {
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--muted);
    animation: typeBlink 1.2s infinite;
}

.typing i:nth-child(2) { animation-delay: 0.2s; }
.typing i:nth-child(3) { animation-delay: 0.4s; }

@keyframes typeBlink {
    0%, 60%, 100% { opacity: 0.25; }
    30% { opacity: 1; }
}

/* ============ КАРТА ЛОКАЦИЙ ============ */
/* Пины расставляются в процентах через inline left/top, а не в координатах SVG:
   так views.js может двигать точку, не пересчитывая viewBox, и карта переживает
   замену подложки на другую страну без правки JS. */

/* Соотношение сторон карты = соотношение viewBox контура (100 x 68.58).
   Пины стоят в процентах от КОНТЕЙНЕРА (inline left/top из views.js), а SVG
   рисуется с preserveAspectRatio="none" - он растягивается ровно по рамке
   контейнера без полей, поэтому проценты пина и координаты viewBox
   совпадают один в один. Пропорция здесь нужна уже не как компенсация,
   а чтобы страна не выглядела сплюснутой. */
.map-wrap {
    position: relative;
    width: 100%;
    aspect-ratio: 100 / 68.58;
    /* Ограничиваем по ШИРИНЕ: на широком экране карта иначе уходит
       за 900px высоты и раздел снова читается как плита пустоты. */
    max-width: 820px;
    margin: 0 auto;
    /* Море: тон панели плюс мягкое свечение сверху, чтобы полоса
       не читалась как пустая плита. */
    background:
        radial-gradient(120% 90% at 50% 15%, var(--accent-soft) 0%, transparent 62%),
        var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-3);
    overflow: hidden;
}

/* Подложка растягивается целиком: по ней позиционируются пины,
   поэтому она обязана совпадать с рамкой контейнера пиксель в пиксель */
.map-svg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    display: block;
}

/* Контур. Раньше заливка контура и фон панели были одним и тем же
   --surface-2: страна растворялась в подложке, и раздел читался как
   пустая серая плита с точками. Контур - отдельная поверхность поверх
   фона панели, с видимой линией границы. */
.map-svg path {
    /* Суша заметно плотнее моря: --surface-2 в светлой теме почти совпадал
       с фоном панели и контур растворялся. color-mix даeт разницу в обеих
       темах, не заводя новую переменную. */
    fill: color-mix(in oklch, var(--surface-2) 72%, var(--border) 28%);
    stroke: var(--border-strong);
    stroke-width: 1.25;
    vector-effect: non-scaling-stroke;
}

.map-pin {
    position: absolute;
    z-index: 2;
    display: grid;
    place-items: center;
    width: 22px;
    height: 22px;
    padding: 0;
    background: none;
    border: none;
    transform: translate(-50%, -50%);
    transition: transform var(--transition), z-index 0s;
}

.map-pin:hover,
.map-pin:focus-visible,
.map-pin.is-active { z-index: 6; }

.map-pin:hover,
.map-pin:focus-visible {
    z-index: 5;
    transform: translate(-50%, -50%) scale(1.12);
}

.map-pin:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 4px;
    border-radius: var(--r-2);
}

/* Точка. Белое кольцо вокруг обязательно: без него пин на пестрой подложке
   теряется, а с кольцом читается на любом фоне. */
.map-pin-dot {
    position: relative;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: var(--muted);
    border: 2px solid var(--surface);
    box-shadow: var(--shadow);
    transition: background var(--transition), box-shadow var(--transition);
}

/* Подпись города под точкой. Плашка, а не голый текст: над картой
   текст без фона нечитаем.

   Главная правка: подписи больше не висят все разом. В сети девять точек,
   но городов пять - Киев, Львов, Днепр идут парами (розница плюс склад
   рядом), и подписи ложились друг на друга, читаясь как дубли "Киев Киев".
   Теперь подпись видна у активной точки и у той, на которую навели или
   попали табом. Остальные точки - только кружок, название в title и в
   таблице ниже. Наложений нет по построению, а не по подбору координат. */
.map-pin-label {
    position: absolute;
    top: calc(100% + 4px);
    left: 50%;
    transform: translateX(-50%);
    padding: 2px 8px;
    border-radius: var(--r-pill);
    background: var(--surface);
    border: 1px solid var(--border);
    box-shadow: var(--shadow-2);
    color: var(--dim);
    font-size: var(--fs-2);
    font-weight: 600;
    white-space: nowrap;
    pointer-events: none;
    opacity: 0;
    transition: opacity var(--transition), background var(--transition), color var(--transition), border-color var(--transition);
}

.map-pin.is-active .map-pin-label,
.map-pin:hover .map-pin-label,
.map-pin:focus-visible .map-pin-label {
    opacity: 1;
}

/* Подписи постоянно видны от 1200px. Демо показывают через демонстрацию
   экрана и по статичным скриншотам: девять безымянных кружков не дают
   сравнивать точки, а весь смысл раздела именно в сравнении. Ниже 1200px
   подписи остаются по hover - там ширины на них нет. */
@media (min-width: 1200px) {
    .map-pin-label {
        opacity: 1;
        /* Приглушены, чтобы карта не превращалась в стену текста.
           Наведение и активная точка выводят подпись на первый план. */
        background: color-mix(in oklch, var(--surface) 82%, transparent);
        color: var(--muted);
        box-shadow: none;
    }

    .map-pin:hover .map-pin-label,
    .map-pin:focus-visible .map-pin-label,
    .map-pin.is-active .map-pin-label {
        background: var(--surface);
        color: var(--text);
        box-shadow: var(--shadow-2);
    }

    .map-pin.is-active .map-pin-label {
        background: var(--accent);
        border-color: var(--accent);
        color: var(--on-accent);
    }

    /* Развод подписей по диагонали. В парах Киев/Позняки, Львов/склад,
       Днепр/склад пины стоят в 10-21px друг от друга: подпись под каждым
       налезала бы на соседнюю. Верхний объект пары подписан сверху,
       нижний снизу, плюс разные стороны по горизонтали. */
    .map-pin.lbl-tl .map-pin-label,
    .map-pin.lbl-tr .map-pin-label {
        top: auto;
        bottom: calc(100% + 4px);
    }

    .map-pin.lbl-tl .map-pin-label,
    .map-pin.lbl-bl .map-pin-label {
        left: auto;
        right: calc(50% + 6px);
        transform: none;
    }

    .map-pin.lbl-tr .map-pin-label,
    .map-pin.lbl-br .map-pin-label {
        left: calc(50% + 6px);
        right: auto;
        transform: none;
    }
}

/* Статус локации. Цветом красим только точку и ореол вокруг нее:
   если красить и подпись, карта превращается в светофор и теряет читаемость. */
.map-pin.is-ok .map-pin-dot { background: var(--ok); box-shadow: 0 0 0 4px var(--ok-soft); }
.map-pin.is-warn .map-pin-dot { background: var(--warn); box-shadow: 0 0 0 4px var(--warn-soft); }

/* Ореол у alert постоянный, ровно такой же, как у ok и warn: отличается
   только цвет. Раньше он был кадром анимации mapPulse (0%/100% - кольцо,
   50% - transparent), поэтому большую часть цикла Харьков стоял голой
   точкой и на статичном скриншоте читался как недоделка. Пульсацию унесли
   на отдельный слой ::after, база теперь не мигает. */
.map-pin.is-alert .map-pin-dot {
    background: var(--err);
    box-shadow: 0 0 0 4px var(--err-soft);
}

/* Пульсирующее кольцо поверх постоянного ореола. Отдельный слой, а не
   box-shadow самой точки: так анимация не может погасить базовое кольцо.
   Пульсация только на alert: если пульсируют все пины, не пульсирует ни один. */
.map-pin.is-alert .map-pin-dot::after {
    content: '';
    position: absolute;
    inset: -2px;
    border-radius: 50%;
    pointer-events: none;
    animation: mapPulse 2.2s var(--ease-out) infinite;
}

@keyframes mapPulse {
    0%, 100% { box-shadow: 0 0 0 0 var(--err-soft); }
    50% { box-shadow: 0 0 0 10px transparent; }
}

/* Активная локация - та, на которую пересчитан весь экран. Ее подпись
   заливается акцентом, чтобы было видно с проектора, какой срез открыт. */
.map-pin.is-active {
    z-index: 4;
    transform: translate(-50%, -50%) scale(1.08);
}

.map-pin.is-active .map-pin-label {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--on-accent);
}

.map-pin.is-active .map-pin-dot {
    border-color: var(--accent);
}

.map-pin.is-active:hover {
    transform: translate(-50%, -50%) scale(1.16);
}

/* Легенда идет под картой и по ее ширине, а не растягивается на всю панель */
.map-wrap + .legend {
    max-width: 900px;
    margin: var(--sp-3) auto 0;
    justify-content: center;
}

/* На телефоне воронка перестраивается в две строки: подпись с цифрами
   сверху, полоса под ними во всю ширину. Три колонки в 390px не живут. */
@media (max-width: 560px) {
    .funnel-step {
        grid-template-columns: 1fr auto;
        gap: var(--sp-1) var(--sp-2);
        padding: var(--sp-2) 0;
    }

    .funnel-track { grid-column: 1 / -1; grid-row: 2; height: 22px; }
    .funnel-label { grid-row: 1; }
    .funnel-conv { grid-row: 1; text-align: right; }

    .map-wrap { aspect-ratio: 4 / 3; max-height: 340px; }
    .map-pin-label { font-size: var(--fs-1); }
}

/* ============ СНОСКА ============ */

.footnote {
    margin-top: var(--sp-5);
    color: var(--muted);
    font-size: var(--fs-2);
    text-align: center;
}

/* Пульсации пинов и точек индикаторов - раздражители для тех, кто просил
   убрать движение. Глобальное правило в main.css гасит длительность,
   здесь снимаем анимацию целиком, чтобы не осталось дрожащего кадра. */
@media (prefers-reduced-motion: reduce) {
    .ind.is-alert .ind-dot,
    .map-pin.is-alert .map-pin-dot::after,
    .typing i {
        animation: none !important;
    }

    .map-pin:hover,
    .map-pin:focus-visible,
    .map-pin.is-active,
    .map-pin.is-active:hover {
        transform: translate(-50%, -50%);
    }
}

/* Статус в таблице настройки индикаторов: цветной текст вместо плашки.
   В развернутой таблице 15 строк, из них 11 со словом "критично" - ряд
   красных плашек читался как сплошное полотно и глушил сам показатель.
   У Strimix внутри таблиц статусы и дельты набраны цветным текстом,
   плашка остается только снаружи таблиц. Бейджи в других местах демо
   не трогаем: правило действует лишь внутри колонки .src-level. */
.src-level .badge {
    background: none;
    padding: 0;
    border: 0;
    font-weight: 600;
}

.src-level .badge.alert { color: var(--err); }
.src-level .badge.warn  { color: var(--warn); }
.src-level .badge.ok    { color: var(--ok); }
.src-level .badge.neutral { color: var(--muted); }
