/* ===========================================================================
   Tikpilot — оформление в духе панели Keenetic.

   Крупный заголовок страницы, много воздуха вокруг блоков, плоские
   поверхности на тонкой рамке без теней, контурные кнопки, вкладки
   с подчёркиванием, шапка таблицы отдельной заливкой. Цвета остались
   свои: менялись размеры, отступы, рамки и форма элементов, а палитра
   ниже та же, что была. Таблицы намеренно оставлены плотными: у Keenetic
   строка 57px, здесь 30px — панель рассчитана на парк в полсотни точек,
   и высота строки тут стоит дороже воздуха.

   Всё на собственном CSS: ни сборщиков, ни CDN — интерфейс работает
   в изолированной сети.

   Тёмная тема включается атрибутом data-theme="dark" на <html>.
   =========================================================================== */

:root {
    /* Рабочая область */
    --bg:            #f4f6f6;
    --panel:         #ffffff;
    --panel-2:       #f7f9f9;
    --panel-3:       #eef2f1;
    --border:        #dfe4e3;
    --border-strong: #c4cdcb;
    --text:          #1d2b28;
    --text-soft:     #44544f;
    --muted:         #64756f;

    /* Боковое меню светлое, как в новом WebFig: белый фон, тёмный текст */
    --nav-bg:        #ffffff;
    --nav-bg-2:      #fbfcfc;
    --nav-text:      #24322e;
    --nav-text-dim:  #8a9994;
    --nav-active:    #0f6156;
    --nav-hover:     #eef4f2;
    --nav-border:    #e2e8e6;
    --nav-text-on:   #10352f;   /* текст активного пункта */

    --accent:        #0f6156;
    --accent-hover:  #0b4a41;
    --accent-soft:   #e7f1ef;

    --ok:            #1f7a4d;
    --ok-soft:       #e6f3ec;
    --warn:          #a35c00;
    --warn-soft:     #fdf0df;
    --err:           #b3241f;
    --err-soft:      #fbeae9;

    /* Лента журнала. Отдельные переменные, а не жёсткие цвета: журнал
       должен менять вид вместе со всей панелью, а не оставаться тёмным
       окном посреди светлой страницы. */
    --log-bg:        #f7f9f9;
    --log-text:      #1d2b28;
    --log-time:      #7b8b86;
    --log-src:       #0f6156;
    --log-topic:     #64756f;

    /* Цвета правил подсветки. Отдельно от цветов состояния: те означают
       «хорошо, тревожно, плохо», а эти просто различают сюжеты в ленте,
       и подбирались по читаемости на фоне журнала. */
    --hl-orange:     #b25a12;
    --hl-teal:       #0b6f80;
    --hl-blue:       #1257a8;
    --hl-purple:     #6c3fb5;
    --hl-pink:       #b3246e;

    --radius:        4px;
    --radius-lg:     8px;

    /* Каркас: высота верхней полосы и ширина колонки разделов. Ширина
       живёт переменной, потому что от неё зависят и сама колонка,
       и её содержимое, и переход между двумя состояниями. */
    --topbar-h:      56px;
    --rail-w:        228px;
    --shadow:        0 2px 8px rgba(20, 40, 36, .08);
    --shadow-lg:     0 8px 28px rgba(20, 40, 36, .16);
    --row-h:         30px;
}

html[data-theme="dark"] {
    --bg:            #121a19;
    --panel:         #1a2523;
    --panel-2:       #202d2b;
    --panel-3:       #283734;
    --border:        #32433f;
    --border-strong: #425551;
    --text:          #dfe8e5;
    --text-soft:     #b6c4c0;
    --muted:         #869491;

    --nav-bg:        #16211f;
    --nav-bg-2:      #121b1a;
    --nav-text:      #c6d4d0;
    --nav-text-dim:  #7d8d89;
    --nav-active:    #2f9e8b;
    --nav-hover:     #1f2c2a;
    --nav-border:    #0e1615;
    --nav-text-on:   #ffffff;

    --accent:        #2f9e8b;
    --accent-hover:  #3cb69f;
    --accent-soft:   #16302b;

    --ok:            #4caf7d;
    --ok-soft:       #16291f;
    --warn:          #d29a3c;
    --warn-soft:     #2b2417;
    --err:           #e07a72;
    --err-soft:      #2c1a19;

    /* В тёмной теме журнал чуть глубже панели: так лента читается
       как отдельная область, а не сливается с фоном страницы */
    --log-bg:        #101a19;
    --log-text:      #cfe0dc;
    --log-time:      #5c7b75;
    --log-src:       #7fb3a8;
    --log-topic:     #7f8f8b;

    --hl-orange:     #e2954f;
    --hl-teal:       #4fc3d6;
    --hl-blue:       #7fb0ee;
    --hl-purple:     #b699f2;
    --hl-pink:       #f091bf;

    --shadow:        0 2px 10px rgba(0, 0, 0, .40);
    --shadow-lg:     0 10px 30px rgba(0, 0, 0, .55);
}

* { box-sizing: border-box; }

body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font: 14px/1.55 "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ================================================================== layout */

/* Набор значков лежит в начале страницы и сам ничего не рисует */
.sprite { display: none; }

/* --------------------------------------------------------- верхняя полоса */
/* Липкая: остаётся на месте при прокрутке, поэтому шапки таблиц ниже
   отсчитывают свою липкость от её высоты, а не от края экрана. */
.topbar {
    position: sticky; top: 0; z-index: 40;
    display: flex; align-items: center; gap: 14px;
    height: var(--topbar-h);
    padding-right: 14px;
    background: var(--nav-bg);
    border-bottom: 1px solid var(--nav-border);
}
.brand {
    display: flex; align-items: center; gap: 10px;
    padding: 0 20px;
    font-size: 16px; font-weight: 600; letter-spacing: .2px;
    color: var(--accent); text-decoration: none; white-space: nowrap;
}
.brand:hover { text-decoration: none; }
.brand .mark { display: block; border-radius: 4px; }
.login-card .head .mark { display: block; margin: 0 auto 8px; border-radius: 8px; }

/* Счётчик упавших точек рядом с названием: единственное число, которое
   должно быть видно с любой страницы. */
.topbar-count {
    background: var(--err); color: #fff;
    border-radius: 9px; padding: 0 8px;
    font-size: 11px; font-weight: 700; line-height: 19px;
    text-decoration: none;
}
.topbar-count:hover { text-decoration: none; opacity: .9; }
.topbar .demo-note { padding: 0; }

.topbar-actions { margin-left: auto; display: flex; align-items: center; gap: 4px; }

/* Кнопка-значок: квадрат под значок без рамки и заливки, подсветка
   только под курсором. Такими в панели роутера сделан весь правый край
   верхней полосы. */
.icon-btn {
    display: grid; place-items: center;
    width: 36px; height: 36px;
    border: none; border-radius: var(--radius-lg);
    background: none; cursor: pointer;
    color: var(--nav-text-dim); text-decoration: none;
}
.icon-btn svg {
    width: 20px; height: 20px;
    fill: none; stroke: currentColor; stroke-width: 1.7;
    stroke-linecap: round; stroke-linejoin: round;
}
.icon-btn:hover { background: var(--nav-hover); color: var(--nav-text-on); text-decoration: none; }

/* ------------------------------------------------------------- разделы */
.layout { display: flex; align-items: flex-start; min-height: calc(100vh - var(--topbar-h)); }

