*{box-sizing:border-box}
:root{--bg1:#0d1b2a;--panel:rgba(33,41,58,.72);--line:rgba(255,255,255,.09);--txt:#dfe6f1;--muted:#8fa1b8;--acc:#4cc9f0;--ok:#43d17c;--bad:#ef5b6e}
html,body{height:100%;overflow:hidden}
body{margin:0;font-family:"Segoe UI",system-ui,sans-serif;color:var(--txt);
background-color:#0d1b2a}
a{color:var(--acc);text-decoration:none}a:hover{text-decoration:underline}
.container-fluid{max-width:1400px;margin:auto;padding:1rem 1.5rem}
.sidebar{position:fixed;left:0;top:0;bottom:0;width:240px;background:rgba(38,44,56,var(--sidebar-alpha,.78));backdrop-filter:blur(10px);border-right:1px solid var(--line);padding:1.2rem .8rem;z-index:5;display:flex;flex-direction:column;transition:transform .25s ease}
html.sidebar-hidden .sidebar{transform:translateX(-100%)}
.brand-inner{display:inline-flex;align-items:center;gap:.5rem;min-width:0}
.sidebar-toggle-btn{margin-left:auto;border:none;background:transparent;color:var(--muted);font-size:1.05rem;line-height:1;padding:.3rem .45rem;border-radius:6px;cursor:pointer;flex:0 0 auto}
.sidebar-toggle-btn:hover{color:#fff;background:rgba(255,255,255,.08)}
.sidebar-show-btn{position:fixed;top:14px;left:12px;z-index:30;display:none;align-items:center;justify-content:center;width:38px;height:38px;border-radius:8px;border:1px solid var(--line);background:rgba(38,44,56,.9);color:#fff;font-size:1.15rem;line-height:1;cursor:pointer}
.sidebar-show-btn:hover{background:rgba(38,44,56,1);border-color:var(--acc)}
html.sidebar-hidden .sidebar-show-btn{display:inline-flex}
.nav-scroll{flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;margin:0 -.25rem;padding:0 .25rem;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.22) transparent}
.nav-scroll::-webkit-scrollbar{width:8px}
.nav-scroll::-webkit-scrollbar-track{background:transparent}
.nav-scroll::-webkit-scrollbar-thumb{background:rgba(255,255,255,.18);border-radius:4px}
.nav-scroll::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,.3)}
.brand{font-size:1.15rem;font-weight:700;color:#fff;padding:.4rem .6rem 1rem;display:flex;gap:.5rem;align-items:center}
.nav-link{display:flex;gap:.6rem;align-items:center;color:var(--muted);padding:.55rem .8rem;border-radius:.4rem;margin-bottom:.2rem}
.nav-link:hover{color:#fff;background:rgba(255,255,255,.07);text-decoration:none}
.nav-link.active{color:#fff;background:rgba(76,201,240,.18)}
.nav-btn{border:0;background:none;cursor:pointer}
/* Раскрывающиеся группы меню */
.nav-row{display:flex;align-items:stretch;margin-bottom:.2rem}
.nav-parent{flex:1 1 auto;min-width:0;margin-bottom:0;justify-content:flex-start}
.nav-caret-btn{flex:0 0 auto;width:2rem;border:0;background:none;color:var(--muted);cursor:pointer;display:flex;align-items:center;justify-content:center;margin-left:.35rem}
.nav-caret-btn:hover{color:#fff;background:rgba(255,255,255,.07)}
.nav-caret{margin-left:0;transition:transform .2s;font-size:.75em}
.nav-group.open .nav-caret{transform:rotate(90deg)}
.nav-submenu{display:none;padding-left:.8rem;margin-bottom:.4rem}
.nav-group.open .nav-submenu{display:block}
.nav-subitem{font-size:.92em}
/* Рабочий стол: меню слева, рабочее пространство — центральная часть с заставкой */
.modal-backdrop{left:240px !important;right:0 !important;opacity:0 !important}
.modal{left:240px !important;right:0 !important}
/* меню скрыто — модальные окна занимают весь экран */
html.sidebar-hidden .modal-backdrop,html.sidebar-hidden .modal{left:0 !important}
/* мобильный телефон: модальные окна — на всю ширину, снизу вверх (bottom-sheet) */
@media (max-width:768px){
.modal-backdrop,.modal{left:0 !important;right:0 !important}
.modal.show{display:flex;align-items:flex-end;padding:0}
.modal-dialog{width:100%;margin:0;max-width:none}
.modal-dialog-centered{min-height:0;display:block;margin:0}
.modal-content{border-radius:.8rem .8rem 0 0;max-height:92vh;display:flex;flex-direction:column}
.modal-body{overflow-y:auto;-webkit-overflow-scrolling:touch}
}
#aboutModal .modal-header{cursor:grab;user-select:none;touch-action:none}
#aboutModal .modal-header:active{cursor:grabbing}
.main{position:fixed;top:0;left:240px;right:0;bottom:0;margin:0;max-width:none;padding-top:1.2rem;overflow:hidden;transition:left .25s ease}
html.sidebar-hidden .main{left:0}
/* Оконный рабочий стол */
.win{position:fixed;display:flex;flex-direction:column;background:rgba(23,32,48,var(--window-alpha,0.97));border:1px solid var(--line);border-radius:.5rem;box-shadow:0 18px 50px rgba(0,0,0,.55);overflow:hidden}
.win-titlebar{display:flex;align-items:center;justify-content:space-between;padding:.45rem .75rem;background:rgba(255,255,255,.06);border-bottom:1px solid var(--line);cursor:grab;user-select:none;touch-action:none}
.win-titlebar:active{cursor:grabbing}
.win-title{font-weight:600;color:#fff;display:flex;gap:.45rem;align-items:center}
.win-close{border:0;background:none;color:var(--muted);cursor:pointer;padding:0 .25rem;font-size:1rem;line-height:1}
.win-close:hover{color:#fff}
.win-body { flex: 1; overflow-y: auto; padding: 20px; }
.win[data-key="about"] .win-body,.win[data-key="about"] .win-body *{color:#fff}

/* Вкладки внутри окна (новый механизм, client-side) */
.tabbar { display: flex; align-items: center; gap: 4px; margin-bottom: 16px; border-bottom: 1px solid var(--line); }
.tabbtn { padding: 8px 14px; font-size: 13px; color: var(--muted); cursor: pointer; border: 0; background: none; border-bottom: 2px solid transparent; margin-bottom: -1px; user-select: none; transition: color .15s, border-color .15s; }
.tabbtn:hover { color: #fff; }
.tabbtn.active { color: var(--acc); border-bottom-color: var(--acc); }
.tabpanel { display: none; }
.tabpanel.active { display: block; }
.win-resize{position:absolute;right:0;bottom:0;width:16px;height:16px;cursor:nwse-resize;touch-action:none}
.card{background:var(--panel);backdrop-filter:blur(8px);border:1px solid var(--line);border-radius:.6rem}
.card-header{padding:.7rem 1rem;border-bottom:1px solid var(--line);font-weight:600;color:#fff;display:flex;justify-content:space-between;align-items:center}
.card-body{padding:1rem}
.stat-num{font-size:1.9rem;font-weight:700;color:#fff}
.stat-label{color:#fff}
.stat-card{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:.75rem}
.stat-icon{width:2.6rem;height:2.6rem;border-radius:.5rem;display:flex;align-items:center;justify-content:center;font-size:1.3rem;margin-bottom:.4rem}
.stat-icon.primary{background:rgba(76,201,240,.18);color:#4cc9f0}
.stat-icon.success{background:rgba(67,209,124,.18);color:#43d17c}
.stat-icon.warning{background:rgba(255,193,7,.18);color:#ffc107}
.stat-icon.danger{background:rgba(239,91,110,.18);color:#ef5b6e}
.table-dark{color:var(--txt)}
.table-dark th{color:#fff;text-transform:uppercase;font-size:.72rem;letter-spacing:.05em;border-color:var(--line)}
.table-dark td,.table-dark th{border-color:var(--line);vertical-align:middle}
.table-dark tbody tr:hover{background:rgba(255,255,255,.04)}
.btn{display:inline-block;padding:.4rem .9rem;border-radius:.4rem;border:1px solid transparent;cursor:pointer;font-size:.9rem}
.btn-primary{background:var(--acc);color:#06263b;font-weight:600}.btn-primary:hover{filter:brightness(1.1)}
.btn-outline{border-color:rgba(255,255,255,.25);color:var(--txt)}.btn-outline:hover{background:rgba(255,255,255,.08)}
.btn-danger{background:transparent;border-color:var(--bad);color:var(--bad)}.btn-danger:hover{background:var(--bad);color:#fff}
.form-label{color:var(--muted);font-size:.8rem;margin-bottom:.25rem}
.form-control{background:rgba(10,16,28,.7);border:1px solid var(--line);color:var(--txt);border-radius:.4rem;padding:.45rem .7rem;width:100%}
.form-control:focus{outline:none;border-color:var(--acc)}
.form-check-input{background-color:rgba(10,16,28,.7);border-color:var(--line)}
.badge-ok{background:rgba(67,209,124,.15);color:var(--ok)}.badge-off{background:rgba(239,91,110,.15);color:var(--bad)}
.login-wrap{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:1rem}
.login-wrap.login-pos-right{justify-content:flex-end;padding-right:8vw}
@media (max-width:576px){.login-wrap.login-pos-right{justify-content:center;padding-right:1rem}}
.login-card{width:100%;max-width:400px;background:rgba(33,41,58,var(--login-alpha,.78));backdrop-filter:blur(12px);border:1px solid var(--line);border-radius:.8rem;padding:2rem}
.login-logo{font-size:2.6rem;text-align:center;margin-bottom:.5rem}
.muted{color:var(--muted)}.small{font-size:.8rem}
.users-tab{color:#fff}
.appearance-tab .form-check-label{color:#fff}
#menu-sections-list .form-check-label{color:#fff}
.users-tab .muted,.users-tab label,.users-tab th,.users-tab td{color:#fff}
.users-tab .form-control{color:#fff}
.tbl-white td,.tbl-white td code,.tbl-white td .muted{color:#fff}
/* Element-level specificity beats Tabler's `.table>:not(caption)>*>*`, which
   otherwise repaints the header cells in its muted colour. */
table.tbl-white th{color:#fff}
.alert{padding:.6rem .9rem;border-radius:.4rem;font-size:.9rem}.alert-danger{background:rgba(239,91,110,.15);color:var(--bad)}
.sidebar-user{display:flex;align-items:center;justify-content:center;gap:.55rem;padding:.35rem .6rem 1rem}
.sidebar-copy{margin-top:auto;padding:.75rem .4rem 0;text-align:center;font-size:.72rem;color:var(--muted);line-height:1.6}
.sidebar-copy a{display:block;color:var(--muted);text-decoration:none}
.sidebar-copy a:hover{color:#fff}
.sidebar-avatar{width:57px;height:57px;border-radius:50%;object-fit:cover;flex:0 0 auto;background:rgba(255,255,255,.12);border:1px solid var(--line)}
.sidebar-avatar-ph{background:rgba(255,255,255,.14)}
.sidebar-username{color:#fff;font-size:.9rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.user-avatar-cell img{width:36px;height:36px;border-radius:50%;object-fit:cover;vertical-align:middle}
.user-avatar-ph{display:inline-block;width:36px;height:36px;border-radius:50%;background:rgba(255,255,255,.14);border:1px solid var(--line);vertical-align:middle}
.modal-content{background:rgba(33,41,58,var(--window-alpha,1));backdrop-filter:blur(10px);border:1px solid var(--line);border-radius:.6rem;color:var(--txt)}
.modal-header{border-bottom:1px solid var(--line);color:#fff}
.modal-title{font-weight:600;font-size:1.05rem}
.modal-footer{border-top:1px solid var(--line)}
.btn-close{filter:invert(1) brightness(.8)}
.modal .form-check-label{color:#fff}
/* Выпадающие меню и select — в цвет окон приложения */
.dropdown-menu{background:rgba(23,32,48,var(--window-alpha,0.97));backdrop-filter:blur(10px);border:1px solid var(--line);color:var(--txt);box-shadow:0 18px 50px rgba(0,0,0,.55)}
.dropdown-menu .form-select{background-color:rgba(10,16,28,.7);border:1px solid var(--line);color:var(--txt)}
.dropdown-menu .form-select:focus{outline:none;border-color:var(--acc)}
.dropdown-menu .form-select option{background:#172030;color:var(--txt)}
.form-select{background-color:rgba(10,16,28,.7);border:1px solid var(--line);color:var(--txt)}
.form-select:focus{outline:none;border-color:var(--acc)}
.form-select option{background:#172030;color:var(--txt)}
/* Прелоадер входа */
.preloader{position:fixed;inset:0;z-index:3000;display:flex;align-items:center;justify-content:center;background:rgba(13,27,42,.65);opacity:0;pointer-events:none;transition:opacity .6s ease}
.preloader.show{opacity:1;pointer-events:auto}
/* Прелоадер «Неоновые кольца» (вариант 2) */
.pl-rings{position:relative;width:420px;height:420px}
.pl-ring{position:absolute;top:50%;left:50%;width:190px;height:190px;margin:-95px 0 0 -95px;border:none;border-bottom:8px solid var(--plc);border-radius:50%;box-shadow:0 0 4px #8e8e8e}
.pl-ring.r1{--plc:#fe53bb;animation:plRot1 1s ease-in-out infinite}
.pl-ring.r2{--plc:#8f51ea;animation:plRot2 1s ease-in-out infinite}
.pl-ring.r3{--plc:#0044ff;animation:plRot3 1s ease-in-out infinite}
.pl-ring.big{width:380px;height:380px;margin:-190px 0 0 -190px;border-bottom:16px solid var(--plc)}
.pl-ring.r4{--plc:#fe53bb;animation:plRot4 1s ease-in-out infinite}
.pl-ring.r5{--plc:#8f51ea;animation:plRot5 1s ease-in-out infinite}
.pl-ring.r6{--plc:#0044ff;animation:plRot6 1s ease-in-out infinite}
@keyframes plRot1{from{transform:rotateX(50deg) rotateZ(110deg)}to{transform:rotateX(50deg) rotateZ(470deg)}}
@keyframes plRot2{from{transform:rotateX(20deg) rotateY(50deg) rotateZ(20deg)}to{transform:rotateX(20deg) rotateY(50deg) rotateZ(380deg)}}
@keyframes plRot3{from{transform:rotateX(40deg) rotateY(130deg) rotateZ(450deg)}to{transform:rotateX(40deg) rotateY(130deg) rotateZ(90deg)}}
@keyframes plRot4{from{transform:rotateX(50deg) rotateZ(470deg)}to{transform:rotateX(50deg) rotateZ(110deg)}}
@keyframes plRot5{from{transform:rotateX(20deg) rotateY(50deg) rotateZ(380deg)}to{transform:rotateX(20deg) rotateY(50deg) rotateZ(20deg)}}
@keyframes plRot6{from{transform:rotateX(40deg) rotateY(130deg) rotateZ(90deg)}to{transform:rotateX(40deg) rotateY(130deg) rotateZ(450deg)}}
.pl-nucleus{position:absolute;top:50%;left:50%;width:20px;height:20px;margin:-10px 0 0 -10px;background:radial-gradient(circle,#fe53bb,#8f51ea);border-radius:50%;box-shadow:0 0 20px #fe53bb,0 0 40px #8f51ea;animation:plPulse 1s ease-in-out infinite}
@keyframes plPulse{0%,100%{transform:scale(1);box-shadow:0 0 20px #fe53bb,0 0 40px #8f51ea}50%{transform:scale(1.2);box-shadow:0 0 30px #fe53bb,0 0 60px #8f51ea}}
@media (max-width:480px){.pl-rings{transform:scale(.65)}}
.spin-outer{width:5rem;height:5rem;border:4px solid transparent;border-top-color:#60a5fa;border-radius:50%;display:flex;align-items:center;justify-content:center;animation:spin 1s linear infinite}
.spin-inner{width:4rem;height:4rem;border:4px solid transparent;border-top-color:#f87171;border-radius:50%;animation:spin 1s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}
/* выделение строк таблицы чекбоксом */
:root{--row-sel:rgba(72,183,252,.20)}
tbody tr.row-sel td{background:var(--row-sel)}

/* Права доступа — выпадающий список с чекбоксами */
.perm-dd{position:relative}
.perm-menu{max-height:280px;overflow-y:auto;width:100%;min-width:100%;background:rgba(33,41,58,.97);border:1px solid var(--line);color:var(--txt)}
.perm-menu .form-check-label{color:var(--txt);padding-left:0}
.perm-menu .form-check{padding-left:.75rem}
.perm-menu .form-check-input{float:none;margin-left:0;margin-right:.6rem;vertical-align:-.2em}
.perm-toggle{cursor:pointer}
/* ---------- Виджет приветствия: всегда открытое полупрозрачное окно ---------- */
.win-widget{background:rgba(23,32,48,var(--widget-alpha,.55));backdrop-filter:blur(10px)}
.widget-inner{display:flex;flex-direction:column;align-items:center;gap:.45rem;text-align:center;padding-top:.75rem}
.widget-hello{color:var(--muted);font-size:.9rem;margin:0}
.widget-name{color:#fff;font-weight:600;font-size:1.05rem;word-break:break-word}
.widget-date{color:#cbd5e1;font-size:.85rem;text-transform:capitalize}
.widget-clock{color:#fff;font-size:1.8rem;font-weight:700;font-variant-numeric:tabular-nums;line-height:1.1}

/* ===== _md_ifaces.css (ported from MikroDash) ===== */
.card-badge{font-family:var(--font-mono);font-size:.68rem;font-weight:600;padding:.15em .55em;border-radius:5px;background:var(--card-badge-bg);color:var(--card-badge-color);border:none;line-height:1.5}
.card-badge.active-blue{background:var(--card-badge-bg-active-blue);color:var(--card-badge-color-active-blue)}
/* Keyboard focus. Before this there were seven :focus rules in the whole
       app, all border-colour on inputs and none at all on buttons, so tabbing
       through the UI gave no indication of position (WCAG 2.4.7). Uses
       :focus-visible so a mouse click does not leave a ring behind. */
    /* !important is deliberate and load-bearing here. A dozen component classes
       set `outline:none` in their *base* rule rather than a :focus rule -
       .conn-src-filter, .sform-input, .rt-sel, .rpt-select, .bw-toolbar
       input/select among them - so those apply in every state, and
       #navRouterSelect / #routerSelectBtn are ID selectors that outrank any
       class-based focus rule outright. Without it the ring is suppressed across
       most of the interactive surface. An accessibility guarantee should not be
       defeatable by a decorative reset. */

    /* ── Shared data-table behaviour ─────────────────────────────────────
       One definition each, instead of a copy per table. The sort indicators
       existed four times (.bw-table, .rt-table, .iface-list, #page-dhcp) and
       had already drifted in encoding, and any new sortable table had to
       remember to add its own pair. Global on th means it is now free.
       Row hover was also four copies, one of which had drifted to .06 alpha
       against .05 everywhere else; they now match. Column widths, padding
       and density stay per-table, because those genuinely differ. */
    th.sort-asc::after{content:' \2191'}
.table tbody tr:hover td,.bw-table tr:hover td,
    .iface-list tbody tr:hover td{background:rgba(99,130,190,.05)}
/* stale */
    .stale-overlay{position:absolute;inset:0;border-radius:10px;background:rgba(7,9,15,.55);display:flex;align-items:center;justify-content:center;font-family:var(--font-mono);font-size:.7rem;color:var(--text-muted);z-index:10;opacity:0;pointer-events:none;transition:opacity .3s}
.card.is-stale .stale-overlay{opacity:1;pointer-events:auto}
.card.is-collector-off .stale-overlay{opacity:1;pointer-events:auto;
      background:rgba(7,9,15,.62);color:rgba(148,163,190,.75)}
/* iface grid */

    /* ── Interfaces summary row ──────────────────────────────────────────────── */
    .if-summary-row{display:grid;grid-template-columns:1fr 2fr;gap:.75rem;margin-bottom:.85rem;align-items:stretch}
}
    .if-scard{background:var(--bg-card);border:1px solid var(--border);border-radius:8px;padding:.8rem 1rem;min-width:0;display:flex;flex-direction:column}
.if-scard-title{font-family:var(--font-ui);font-weight:600;font-size:.82rem;letter-spacing:.04em;text-transform:uppercase;color:var(--text-muted);margin-bottom:.65rem}
/* Interface Types grid (left card) */
    .if-type-grid{display:flex;flex-wrap:wrap;gap:.4rem;flex:1;align-content:flex-start}
.if-type-item{background:rgba(99,130,190,.06);border:1px solid var(--border);border-radius:6px;padding:.45rem .6rem;display:flex;flex-direction:column;gap:.1rem;min-width:80px}
.if-type-label{font-size:.58rem;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);font-family:var(--font-ui);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.if-type-count{font-size:1.35rem;font-weight:700;font-family:var(--font-mono);color:var(--text-main);line-height:1}
/* Ports panel (right card) */
    .if-ports-scroll{display:flex;flex-wrap:wrap;gap:.55rem .7rem;align-items:center;justify-content:center;flex:1}
.if-port-item{display:flex;flex-direction:column;align-items:center;gap:.28rem;cursor:default}
.if-port-label{font-size:.6rem;font-family:var(--font-mono);color:var(--text-muted);text-align:center;max-width:52px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.2}
/* Port SVG colours driven by data attribute */
    .if-port-item[data-state="up"]   .if-port-svg .port-body{fill:rgba(74,222,128,.15);stroke:rgba(74,222,128,.7)}
.if-port-item[data-state="up"]   .if-port-svg .port-led {fill:rgba(74,222,128,.95)}
.if-port-item[data-state="up"]   .if-port-label{color:rgba(74,222,128,.85)}
.if-port-item[data-state="down"] .if-port-svg .port-body{fill:rgba(248,113,113,.1);stroke:rgba(248,113,113,.55)}
.if-port-item[data-state="down"] .if-port-svg .port-led {fill:rgba(248,113,113,.8)}
.if-port-item[data-state="down"] .if-port-label{color:rgba(248,113,113,.7)}
.if-port-item[data-state="dis"]  .if-port-svg .port-body{fill:rgba(99,130,190,.07);stroke:rgba(99,130,190,.2)}
.if-port-item[data-state="dis"]  .if-port-svg .port-led {fill:rgba(99,130,190,.3)}
.if-port-item[data-state="dis"]  .if-port-label{color:var(--text-muted)}
/* Tile sizing is driven by two custom properties so the card size control
       scales every element together: --if-w sets the grid track, --if-scale
       multiplies each type size and the sparkline. --if-spark-w is the
       sparkline's intrinsic width (see ifaceSparkSvg), reserved on the name so
       a long name ellipsises *before* the graph rather than sliding under it. */
    /* grid-auto-rows:1fr makes every row the same height as the tallest, so a
       row of interfaces without IP addresses is no shorter than one with them.
       Tiles stretch to fill their row by default. */
    .iface-grid{--if-w:175px;--if-scale:1;--if-spark-w:56px;
                display:grid;grid-template-columns:repeat(auto-fill,minmax(var(--if-w),1fr));
                grid-auto-rows:1fr;gap:.55rem}
.iface-grid[data-size="md"]{--if-w:230px;--if-scale:1.18}
.iface-grid[data-size="lg"]{--if-w:300px;--if-scale:1.42}
/* ── Interfaces: list view ─────────────────────────────────── */
    /* Wide table, so it scrolls inside its own container rather than pushing
       the page sideways. */
    /* .iface-grid sets display:grid, which outranks the user-agent [hidden]
       rule, so the attribute needs an explicit counterpart to take effect. */
    .iface-grid[hidden],.iface-list-wrap[hidden]{display:none}
.iface-list-wrap{overflow-x:auto}
.iface-list{font-family:var(--font-mono);font-size:.7rem;margin-bottom:0;white-space:nowrap}
.iface-list th{font-size:.62rem;text-transform:uppercase;letter-spacing:.03em;color:var(--text-muted);font-weight:600;border-bottom:1px solid var(--border);padding:.3rem .5rem}
.iface-list td{padding:.28rem .5rem;border-bottom:1px solid rgba(99,130,190,.07);vertical-align:middle}
.iface-list tr.down td{opacity:.6}
.iface-list tr.disabled td{opacity:.38}
.iface-list th[data-sort]{cursor:pointer;user-select:none}
.iface-list th[data-sort]:hover{color:var(--text-main)}
.ifl-num{text-align:right;font-variant-numeric:tabular-nums}
/* Scoped to `.iface-list td` on purpose. Tabler sets a colour on every cell
       via `.table>:not(caption)>*>*`, which outranks a bare class, so an
       unscoped `.ifl-rx` was being overridden and the rate columns rendered in
       the default text colour instead of matching the tile cards. Spans nested
       inside a cell are unaffected, which is why the rules below this one do
       not need the same treatment. */
    .iface-list td.ifl-name{font-weight:600;color:var(--text-main);max-width:16rem;overflow:hidden;text-overflow:ellipsis}
.iface-list td.ifl-type{color:var(--text-muted);font-size:.65rem}
.iface-list td.ifl-ip{color:var(--accent-rx);max-width:12rem;overflow:hidden;text-overflow:ellipsis}
/* Same variables the tile cards use for live rates, so the two views match */
    .iface-list td.ifl-rx{color:var(--accent-rx)}
.iface-list td.ifl-tx{color:var(--accent-tx)}
.iface-list td.ifl-zero{color:var(--text-muted)}
.empty-state{font-family:var(--font-mono);font-size:.74rem;color:var(--text-muted);padding:.9rem 0;text-align:center}
.iface-grid{--if-w:160px}
/* Tabler's .d-flex is display:flex!important, so it beats both the [hidden]
       user-agent rule and an ordinary override. Same trap as .iface-grid[hidden]
       above, one step worse because the utility class carries !important. */
    #rtRoutesFilters[hidden],#rtPeersFilters[hidden]{display:none!important}
.nchan-iface-list { display: flex; flex-direction: column; gap: .15rem; }

/* ===== _md_ifaces2.css (ported from MikroDash) ===== */
/* Keyboard focus. Before this there were seven :focus rules in the whole
       app, all border-colour on inputs and none at all on buttons, so tabbing
       through the UI gave no indication of position (WCAG 2.4.7). Uses
       :focus-visible so a mouse click does not leave a ring behind. */
    /* !important is deliberate and load-bearing here. A dozen component classes
       set `outline:none` in their *base* rule rather than a :focus rule -
       .conn-src-filter, .sform-input, .rt-sel, .rpt-select, .bw-toolbar
       input/select among them - so those apply in every state, and
       #navRouterSelect / #routerSelectBtn are ID selectors that outrank any
       class-based focus rule outright. Without it the ring is suppressed across
       most of the interactive surface. An accessibility guarantee should not be
       defeatable by a decorative reset. */

    /* ── Shared data-table behaviour ─────────────────────────────────────
       One definition each, instead of a copy per table. The sort indicators
       existed four times (.bw-table, .rt-table, .iface-list, #page-dhcp) and
       had already drifted in encoding, and any new sortable table had to
       remember to add its own pair. Global on th means it is now free.
       Row hover was also four copies, one of which had drifted to .06 alpha
       against .05 everywhere else; they now match. Column widths, padding
       and density stay per-table, because those genuinely differ. */
    th.sort-asc::after{content:' \2191'}
th.sort-desc::after{content:' \2193'}
.iface-tile{padding:.6rem .7rem;border-radius:7px;border:1px solid var(--border);background:rgba(99,130,190,.04);font-family:var(--font-mono);font-size:calc(.7rem * var(--if-scale));transition:border-color .2s,background .2s}
.iface-tile.up{border-color:rgba(74,222,128,.22);background:rgba(74,222,128,.04)}
.iface-tile.down{border-color:rgba(248,113,113,.2);background:rgba(248,113,113,.03);opacity:.6}
.iface-tile.disabled{opacity:.35}
/* padding-right keeps the name clear of the absolutely positioned sparkline */
    .iface-name{font-weight:600;font-size:calc(.77rem * var(--if-scale));color:var(--text-main);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:.1rem;
                padding-right:calc(var(--if-spark-w) * var(--if-scale) + .35rem)}
/* nowrap matters here too: a long comment used to wrap and make the tile
       taller than its neighbours in the same row */
    .iface-type{font-size:calc(.62rem * var(--if-scale));color:var(--text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.iface-ip{font-size:calc(.65rem * var(--if-scale));color:var(--accent-rx);margin-top:.2rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.iface-rates{margin-top:.35rem;display:flex;flex-direction:column;gap:.18rem}
.iface-rate-row{display:flex;align-items:center;gap:.3rem;font-size:calc(.63rem * var(--if-scale))}
.iface-rate-label{width:.7rem;text-align:right;opacity:.55;flex-shrink:0}
.iface-rate-bar-wrap{flex:1;height:3px;border-radius:2px;background:rgba(99,130,190,.1);overflow:hidden;min-width:0}
.iface-rate-bar{height:100%;border-radius:2px;transition:width .6s ease;min-width:0}
.iface-rate-bar.rx{background:var(--accent-rx)}
.iface-rate-bar.tx{background:var(--accent-tx)}
.iface-rate-val{width:calc(5.8rem * var(--if-scale));text-align:right;flex-shrink:0;white-space:nowrap;font-size:calc(.61rem * var(--if-scale))}
.iface-rate-val.rx{color:var(--accent-rx)}
.iface-rate-val.tx{color:var(--accent-tx)}
.iface-rate-val.zero{color:var(--text-muted)}
.iface-dot{display:inline-block;width:6px;height:6px;border-radius:50%;margin-right:3px;vertical-align:middle}
.iface-dot.up{background:var(--accent-ok)}
.iface-dot.down{background:var(--accent-err)}
.iface-dot.dis{background:var(--text-muted)}
.iface-tile{position:relative}
.iface-spark{position:absolute;top:.35rem;right:.4rem;pointer-events:none;opacity:.75;
                 width:calc(var(--if-spark-w) * var(--if-scale));height:calc(18px * var(--if-scale))}
/* Colour is carried inline, from the same palette the Interface Types card
       uses, so a type reads the same in both places. */
    .ifl-type-pill{display:inline-block;padding:.03rem .32rem;border-radius:3px;font-size:.6rem;font-weight:600;letter-spacing:.02em}
/* Scoped to `.iface-list td` on purpose. Tabler sets a colour on every cell
       via `.table>:not(caption)>*>*`, which outranks a bare class, so an
       unscoped `.ifl-rx` was being overridden and the rate columns rendered in
       the default text colour instead of matching the tile cards. Spans nested
       inside a cell are unaffected, which is why the rules below this one do
       not need the same treatment. */
    .iface-list td.ifl-name{font-weight:600;color:var(--text-main);max-width:16rem;overflow:hidden;text-overflow:ellipsis}
.iface-list td.ifl-zero{color:var(--text-muted)}
/* A counter the interface does not report at all. Distinct from a zero so
       an unsupported counter never reads as a clean bill of health. */
    .ifl-na{color:var(--text-muted);opacity:.4}
.ifl-bad{color:var(--accent-err)}
/* Movement since the previous poll - the part that says a fault is live
       rather than historical. */
    .ifl-delta{margin-left:.3rem;font-size:.6rem;padding:0 .25rem;border-radius:3px;
               background:rgba(248,113,113,.16);color:var(--accent-err)}
.vpn-tile-name .iface-dot{flex-shrink:0}

/* ===== Сетевые интерфейсы (раздел, порт MikroDash) ===== */
.ni-wrap{position:relative;padding:.25rem}
.ni-toolbar{display:flex;align-items:center;gap:.6rem;margin-bottom:.8rem;flex-wrap:wrap}
.ni-toolbar .ni-label{font-weight:600;color:#fff;display:inline-flex;align-items:center;gap:.4rem}
.ni-toolbar select{max-width:260px}
.ni-toolbar .spacer{flex:1}
.ni-cols{display:grid;grid-template-columns:minmax(280px,340px) 1fr;gap:.75rem;align-items:start}
@media(max-width:900px){.ni-cols{grid-template-columns:1fr}}
.ni-iflist{display:flex;flex-direction:column;gap:.3rem;max-height:62vh;overflow-y:auto;padding-right:.25rem}
.ni-iface{display:flex;align-items:center;gap:.55rem;padding:.45rem .6rem;border-radius:.4rem;border:1px solid transparent;background:rgba(255,255,255,.03);cursor:pointer;font-size:.85rem}
.ni-iface:hover{background:rgba(76,201,240,.08)}
.ni-iface.active{border-color:var(--acc);background:rgba(76,201,240,.12)}
.ni-iface .dot{width:.5rem;height:.5rem;border-radius:50%;flex:0 0 auto}
.ni-iface.ok .dot{background:var(--ok)}
.ni-iface.bad .dot{background:var(--bad)}
.ni-iface.warn .dot{background:#ffc107}
.ni-iface .name{font-weight:600;color:#fff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ni-iface .type{flex:0 0 auto}
.ni-iface .rate{margin-left:auto;font-size:.72rem;white-space:nowrap;display:flex;gap:.5rem}
.ni-wrap .rx{color:var(--acc)}
.ni-wrap .tx{color:#2dd4a8}
.ni-detail{min-height:300px;padding:1rem;max-height:calc(100vh - 150px);overflow-y:auto;overflow-x:hidden}
.ni-table td{word-break:break-word}
.ni-emptybox{display:flex;flex-direction:column;align-items:center;gap:.4rem}
.ni-dhead{display:flex;align-items:center;gap:.6rem;margin-bottom:.9rem;flex-wrap:wrap}
.ni-ifname{font-size:1.05rem;font-weight:700;color:#fff}
.ni-err{color:var(--bad);font-weight:600}
.badge.ok{background:rgba(67,209,124,.18);color:var(--ok)}
.badge.bad{background:rgba(239,91,110,.18);color:var(--bad)}
.badge.warn{background:rgba(255,193,7,.18);color:#ffc107}
.ni-table{width:100%;border-collapse:collapse;font-size:.85rem;margin-bottom:.9rem}
.ni-table td{padding:.4rem .55rem;border-bottom:1px solid var(--line)}
.ni-table td.k{color:var(--muted);width:42%}
.ni-table td:not(.k){color:#fff;font-weight:600}
.ni-meters{display:grid;grid-template-columns:1fr 1fr;gap:.6rem;margin-bottom:.9rem}
.ni-meter{background:rgba(255,255,255,.04);border:1px solid var(--line);border-radius:.45rem;padding:.55rem .7rem}
.ni-meter .lbl{display:block;color:var(--muted);font-size:.72rem;margin-bottom:.15rem}
.ni-meter .val{font-size:1.05rem;font-weight:700}
.ni-chart{height:240px;border:1px solid var(--line);border-radius:.45rem;overflow:hidden;margin-bottom:.9rem;background:rgba(0,0,0,.18);position:relative}
.ni-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:.5rem}
.ni-stat{background:rgba(255,255,255,.04);border-radius:.45rem;padding:.5rem .65rem;display:flex;flex-direction:column;gap:.15rem}
.ni-stat b{color:#fff;font-size:.9rem}
.bad-txt{color:var(--bad)!important}
#ni-stale.show{opacity:1;pointer-events:auto}

/* Индикатор доступности роутера (раздел «Роутеры») */
.avail{display:inline-flex;align-items:center;gap:.4em;font-size:.82rem;white-space:nowrap}
.avail i{width:.62em;height:.62em;border-radius:50%;flex:0 0 auto}
.avail.ok{color:var(--ok)}
.avail.ok i{background:var(--ok);box-shadow:0 0 .45rem var(--ok)}
.avail.bad{color:var(--bad)}
.avail.bad i{background:var(--bad);box-shadow:0 0 .45rem var(--bad)}