.sidebar {
    width: var(--rail-w);
    flex: 0 0 var(--rail-w);
    background: var(--nav-bg);
    border-right: 1px solid var(--nav-border);
    padding: 8px 0 0;
    display: flex;
    flex-direction: column;
    position: sticky;
    top: var(--topbar-h);
    height: calc(100vh - var(--topbar-h));
    overflow-y: auto;
    overflow-x: hidden;
    transition: width .16s ease, flex-basis .16s ease;
}
/* Колонка это flex-столбец с прокруткой. Без запрета на сжатие браузер,
   когда разделы не помещаются по высоте, ужимает не список целиком,
   а каждый пункт понемногу — и заголовкам групп срезает буквы. */
.sidebar > * { flex: 0 0 auto; }

.nav-group {
    color: var(--nav-text-dim);
    font-size: 11px; font-weight: 700; letter-spacing: .9px; text-transform: uppercase;
    padding: 14px 22px 6px;
    white-space: nowrap; overflow: hidden;
}

.nav-link {
    display: flex; align-items: center; gap: 12px;
    height: 42px; margin: 2px 10px; padding: 0 12px;
    border: none; background: none; font: inherit; font-size: 14px;
    border-radius: var(--radius-lg);
    color: var(--nav-text);
    text-decoration: none; text-align: left;
    white-space: nowrap; overflow: hidden; cursor: pointer;
}
.nav-link svg {
    width: 22px; height: 22px; flex: 0 0 22px;
    fill: none; stroke: currentColor; stroke-width: 1.7;
    stroke-linecap: round; stroke-linejoin: round;
    color: var(--nav-text-dim);
}
.nav-link .label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.nav-link:hover { background: var(--nav-hover); color: var(--nav-text-on); text-decoration: none; }
.nav-link:hover svg { color: var(--nav-text); }
/* Выбранный раздел помечен заливкой блока целиком и цветным значком,
   а не полоской у края: узкой колонке полоску негде показать. */
.nav-link.active {
    background: var(--accent-soft);
    color: var(--nav-text-on);
    font-weight: 600;
}
.nav-link.active svg { color: var(--accent); }
.nav-link .count {
    flex: 0 0 auto;
    background: var(--accent); color: #fff;
    border-radius: 9px; padding: 0 7px;
    font-size: 11px; font-weight: 600; line-height: 18px;
}
.nav-link .count.quiet { background: var(--panel-3); color: var(--text-soft); }
.nav-spacer { flex: 1 1 auto; min-height: 12px; }

/* Переключатель языка живёт в верхней полосе, но на телефоне она узкая
   и языку там места нет — тогда он показывается в листе разделов. */
.sidebar .only-mobile { display: none; }

/* Кнопка ширины внизу колонки. Когда колонка раскрыта, рядом с кнопкой
   стоит версия панели: место всё равно пустует, а строку с версией
   иначе пришлось бы держать отдельно. */
.nav-toggle {
    display: flex; align-items: center; gap: 12px;
    height: 46px; margin-top: 6px; padding: 0 22px;
    border: none; border-top: 1px solid var(--nav-border);
    background: none; cursor: pointer;
    font: inherit; font-size: 11px;
    color: var(--nav-text-dim);
    white-space: nowrap; overflow: hidden;
}
.nav-toggle svg {
    width: 22px; height: 22px; flex: 0 0 22px;
    fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round;
}
.nav-toggle:hover { background: var(--nav-hover); color: var(--nav-text-on); }


/* ------------------------------------------- узкая колонка, только значки */
/* Состояние по умолчанию на большом экране: раздел читается по значку,
   название всплывает подсказкой браузера. Правила заперты в медиазапрос,
   чтобы на телефоне, где колонка становится листом снизу, они не мешали
   подписям. */
@media (min-width: 721px) {
    html:not([data-nav="open"]) { --rail-w: 68px; }

    html:not([data-nav="open"]) .nav-link {
        position: relative;
        width: 42px; margin: 3px auto; padding: 0;
        justify-content: center;
    }
    html:not([data-nav="open"]) .nav-link .label { display: none; }
    /* Счётчик перестаёт быть строкой в конце пункта и становится меткой
       в углу значка — как значок непрочитанного в мобильном приложении */
    html:not([data-nav="open"]) .nav-link .count {
        position: absolute; top: 2px; right: 2px;
        min-width: 15px; padding: 0 4px;
        font-size: 10px; line-height: 15px; text-align: center;
    }
    html:not([data-nav="open"]) .nav-link .count.quiet { display: none; }

    /* Название группы разделов в узкой колонке не помещается, и вместо
       него остаётся то, ради чего группа и заведена: черта между
       блоками. Перед первой группой чертить нечего. */
    html:not([data-nav="open"]) .nav-group {
        height: 1px; padding: 0; margin: 9px 16px;
        background: var(--nav-border);
        color: transparent; font-size: 0;
    }
    html:not([data-nav="open"]) .nav-group:first-of-type { display: none; }

    html:not([data-nav="open"]) .nav-toggle { justify-content: center; padding: 0; }
    html:not([data-nav="open"]) .nav-toggle .label { display: none; }
}

.content { flex: 1; padding: 24px 24px 64px; min-width: 0; }

/* --------------------------------------------------------------- заголовок */
.page-head {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: 16px; flex-wrap: wrap;
    margin-bottom: 22px;
}
/* Заголовок крупный и без линии под ним: у Keenetic страница начинается
   с имени раздела и пояснения к нему, а не с разлинованной шапки.
   Черту убрал сознательно — она делила страницу там, где деления нет. */
.page-head h1 { margin: 0; font-size: 28px; font-weight: 700; line-height: 1.25; }
.page-head .sub { color: var(--muted); font-size: 13.5px; margin-top: 6px; }

/* Полоска вкладок раздела. Обычные ссылки: у каждой вкладки свой адрес,
   и на неё можно дать ссылку или открыть в новом окне. */
/* Вкладки-подчёркивания вместо вкладок-папок: активная помечена чертой
   снизу, а не рамкой вокруг. Так полоска читается как строка ссылок,
   которой она и является, и не спорит с рамкой блока под ней. */
.tabs {
    display: flex; flex-wrap: wrap; gap: 24px;
    margin: 0 0 22px;
    border-bottom: 1px solid var(--border);
}
.tab {
    padding: 0 0 11px;
    color: var(--muted);
    font-size: 14px;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
}
.tab:hover { color: var(--text); text-decoration: none; }
.tab.active {
    color: var(--accent);
    font-weight: 600;
    border-bottom-color: var(--accent);
}

/* Красным то, что требует внимания прямо сейчас. Отдельным классом,
   а не стилем в разметке: такое место в панели не одно. */
.text-bad { color: var(--err); }

/* ==================================================================== блоки */
.panel {
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    margin-bottom: 22px;
}
/* Заголовок карточки без заливки и без черты: у Keenetic это просто
   негромкая подпись над содержимым. Заливка делала из него ещё одну
   шапку таблицы, а шапка на карточке уже есть — своя, у таблицы. */
.panel > h2 {
    margin: 0;
    padding: 15px 18px 9px;
    font-size: 13px; font-weight: 700;
    text-transform: uppercase; letter-spacing: normal;
    color: var(--muted);
    background: none;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
}
.panel > h2 .h2-extra {
    font-weight: 400; text-transform: none; letter-spacing: 0; font-size: 12px;
    display: flex; align-items: center; gap: 8px;
}
.panel .panel-body { padding: 6px 18px 18px; }

/* ------------------------------------------------------- требует внимания

   Блок сверху дашборда. Цвет несёт смысл только слева, узкой полосой:
   красить строку целиком значило бы получить светофор из десяти строк,
   в котором ничего не выделяется. Число слева крупное, потому что
   читают в первую очередь именно его: «сколько точек лежит». */
.panel.attention { border-color: var(--border-strong); }
.panel.attention.bad > h2 { color: var(--err); }
.panel.attention.warn > h2 { color: var(--warn); }

.attn-row {
    display: flex; align-items: center; gap: 12px;
    padding: 9px 13px;
    border-bottom: 1px solid var(--border);
    border-left: 3px solid transparent;
}
.attn-list > .attn-row:last-child { border-bottom: none; }
.attn-row.bad  { border-left-color: var(--err); }
.attn-row.warn { border-left-color: var(--warn); }
.attn-row.info { border-left-color: var(--muted); }

.attn-count {
    min-width: 34px; text-align: right;
    font-size: 19px; font-weight: 700; font-variant-numeric: tabular-nums;
}
.attn-row.bad  .attn-count { color: var(--err); }
.attn-row.warn .attn-count { color: var(--warn); }

.attn-body { flex: 1; min-width: 0; }
.attn-title { font-weight: 600; font-size: 13px; }
/* Перечисление имён может быть длинным, но строка должна оставаться
   строкой: список из десяти пунктов, где каждый в три ряда, перестаёт
   быть списком. Полное перечисление всегда доступно по ссылке справа. */
.attn-detail { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.attn-act { display: flex; align-items: center; gap: 8px; }

@media (max-width: 720px) {
    /* На телефоне действие уезжает под текст: три колонки в 360 пикселей
       не помещаются, а сжимать имена дальше уже некуда */
    .attn-row { flex-wrap: wrap; gap: 8px; }
    .attn-count { min-width: 26px; font-size: 17px; }
    .attn-body { flex: 1 1 100%; order: 2; }
    .attn-detail { white-space: normal; }
    .attn-act { order: 3; margin-left: auto; }
}

.grid { display: grid; gap: 12px; }
/* Колонка сетки по умолчанию не сжимается уже своего содержимого:
   min-width у элемента сетки это auto. Широкая таблица внутри распирала
   колонку, колонка страницу, и на телефоне всю страницу начинало возить
   вбок целиком, вместе с прокруткой самой таблицы. Ноль возвращает
   колонке право быть уже содержимого, а прокрутку оставляет там, где
   ей место: внутри .table-wrap. */
.grid > * { min-width: 0; }
.grid.cols-5 { grid-template-columns: repeat(5, 1fr); }
.grid.cols-4 { grid-template-columns: repeat(4, 1fr); }
.grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
.grid.cols-2 { grid-template-columns: repeat(2, 1fr); }

/* ================================================================== плитки */
.stat {
    background: var(--panel);
    border: 1px solid var(--border);
    border-left: 3px solid var(--border-strong);
    border-radius: var(--radius);
    padding: 11px 14px;
}
.stat .label {
    color: var(--muted); font-size: 11px;
    text-transform: uppercase; letter-spacing: .5px; font-weight: 600;
}
.stat .value { font-size: 26px; font-weight: 600; margin-top: 4px; line-height: 1.1; }
.stat .note { color: var(--muted); font-size: 11px; margin-top: 3px; }
.stat.ok   { border-left-color: var(--ok); }
.stat.ok   .value { color: var(--ok); }
.stat.err  { border-left-color: var(--err); }
.stat.err  .value { color: var(--err); }
.stat.warn { border-left-color: var(--warn); }
.stat.warn .value { color: var(--warn); }
.stat.info { border-left-color: var(--accent); }

/* ================================================================= таблицы */
.table-wrap { overflow-x: auto; }
.panel > .table-wrap:last-child { border-radius: 0 0 var(--radius-lg) var(--radius-lg); }

table { width: 100%; border-collapse: collapse; background: var(--panel); font-size: 13px; }
th, td { padding: 5px 9px; text-align: left; border-bottom: 1px solid var(--border); }
/* Шапка таблицы: отдельная заливка, обычный регистр, тот же кегль, что
   и строки. Капитель мелким шрифтом читается как служебная надпись,
   а у Keenetic шапка это такая же строка, только жирная. */
th {
    background: var(--panel-3);
    padding: 9px 9px;
    font-size: 13px; font-weight: 700;
    color: var(--text); text-transform: none; letter-spacing: normal;
    /* Липкость отсчитывается от .table-wrap: это он прокручивается,
       а не страница. Поэтому ноль, а не высота верхней полосы — иначе
       шапка отъезжает вниз и накрывает первую строку. */
    white-space: nowrap; position: sticky; top: 0; z-index: 1;
    border-bottom: 1px solid var(--border-strong);
}
/* Заголовки, по которым можно сортировать */
th.sortable-col { cursor: pointer; user-select: none; padding-right: 18px; position: relative; }
th.sortable-col:hover { background: var(--panel-3); color: var(--text); }
th.sortable-col::after {
    content: "⇅";
    position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
    opacity: .25; font-size: 10px;
}
th.sorted-asc::after  { content: "▲"; opacity: 1; color: var(--accent); font-size: 8px; }
th.sorted-desc::after { content: "▼"; opacity: 1; color: var(--accent); font-size: 8px; }

/* Чересстрочной заливки нет: строки разделены линейкой, как у Keenetic.
   Две метки одного и того же — граница и заливка — спорят друг с другом,
   а на широкой таблице глаз ведёт линейка. */
tbody tr:hover { background: var(--accent-soft); }
tbody tr:last-child td { border-bottom: none; }
td.nowrap, th.nowrap { white-space: nowrap; }
/* Числовая колонка: по правому краю и цифрами одинаковой ширины, иначе
   столбец счётчиков выглядит рваным */
td.num, th.num { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
td.mono, .mono { font-family: Consolas, "SF Mono", Menlo, monospace; font-size: 12px; }
.empty { padding: 26px; text-align: center; color: var(--muted); font-size: 13px; }

/* ================================================================== бейджи */
.badge {
    display: inline-flex; align-items: center; gap: 5px;
    padding: 2px 8px; border-radius: var(--radius);
    font-size: 11px; font-weight: 600; white-space: nowrap;
    background: var(--panel-3); color: var(--text-soft);
    border: 1px solid var(--border);
}
.badge .led { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.badge.online, .badge.ok, .badge.done {
    color: var(--ok); background: var(--ok-soft); border-color: rgba(31, 122, 77, .30);
}
.badge.offline, .badge.error {
    color: var(--err); background: var(--err-soft); border-color: rgba(179, 36, 31, .30);
}
.badge.pending, .badge.cancelled {
    color: var(--warn); background: var(--warn-soft); border-color: rgba(163, 92, 0, .30);
}
.badge.running {
    color: var(--accent); background: var(--accent-soft); border-color: rgba(15, 97, 86, .30);
}
.badge.unknown, .badge.skipped { color: var(--muted); }

.tag-slate  { color:#5b6b7c; background:#e9edf2; border-color:#cbd5e0; }
.tag-blue   { color:#1f5c9e; background:#e6f0fa; border-color:#bcd6ef; }
.tag-green  { color:#2e7d32; background:#e7f4e8; border-color:#bfe0c1; }
.tag-amber  { color:#a06600; background:#fdf2dd; border-color:#efd7a6; }
.tag-red    { color:#b3352a; background:#fbeae8; border-color:#f0c6c1; }
.tag-violet { color:#5f3fa5; background:#efeafb; border-color:#d4c6f0; }
.tag-cyan   { color:#0f6b7a; background:#e2f4f7; border-color:#b6dfe6; }
.tag-pink   { color:#a83a6d; background:#fbe9f1; border-color:#f0c3d8; }

html[data-theme="dark"] .tag-slate  { color:#a5b4c3; background:#28323d; border-color:#3b4856; }
html[data-theme="dark"] .tag-blue   { color:#7fb2e8; background:#1d2b3b; border-color:#2f4459; }
html[data-theme="dark"] .tag-green  { color:#7fce84; background:#1a2a1d; border-color:#2c4430; }
html[data-theme="dark"] .tag-amber  { color:#e0b063; background:#2b2415; border-color:#463a20; }
html[data-theme="dark"] .tag-red    { color:#ea8b81; background:#2c1b19; border-color:#4a2b27; }
html[data-theme="dark"] .tag-violet { color:#b39ae8; background:#241f36; border-color:#3a3157; }
html[data-theme="dark"] .tag-cyan   { color:#6cc7d6; background:#152a2f; border-color:#25454d; }
html[data-theme="dark"] .tag-pink   { color:#e493ba; background:#2e1c26; border-color:#4c2d3c; }

/* ================================================================== кнопки */
/* Кнопки контурные: прозрачный фон, рамка в цвет и подпись в цвет.
   Главное действие отличается не заливкой, а цветом рамки и текста —
   так у Keenetic, и так ряд кнопок над таблицей перестаёт быть
   разноцветной лентой. Градиента нет: он выдаёт возраст интерфейса. */
.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    padding: 6px 14px;
    border-radius: var(--radius);
    border: 1px solid var(--border-strong);
    background: transparent;
    color: var(--text);
    font-size: 13px; font-weight: 500; font-family: inherit;
    cursor: pointer; white-space: nowrap;
    transition: background .1s, border-color .1s, color .1s;
}
.btn:hover { background: var(--panel-3); border-color: var(--muted); text-decoration: none; color: var(--text); }
.btn:active { background: var(--panel-3); }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn.primary {
    background: transparent;
    border-color: var(--accent); color: var(--accent); font-weight: 600;
}
.btn.primary:hover { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
.btn.danger { color: var(--err); border-color: rgba(179, 36, 31, .40); background: transparent; }
.btn.danger:hover { background: var(--err); border-color: var(--err); color: #fff; }
.btn.sm { padding: 2px 8px; font-size: 11.5px; }
.btn-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }

/* =================================================================== формы */
/* Подпись поля обычным начертанием и в полный кегль: у Keenetic она
   часть текста страницы, а не мелкая техническая метка. */
label { display: block; font-size: 13px; color: var(--muted); margin-bottom: 5px; font-weight: 400; }
input[type=text], input[type=password], input[type=number], input[type=search],
input[type=file], select, textarea {
    width: 100%; padding: 8px 10px;
    border-radius: var(--radius);
    border: 1px solid var(--border-strong);
    background: var(--panel); color: var(--text);
    font: inherit; font-size: 14px; outline: none;
}
input:focus, select:focus, textarea:focus {
    border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft);
}
textarea { min-height: 110px; resize: vertical; font-family: Consolas, Menlo, monospace; font-size: 12.5px; }
.field { margin-bottom: 14px; }
.field .hint, .hint { color: var(--muted); font-size: 12.5px; margin-top: 4px; font-weight: 400; }
.check { display: flex; align-items: center; gap: 7px; }
.check input { width: 14px; height: 14px; accent-color: var(--accent); }
.check label { margin: 0; color: var(--text); font-weight: 400; font-size: 13.5px; }

.filters {
    display: flex; gap: 14px; flex-wrap: wrap; align-items: flex-end;
    background: var(--panel); border: 1px solid var(--border);
    border-radius: var(--radius-lg); padding: 14px 16px; margin-bottom: 20px;
}
.filters .field { margin: 0; min-width: 140px; }
.filters .grow { flex: 1; min-width: 200px; }

/* ================================================================= модалки */
.modal-backdrop {
    position: fixed; inset: 0; background: rgba(20, 28, 36, .55);
    display: none; align-items: flex-start; justify-content: center;
    padding: 40px 16px; z-index: 100; overflow-y: auto;
}
.modal-backdrop.open { display: flex; }
.modal {
    background: var(--panel); border: 1px solid var(--border-strong);
    border-radius: var(--radius-lg); width: 100%; max-width: 600px;
    box-shadow: var(--shadow-lg);
}
.modal.wide { max-width: 860px; }
.modal header {
    padding: 10px 14px;
    background: var(--accent); color: #fff;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    display: flex; align-items: center; justify-content: space-between;
}
.modal header h3 { margin: 0; font-size: 13px; font-weight: 600; }
.modal .body { padding: 14px; max-height: 66vh; overflow-y: auto; }
.modal footer {
    padding: 10px 14px; border-top: 1px solid var(--border);
    background: var(--panel-2);
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
    display: flex; justify-content: flex-end; gap: 7px;
}
.close-x { background: none; border: none; color: rgba(255,255,255,.75); font-size: 19px; cursor: pointer; line-height: 1; }
.close-x:hover { color: #fff; }

/* ================================================ панель массовых действий */
.bulkbar {
    position: sticky; bottom: 12px; z-index: 20;
    display: none; align-items: center; gap: 8px; flex-wrap: wrap;
    background: var(--panel); border: 1px solid var(--accent);
    border-left: 3px solid var(--accent);
    border-radius: var(--radius-lg); padding: 9px 12px; margin-top: 12px;
    box-shadow: var(--shadow);
}
.bulkbar.show { display: flex; }
.bulkbar .selected { font-weight: 700; font-size: 12.5px; }

/* ================================================================ прогресс */
.progress {
    height: 16px; background: var(--panel-3); border-radius: var(--radius);
    overflow: hidden; border: 1px solid var(--border-strong); position: relative;
}
.progress .bar {
    height: 100%; background: linear-gradient(180deg, var(--accent) 0%, var(--accent-hover) 100%);
    width: 0; transition: width .3s ease;
}

/* ============================================================ карта статусов */
.map-group { margin-bottom: 14px; }
.map-group:last-child { margin-bottom: 0; }
.map-head {
    display: flex; align-items: center; gap: 9px;
    margin-bottom: 7px; flex-wrap: wrap;
}
.map-head .title { font-weight: 700; font-size: 13px; }
.map-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(126px, 1fr));
    gap: 5px;
}
.map-cell {
    display: block;
    border: 1px solid var(--border);
    border-left: 3px solid var(--muted);
    border-radius: var(--radius);
    background: var(--panel-2);
    padding: 5px 8px;
    color: var(--text); text-decoration: none;
    overflow: hidden;
}
.map-cell:hover { text-decoration: none; border-color: var(--accent); background: var(--accent-soft); }
.map-cell .n {
    font-weight: 600; font-size: 12px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.map-cell .a { color: var(--muted); font-size: 11px; font-family: Consolas, Menlo, monospace; }
.map-cell.online  { border-left-color: var(--ok); }
.map-cell.offline { border-left-color: var(--err); background: var(--err-soft); }
.map-cell.offline .n { color: var(--err); }
.map-cell.unknown { border-left-color: var(--muted); opacity: .8; }
.map-cell.shaky   { border-left-color: var(--warn); background: var(--warn-soft); }

/* ------------------------------------------------------- шкала доступности */
.uptime-bar {
    display: inline-block; vertical-align: middle;
    width: 90px; height: 8px; border-radius: 2px;
    background: var(--panel-3); border: 1px solid var(--border);
    overflow: hidden; margin-right: 7px;
}
.uptime-bar i { display: block; height: 100%; background: var(--ok); }
.uptime-bar.warn i { background: var(--warn); }
.uptime-bar.bad  i { background: var(--err); }

/* ================================================================== прочее */
.toast-wrap { position: fixed; right: 16px; bottom: 16px; z-index: 200; display: flex; flex-direction: column; gap: 7px; }
.toast {
    background: var(--panel); border: 1px solid var(--border-strong);
    border-left: 3px solid var(--accent);
    border-radius: var(--radius); padding: 9px 13px;
    box-shadow: var(--shadow-lg); max-width: 360px; font-size: 12.5px;
}
.toast.error { border-left-color: var(--err); }
.toast.ok { border-left-color: var(--ok); }

.muted { color: var(--muted); }
.small { font-size: 12.5px; }
.result-cell {
    max-width: 480px; white-space: pre-wrap; word-break: break-word;
    font-family: Consolas, Menlo, monospace; font-size: 11.5px;
}
.alert {
    padding: 9px 12px; border-radius: var(--radius); margin-bottom: 12px;
    font-size: 12.5px; border: 1px solid var(--border); border-left-width: 3px;
}
.alert.error { background: var(--err-soft); border-color: rgba(179, 36, 31, .35); border-left-color: var(--err); color: var(--err); }
.alert.ok    { background: var(--ok-soft);  border-color: rgba(31, 122, 77, .35);  border-left-color: var(--ok);  color: var(--ok); }
.alert.info  { background: var(--accent-soft); border-color: rgba(15, 97, 86, .30); border-left-color: var(--accent); color: var(--text-soft); }
.alert.warn  { background: var(--warn-soft); border-color: rgba(163, 92, 0, .35);  border-left-color: var(--warn); color: var(--warn); }

/* ============================================================ страница входа */
.login-page {
    min-height: 100vh; display: flex; align-items: center; justify-content: center;
    background: linear-gradient(160deg, var(--accent) 0%, var(--accent-hover) 100%);
    padding: 20px;
}
.login-card {
    width: 100%; max-width: 340px; background: var(--panel);
    border: 1px solid var(--border-strong); border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg); overflow: hidden;
}
.login-card .head {
    background: var(--panel-2); border-bottom: 1px solid var(--border);
    padding: 16px; text-align: center;
}
.login-card h1 { margin: 0 0 2px; font-size: 17px; font-weight: 600; }
.login-card .sub { color: var(--muted); font-size: 12px; }
.login-card .body { padding: 16px; }

/* ================================================================== адаптив */
@media (max-width: 1100px) {
    .grid.cols-5 { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 900px) {
    .grid.cols-4, .grid.cols-5 { grid-template-columns: repeat(2, 1fr); }
    .grid.cols-3, .grid.cols-2 { grid-template-columns: 1fr; }
}
/* ============================================================== телефон */
/* Панель добавляют на домашний экран, и открывается она без адресной
   строки и вкладок. Значит верх и низ экрана панель обязана обставить
   сама, иначе получается страница, потерявшая браузер.

   Устройство раскладки такое же, как в мобильных приложениях: сверху
   тонкая полоска с названием, снизу пять вкладок под большой палец,
   остальные разделы в выдвигающемся снизу листе. Меню из четырнадцати
   ссылок в шапке, которое было раньше, на телефоне занимало пол-экрана
   и всё равно требовало прицеливаться.

   Отступы под вырез и полоску «домой» раздаёт env(safe-area-inset-*):
   в браузере они нулевые, поэтому одна и та же вёрстка годится и там
   и там. */
.tabbar, .sheet-backdrop { display: none; }

@media (max-width: 720px) {
    /* Страховка от бокового съезда: что бы ни оказалось шире экрана,
       возить вбок будут это, а не всю страницу вместе с шапкой.
       Именно clip, а не hidden: hidden делает из страницы отдельную
       область прокрутки и ломает липкие шапки. Где clip не понимают,
       остаётся прежнее поведение, а причина всё равно устранена
       правилом min-width для колонок сетки. */
    html, body { overflow-x: clip; }

    /* Растягивание вернуть обязательно. У .layout стоит
       align-items: flex-start, и на большом экране это про вертикаль:
       колонка меню не тянется на всю высоту страницы. А в вертикальном
       флексе то же правило означает «по ширине содержимого», и колонка
       с широкой таблицей распирала себя шире экрана. Вылезшее срезал
       overflow-x: clip, поэтому половина страницы просто пропадала,
       и даже прокрутить к ней было нельзя. */
    .layout { flex-direction: column; align-items: stretch; }

    /* Верхняя полоса та же, что на большом экране, только ниже и без
       переключателя языка: на телефоне язык живёт в листе разделов.
       Отступ под вырез добавляется к высоте, иначе название уезжает
       под чёлку. */
    :root { --topbar-h: calc(48px + env(safe-area-inset-top)); }
    .topbar {
        height: var(--topbar-h);
        padding-top: env(safe-area-inset-top);
        padding-right: 6px;
        gap: 8px;
        z-index: 30;
    }
    .brand { padding: 0 12px; font-size: 15px; }
    .lang-switch.top { display: none; }

    /* Боковое меню превращается в лист, выезжающий снизу */
    .sidebar {
        position: fixed; left: 0; right: 0; bottom: 0; top: auto;
        width: auto; flex: none; height: auto; max-height: 82vh;
        overflow-y: auto;
        transform: translateY(101%);
        transition: transform .2s ease;
        z-index: 60;
        border-right: none;
        border-top: 1px solid var(--nav-border);
        border-radius: 14px 14px 0 0;
        box-shadow: var(--shadow-lg);
        padding-bottom: calc(10px + env(safe-area-inset-bottom));
    }
    body.menu-open .sidebar { transform: none; }
    body.menu-open { overflow: hidden; }
    .nav-link { min-height: 46px; }   /* палец, а не курсор */
    .nav-spacer { display: none; }
    /* Кнопка ширины листу не нужна: он и так во всю ширину экрана */
    .nav-toggle { display: none; }
    /* Тема, выход и язык: на телефоне их неоткуда взять, кроме листа */
    .sidebar .only-mobile { display: flex; }

    .sheet-backdrop {
        display: block; position: fixed; inset: 0; z-index: 50;
        background: rgba(0, 0, 0, .4);
        opacity: 0; pointer-events: none; transition: opacity .2s ease;
    }
    body.menu-open .sheet-backdrop { opacity: 1; pointer-events: auto; }

    /* Нижние вкладки */
    .tabbar {
        display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 55;
        background: var(--nav-bg);
        border-top: 1px solid var(--nav-border);
        padding-bottom: env(safe-area-inset-bottom);
    }
    .tab-item {
        position: relative;
        flex: 1 1 0; min-width: 0;
        display: flex; flex-direction: column; align-items: center; gap: 3px;
        padding: 7px 2px 7px;
        background: none; border: none; cursor: pointer;
        font: inherit; font-size: 10.5px; line-height: 1.2;
        color: var(--muted); text-decoration: none;
    }
    .tab-item span {
        max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    }
    /* Размер здесь, а рисование на самих значках в наборе: стиль
       к содержимому <use> применяется от места вставки, и правило,
       написанное для бокового меню, до вкладок не доходит. */
    .tab-item svg { width: 22px; height: 22px; }
    .tab-item.active { color: var(--accent); }
    .tab-item:hover { text-decoration: none; }
    /* Счётчик поверх значка, как значок непрочитанного в приложении */
    .tab-item .dot {
        position: absolute; transform: translate(11px, -4px);
        background: var(--err); color: #fff;
        border-radius: 9px; padding: 0 5px;
        font-size: 10px; font-weight: 700; line-height: 15px;
    }

    .content { padding: 12px 10px calc(66px + env(safe-area-inset-bottom)); }
    .map-grid { grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); }

    /* Шапка блока перестаёт разъезжаться. На широком экране это строка
       «слева название, справа пояснение», и раскладка держит их по краям.
       На телефоне пояснение вроде «цвет слева: зелёный в сети, красный
       не отвечает» в остаток строки не помещается, ломается по словам,
       и название с датой начинают плавать посреди обрывков. Теперь
       пояснение уходит целиком на свою строку под названием. */
    .panel > h2 {
        flex-wrap: wrap;
        align-items: baseline;
        gap: 2px 8px;
        padding: 8px 11px;
    }
    .panel > h2 .h2-extra {
        flex: 1 0 100%;
        justify-content: flex-start;
        font-size: 11.5px;
        line-height: 1.4;
    }

    /* Широкая таблица прокручивается вбок, а не ужимается до столбика
       из букв. Порог в четыре колонки: у таблицы из двух-трёх столбцов
       боковая прокрутка была бы наказанием без причины.
       :has() понимают не все браузеры, и это нормально: где не понимают,
       остаётся прежнее поведение. */
    .table-wrap { -webkit-overflow-scrolling: touch; }
    .table-wrap table:has(thead th:nth-child(4)) { min-width: 560px; }
}

/* ------------------------------------------------- переключатель языка */
.lang-switch {
    display: flex;
    gap: 6px;
    padding: 6px 14px 0;
    font-size: 12px;
}

.lang-switch .lang {
    color: var(--nav-text-dim);
    text-decoration: none;
    padding: 2px 6px;
    border-radius: 4px;
}

.lang-switch .lang:hover { background: var(--nav-hover); }

/* Тот же переключатель в верхней полосе: без отступов, в строку со значками */
.lang-switch.top { padding: 0; margin-right: 6px; }

.lang-switch .lang.current {
    color: var(--accent);
    background: var(--accent-soft);
    cursor: default;
}

/* На странице входа боковой панели нет — цвета другие */
.lang-switch.login {
    justify-content: center;
    padding: 14px 0 0;
}

.lang-switch.login .lang { color: var(--muted); }
.lang-switch.login .lang:hover { background: var(--hover, rgba(0, 0, 0, .05)); }

.lang-switch.login .lang.current {
    color: var(--text);
    background: var(--hover, rgba(0, 0, 0, .06));
}

/* ==================================================== права пользователей */
.user-card {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 10px 12px;
    margin-bottom: 8px;
    background: var(--panel);
}

.user-head {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: 12px; flex-wrap: wrap;
}

.user-form {
    margin-top: 12px;
    padding-top: 12px;
    border-top: 1px solid var(--border);
}

.perm-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
    gap: 10px 18px;
}

.perm-section { min-width: 0; }

.perm-title {
    font-size: 11px; font-weight: 700; letter-spacing: .6px;
    text-transform: uppercase; color: var(--muted);
    margin: 0 0 4px;
}

.perm {
    display: flex; align-items: flex-start; gap: 7px;
    padding: 3px 0; font-size: 13px; cursor: pointer;
}

.perm input { margin-top: 2px; flex: 0 0 auto; }
.perm span { display: flex; flex-direction: column; gap: 1px; }

/* Полный доступ перекрывает остальное, поэтому стоит особняком */
.perm.full {
    grid-column: 1 / -1;
    background: var(--accent-soft);
    border: 1px solid var(--border);
    border-left: 3px solid var(--accent);
    padding: 7px 9px;
}

.scope-picker { display: flex; gap: 24px; flex-wrap: wrap; margin-top: 6px; }
.scope-col { min-width: 200px; max-height: 260px; overflow-y: auto; }

/* ============================================= публичный лист состояния */
.public-page { background: var(--bg); }

.public-wrap {
    max-width: 820px;
    margin: 0 auto;
    padding: 22px 16px 40px;
}

.public-head {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: 14px; flex-wrap: wrap; margin-bottom: 14px;
}

.public-head h1 { margin: 0; font-size: 20px; }
.public-head .sub { color: var(--muted); font-size: 12.5px; }
.public-counts { display: flex; gap: 6px; flex-wrap: wrap; }

.public-foot { margin-top: 14px; text-align: center; }

/* ================================================= конфигурация WireGuard */
/* Текст конфигурации и QR стоят рядом: с одного экрана и копируют в буфер,
   и сканируют телефоном. На узком экране QR уезжает вниз, а не сжимает
   текст до нечитаемого. */
.wg-config { display: flex; gap: 14px; align-items: flex-start; }
.wg-config textarea { flex: 1 1 auto; min-width: 0; }

.wg-qr { flex: 0 0 210px; text-align: center; }
.wg-qr svg {
    width: 100%; height: auto;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: var(--radius);
}
.wg-qr .hint { margin-top: 6px; }

@media (max-width: 720px) {
    .wg-config { flex-direction: column; }
    .wg-qr { flex: 0 0 auto; width: 210px; margin: 0 auto; }
}

/* Простой за сутки и переключатель языка на публичной странице */
.public-downtime {
    background: var(--warn-soft);
    border: 1px solid rgba(163, 92, 0, .30);
    border-left: 3px solid var(--warn);
    border-radius: var(--radius);
    padding: 8px 11px;
    margin-bottom: 12px;
    font-size: 13px;
}
.public-downtime.quiet {
    background: var(--ok-soft);
    border-color: rgba(31, 122, 77, .30);
    border-left-color: var(--ok);
    color: var(--ok);
}

.public-lang { margin-top: 6px; text-align: right; }
.public-lang a, .public-lang .current { margin-left: 8px; }
.public-lang .current { color: var(--muted); }

/* ======================================= сравнение конфигураций (дифф) */
/* Моноширинный текст без переносов: строка конфигурации RouterOS должна
   читаться целиком, а перенос посередине правила только мешает. */
.diff {
    margin: 0;
    padding: 10px 12px;
    font: 12px/1.5 ui-monospace, "Cascadia Mono", Consolas, monospace;
    white-space: pre;
    overflow-x: auto;
}
.diff span { display: block; padding: 0 4px; }
.d-add  { background: var(--ok-soft);   color: var(--ok); }
.d-del  { background: var(--err-soft);  color: var(--err); }
.d-hunk { background: var(--panel-3);   color: var(--muted); margin: 6px 0 2px; }
.d-same { color: var(--text-soft); }

/* Сравнение двумя колонками. Слева было, справа стало: так это показывают
   Winbox и любой инструмент, которым человек пользовался раньше, и так
   видно замену строки, а не «минус там, плюс где-то ниже». */
table.dt {
    width: 100%;
    border-collapse: collapse;
    font: 12px/1.5 ui-monospace, "Cascadia Mono", Consolas, monospace;
    table-layout: fixed;
}
table.dt th {
    font-family: inherit;
    position: sticky;
    top: 0;
    z-index: 1;
}
table.dt td { padding: 0 6px; vertical-align: top; border: 0; }
/* Ширина колонки номеров живёт здесь, а не на ячейке: при table-layout:
   fixed браузер смотрит на первую строку, а там ячейки с colspan */
.dt-c-no { width: 54px; }
/* Колонка номеров неактивная: номер нужен глазами, а выделять его вместе
   с текстом при копировании незачем */
.dt-no {
    text-align: right;
    color: var(--muted);
    user-select: none;
    background: var(--panel-2);
    border-right: 1px solid var(--border);
}
/* Граница между панелями: без неё две колонки читаются как одна широкая */
.dt-right { border-left: 2px solid var(--border-strong); }
/* Длинная строка переносится, а не обрезается. Обрезка прячет ровно тот
   хвост, ради которого дифф и открыли: в правиле RouterOS самое важное
   обычно в конце строки. Перенос идёт с отступом, чтобы продолжение
   не путалось с новой строкой. */
.dt-line {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    padding-left: 18px;
    text-indent: -12px;
}
.dt-add { background: var(--ok-soft);  color: var(--ok); }
.dt-del { background: var(--err-soft); color: var(--err); }
/* Строки на этой стороне нет вовсе. Отличается от пустой строки: там
   ничего не написано, а здесь нечего писать */
.dt-none {
    background: repeating-linear-gradient(
        135deg, transparent, transparent 6px,
        var(--panel-2) 6px, var(--panel-2) 12px);
}
.dt-skip td {
    background: var(--panel-3);
    color: var(--muted);
    text-align: center;
    padding: 3px 6px;
    font-size: 11px;
}

/* ============================================================== консоль */
/* Лента журнала: и консоль панели, и логи с устройств. Цвета берутся
   из темы, поэтому переключатель темы меняет её вместе со всей панелью.
   Раньше поле было тёмным всегда и в светлой теме выглядело чужеродно. */
.console {
    background: var(--log-bg);
    color: var(--log-text);
    border: 1px solid var(--border);
    font: 12px/1.6 ui-monospace, "Cascadia Mono", Consolas, monospace;
    padding: 10px 12px;
    height: calc(100vh - 260px);
    min-height: 300px;
    overflow-y: auto;
    border-radius: var(--radius-lg);
}

.c-line { display: flex; gap: 10px; white-space: pre-wrap; word-break: break-word; }
.c-ts   { color: var(--log-time); flex: 0 0 auto; }
.c-src  { color: var(--log-src); flex: 0 0 90px; }
.c-text { flex: 1 1 auto; }

.c-warning .c-text  { color: var(--warn); }
.c-error   .c-text,
.c-critical .c-text { color: var(--err); }
.c-debug   .c-text  { color: var(--muted); }
.c-empty            { color: var(--muted); }

/* Строка «сейчас» над консолью */
.now-strip {
    display: flex; flex-wrap: wrap; gap: 8px;
    padding: 8px 11px; margin-bottom: 10px;
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    font-size: 12px;
}
.now-item {
    padding: 2px 8px;
    border-radius: 10px;
    background: var(--panel-3);
    color: var(--text-soft);
    white-space: nowrap;
}
.now-item.busy { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.now-item.calm { background: var(--ok-soft); color: var(--ok); }
.now-item.warn { background: var(--warn-soft); color: var(--warn); }

/* Беспроводной клиент: отличается от проводного с одного взгляда */
.badge.air { background: var(--accent-soft); color: var(--accent); }

/* --------------------------------------------------------------- листалка */
/* Номера страниц под длинной таблицей. Строка «с 51 по 100 из 372» слева,
   номера справа: взгляд сначала отвечает на вопрос «много ли осталось». */
.pager {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 9px;
    margin: 9px 0 13px;
}
.pager-pages { display: flex; align-items: center; flex-wrap: wrap; gap: 4px; }
.pager-gap { padding: 0 3px; }
.btn.disabled { opacity: .4; pointer-events: none; }

/* ------------------------------------------------------ сворачиваемые блоки */
/* Страница мониторинга длинная: разделы, которые смотрят не каждый раз,
   складываются кликом по заголовку. Треугольник слева показывает, что
   заголовок нажимается: без него об этом никто не догадается. */
.panel > h2.foldable { cursor: pointer; user-select: none; }
.panel > h2.foldable:hover { color: var(--text); }
.panel > h2.foldable::before {
    content: "\25BE";
    display: inline-block;
    width: 13px;
    font-size: 10px;
    color: var(--muted);
    transition: transform .12s ease;
}
.panel.folded > h2.foldable::before { transform: rotate(-90deg); }
.panel.folded > *:not(h2) { display: none; }
.panel.folded { padding-bottom: 0; }

/* ------------------------------------------------------- журнал устройств */
/* Подсветка строк по правилам: цвет несёт смысл, поэтому меняется текст,
   а не фон. Полосатый журнал читать невозможно. */
.sl-err    { color: var(--err); }
.sl-warn   { color: var(--warn); }
.sl-orange { color: var(--hl-orange); }
.sl-ok     { color: var(--ok); }
.sl-teal   { color: var(--hl-teal); }
/* Синий это синий: раньше он брал цвет темы, а тема зелёная, и правило
   «синий» красило строку в тот же цвет, что и «бирюзовый» */
.sl-info   { color: var(--hl-blue); }
.sl-purple { color: var(--hl-purple); }
.sl-pink   { color: var(--hl-pink); }
.sl-muted  { color: var(--muted); }
.sl-err td, .sl-warn td, .sl-orange td, .sl-ok td, .sl-teal td,
.sl-info td, .sl-purple td, .sl-pink td, .sl-muted td { color: inherit; }

/* Уровень важности: слева от строки, чтобы взгляд цеплялся за красное */
.badge.sev-emerg, .badge.sev-alert, .badge.sev-crit, .badge.sev-error {
    background: var(--err-soft); color: var(--err);
}
.badge.sev-warning { background: var(--warn-soft); color: var(--warn); }
.badge.sev-notice, .badge.sev-info { background: var(--panel-2); color: var(--muted); }
.badge.sev-debug { background: transparent; color: var(--muted); }

/* Лента логов с устройств: те же строки, что и в консоли панели, плюс
   колонка темы. Ширины фиксированные, чтобы адреса и MAC выстраивались
   в столбец: в журнале роутера их больше, чем слов. */
.c-topic { color: var(--log-topic); flex: 0 0 175px; overflow: hidden; text-overflow: ellipsis; }
.c-src a { color: inherit; text-decoration: none; }
.c-src a:hover { text-decoration: underline; }
.c-line.sl-err    .c-text { color: var(--err); }
.c-line.sl-warn   .c-text { color: var(--warn); }
.c-line.sl-orange .c-text { color: var(--hl-orange); }
.c-line.sl-ok     .c-text { color: var(--ok); }
.c-line.sl-teal   .c-text { color: var(--hl-teal); }
.c-line.sl-info   .c-text { color: var(--hl-blue); }
.c-line.sl-purple .c-text { color: var(--hl-purple); }
.c-line.sl-pink   .c-text { color: var(--hl-pink); }
.c-line.sl-muted  .c-text { color: var(--muted); }

/* Колонки ленты логов держат ширину: имя точки в одну строку с многоточием,
   иначе длинное название переносится и ломает выравнивание всей ленты. */
.c-src, .c-topic {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.c-src { flex: 0 0 190px; }

/* На узком экране колонки не помещаются. Время, имя точки и тема съедали
   всю ширину, сообщению оставалось около нуля, и текст переносился по
   одной букве в строку: читать это невозможно.

   Строка становится двухэтажной: сверху время, точка и тема, под ними
   сообщение во всю ширину. Ширины колонок при этом отпускаем, иначе
   190 пикселей под имя точки не оставляют места ни на что другое. */
@media (max-width: 720px) {
    .c-line { flex-wrap: wrap; gap: 0 8px; margin-bottom: 6px; }
    .c-src, .c-topic { flex: 0 1 auto; max-width: 48%; }
    .c-text { flex: 1 0 100%; }

    /* Высота от экрана, а не от окна во весь рост: на телефоне
       calc(100vh - 260px) оставлял полосу в несколько строк */
    .console { height: auto; max-height: 62vh; min-height: 220px; }
    .terminal-box { height: 58vh; min-height: 240px; }
}

/* ------------------------------------------------------------- терминал */
/* Окно терминала. Высота как у ленты логов: строка команд и вывод должны
   помещаться на экран целиком, иначе приходится прокручивать страницу
   вместо самого терминала. */
.terminal-box {
    height: calc(100vh - 320px);
    min-height: 320px;
    padding: 8px;
    background: var(--log-bg);
    border-top: 1px solid var(--border);
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}
.terminal-box .xterm { height: 100%; }

/* Состояние сессии под окном: размер, сокет, объём буфера. Пустое окно
   не отличить от сломанного, а причин у пустоты три, и строка называет
   виноватую сразу. */
.term-state {
    padding: 5px 9px;
    border-top: 1px solid var(--border);
    background: var(--panel-2);
    font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
    font-size: 11px;
}

/* Строка ввода под окном терминала. На большом экране печатают прямо
   в окно, поэтому здесь она незаметна и не мешает; на телефоне это
   единственный способ добраться до таба и до прерывания. */
.term-input {
    display: flex; gap: 6px; align-items: center;
    padding: 8px;
    border-top: 1px solid var(--border);
    background: var(--panel-2);
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}
.term-input input {
    flex: 1 1 auto; min-width: 0;
    font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
}

/* --------------------------------------------------- паспорт устройства */
/* Порты плитками, как на морде коробки. Цвет это скорость: взгляд ищет
   не цифру, а «почему этот порт другого цвета, чем соседний». */
.ports { display: flex; flex-wrap: wrap; gap: 8px; }
.port { width: 62px; text-align: center; }
.port-box {
    height: 38px;
    border-radius: var(--radius);
    border: 1px solid var(--border);
    background: var(--panel-3);
    display: flex; align-items: center; justify-content: center;
}
.port-name { font-size: 11px; color: var(--muted); margin-top: 4px;
             overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.port-poe { font-size: 10px; font-weight: 700; color: #fff;
            text-shadow: 0 1px 1px rgba(0,0,0,.35); letter-spacing: .04em; }
/* Выключенный руками порт отличается от просто погасшего: первое это
   решение человека, второе повод искать причину */
.port-off { opacity: .45; border-style: dashed; }
/* Замыкание, перегрузка, чужое напряжение на порту. Раньше такой порт
   выглядел как порт, в который просто ничего не воткнуто, и поломанная
   камера могла висеть незамеченной месяцами */
.port-poe-bad { background: var(--err); border-color: var(--err); }

.sp-down { background: var(--panel-3); }
/* Линк есть, а скорость версия RouterOS не сообщила. Серым такой порт
   красить нельзя: это прямая ложь про рабочий порт */
.sp-up   { background: var(--accent); }
.sp-10m  { background: #9c6f2a; }
.sp-100m { background: #d08700; }
.sp-1g   { background: #2e9e5b; }
.sp-25g  { background: #16b1a8; }
.sp-10g  { background: #2f7fd0; }

.port-legend { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 9px; }
.port-legend i { display: inline-block; width: 11px; height: 11px; border-radius: 3px;
                 border: 1px solid var(--border); margin-right: 5px;
                 vertical-align: -1px; }

/* Ряд плашек: сервисы, мосты, соседи. Одна разметка на три списка,
   потому что читаются они одинаково: короткое имя плюс уточнение. */
.chip-row { display: flex; flex-wrap: wrap; gap: 7px; }
.chip {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 4px 9px; border-radius: var(--radius);
    border: 1px solid var(--border); background: var(--panel-3);
    font-size: 12px;
}
.chip.on   { border-color: color-mix(in srgb, var(--ok) 45%, var(--border)); }
.chip.off  { opacity: .5; }
.chip.risk { border-color: var(--warn); color: var(--warn); font-weight: 600; }

/* ------------------------------------------- заходы по публичным ссылкам */
/* Точка «прямо сейчас». Мигание намеренно медленное: строка живёт минутами,
   и быстрая пульсация в таблице только раздражает. */
.dot-live {
    display: inline-block;
    width: 7px;
    height: 7px;
    margin-right: 6px;
    border-radius: 50%;
    background: var(--ok);
    animation: live-pulse 2.4s ease-in-out infinite;
}

@keyframes live-pulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: .35; }
}

/* Кому анимация мешает, тот её отключил в системе, и это надо уважать */
@media (prefers-reduced-motion: reduce) {
    .dot-live { animation: none; }
}

/* Список точек в окне «где стоит»: полсотни имён должны листаться,
   а не растягивать окно за край экрана. */
.dv-list {
    max-height: 46vh;
    overflow: auto;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 8px 10px;
}

.dv-list > div {
    padding: 3px 0;
    border-bottom: 1px solid var(--border);
}

.dv-list > div:last-child { border-bottom: 0; }

/* Пара панелей рядом: параметры подключения и клиенты точки. Верх
   выравниваем, иначе короткая панель растягивается на высоту длинной
   и внутри неё повисает пустота. */
/* Пара панелей рядом: слева параметры подключения, справа клиенты.

   Каждая высотой по своему содержимому, как обычная панель: тянуть
   короткую до высоты длинной значит оставить внутри пустоту, а пустота
   в панели выглядит хуже, чем разная высота.

   Единственное, что задано жёстко, это потолок. Клиентов на точке
   бывает полторы сотни, и без него список уехал бы на два экрана вниз;
   упёршись в потолок, он начинает прокручиваться внутри себя.
   Потолок примерно равен высоте таблицы параметров, поэтому на обычной
   точке панели заканчиваются на одном уровне. */
.grid.pair { align-items: start; }
.grid.pair > .panel {
    display: flex;
    flex-direction: column;
    max-height: 520px;
}
/* Заголовок своей высоты, тянется и прокручивается только таблица */
.grid.pair > .panel > h2,
.grid.pair > .panel > .panel-body { flex: 0 0 auto; }
/* min-height: 0 обязателен: без него flex-элемент не сжимается ниже
   своего содержимого, и полоса прокрутки не появляется */
.grid.pair > .panel > .table-wrap {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}

/* Когда панели встают друг под друга, потолок оставляем: сотня строк
   иначе растянет страницу. Флексы при этом не нужны. */
@media (max-width: 900px) {
    .grid.pair > .panel { display: block; max-height: none; }
    .grid.pair > .panel > .table-wrap { max-height: 420px; }
}

/* Плашка режима витрины в боковом меню */
.demo-note { display:flex; align-items:center; gap:8px; padding:8px 14px; }
.demo-note .linklike {
    background:none; border:none; padding:0; font:inherit; cursor:pointer;
    color:var(--muted); text-decoration:underline;
}
.demo-note .linklike:hover { color:var(--fg); }

/* Разделы формы «Параметры работы» */
/* Разделы параметров в колонки: на широком экране один столбец полей
   оставлял пустой всю правую половину страницы, а прокрутки было
   на пять экранов. Колонка не уже 320 пикселей, иначе подписи ломаются. */
.pref-columns {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    gap: 0 26px;
    align-items: start;
}
.pref-group { margin-bottom: 18px; break-inside: avoid; }
.pref-group .field { max-width: 520px; }

/* Мишени подсказок на графиках: прозрачные столбцы во всю высоту поля.
   Подсказку рисует сам браузер по <title>, здесь только подсветка,
   чтобы было видно, какой именно отрезок времени под курсором. */
.chart-hit { fill: transparent; cursor: crosshair; }
.chart-hit:hover { fill: var(--text); fill-opacity: 0.07; }
