/* RakControl — strict dark-and-white console. The base is neutral; colour is
   reserved for semantics only: status badges, load metres, action buttons. */

@font-face {
  font-family: "RPanelBrand";
  src: url("../fonts/ShoptronicSP-Basic.otf") format("opentype");
  font-weight: 400; font-style: normal; font-display: swap;
}

:root {
  --bg:        #f4f5f7;
  --paper:     #ffffff;
  --ink:       #14161a;
  --ink-2:     #3e4451;
  --graphite:  #6b7280;
  --faint:     #98a1b3;
  --rule:      #e6e8ec;
  --rule-2:    #d3d6dc;
  --wash:      #f7f8fa;

  --accent:    #2f6fed;
  --accent-bg: #edf3ff;
  --ok:        #12885a;
  --ok-bg:     #e8f6f0;
  --warn:      #d97706;
  --warn-bg:   #fdf3e5;
  --danger:    #cf3a3a;
  --danger-bg: #fdeeee;
  /* CHECK: the bot needs a human, which is neither healthy nor broken. */
  --check:     #7c3aed;
  --check-bg:  #f3ecff;

  --rail:      #14161a;
  --rail-dim:  #262a31;
  --rail-fg:   #aab1bd;

  --r:         6px;
  --shadow:    0 1px 2px rgba(20,22,26,.06);
  --shadow-lg: 0 20px 50px rgba(20,22,26,.22);
  --rail-w:    244px;
  --bar-h:     58px;
}

*, *::before, *::after { box-sizing: border-box }
* { margin: 0; padding: 0 }

html {
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  font-size: 14px;
  -webkit-text-size-adjust: 100%;
}
body {
  background: var(--bg);
  color: var(--ink);
  line-height: 1.5;
  min-width: 320px;
  font-feature-settings: "tnum" 1;
}
code, .mono, pre { font-family: ui-monospace, "Cascadia Mono", "Cascadia Code", Consolas, "Liberation Mono", monospace }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px }
::selection { background: var(--accent); color: #fff }

/* ============================ layout ============================ */
.layout { display: flex; min-height: 100vh }

/* ============================ rail (dark) ============================ */
.sidebar {
  width: var(--rail-w);
  min-height: 100vh;
  background: var(--rail);
  color: var(--rail-fg);
  display: flex; flex-direction: column;
  position: fixed; left: 0; top: 0; bottom: 0;
  z-index: 100; overflow-y: auto;
}
.brand {
  height: var(--bar-h);
  display: flex; align-items: center; gap: 11px;
  padding: 0 20px;
  font-weight: 700; font-size: 15px; letter-spacing: -.3px;
  color: #fff;
  border-bottom: 1px solid rgba(255,255,255,.08);
}
.brand-mark {
  width: 32px; height: 32px;
  object-fit: contain; flex: none;
  /* The logo PNG carries its own transparency — no tile behind it. No
     background, no blend, just the white shield/molniebot art on the rail. */
  background: none;
  border-radius: 0;
  filter: drop-shadow(0 0 6px rgba(255,255,255,.08));
}
.brand-mark-lg { width: 36px; height: 36px; border-radius: 0 }
/* RPanel wordmark — Shoptronic SP Basic, each letter flipping in on its own
   stagger. Letters spin on the Y axis so the wordmark reads as a solid object
   turning rather than a row of independent fades. */
.brand-name {
  white-space: nowrap;
  font-family: "RPanelBrand", "Segoe UI", system-ui, -apple-system, sans-serif;
  font-size: 21px; font-weight: 400; letter-spacing: .6px;
  color: #fff;
  perspective: 320px;
}
.brand-name .l {
  display: inline-block;
  opacity: 0;
  transform-origin: 50% 55%;
  will-change: opacity, transform;
  animation: letterSpin 3.8s ease-in-out infinite;
}
.brand-name .l:nth-child(1) { animation-delay: 0s }
.brand-name .l:nth-child(2) { animation-delay: .12s }
.brand-name .l:nth-child(3) { animation-delay: .24s }
.brand-name .l:nth-child(4) { animation-delay: .36s }
.brand-name .l:nth-child(5) { animation-delay: .48s }
.brand-name .l:nth-child(6) { animation-delay: .6s }
@keyframes letterSpin {
  0%, 10%   { opacity: 0; transform: rotateY(90deg)  scale(.7) }
  30%, 65%  { opacity: 1; transform: rotateY(0deg)   scale(1) }
  85%, 100% { opacity: 0; transform: rotateY(-90deg) scale(.7) }
}

.nav { padding: 14px 12px; display: flex; flex-direction: column; gap: 2px }
.nav-item {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 11px; border-radius: var(--r);
  color: var(--rail-fg); text-decoration: none;
  font-weight: 500; font-size: 13.5px;
  transition: background .14s ease, color .14s ease;
}
.nav-item .ico {
  width: 17px; height: 17px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
  opacity: .75;
}
.nav-item:hover { background: var(--rail-dim); color: #fff }
.nav-item:hover .ico { opacity: 1 }
.nav-item.active {
  background: #fff; color: var(--ink); font-weight: 650;
}
.nav-item.active .ico { opacity: 1; stroke: var(--ink) }
.as-button { border: 0; background: none; cursor: pointer; width: 100%; font: inherit; text-align: left }

.sidebar-foot {
  margin-top: auto;
  border-top: 1px solid rgba(255,255,255,.08);
  padding: 12px;
  display: flex; flex-direction: column; gap: 10px;
}
.account { display: flex; align-items: center; gap: 10px; min-width: 0 }
.avatar {
  width: 32px; height: 32px; border-radius: 50%;
  background: var(--rail-dim); color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-weight: 700; font-size: 12px; flex: none;
}
.account-meta { display: flex; flex-direction: column; min-width: 0 }
.account-name, .account-role { overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
.account-name { font-weight: 600; font-size: 13px; color: #fff }
.account-role { font-size: 11.5px; color: var(--rail-fg) }
.logout-btn {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  width: 100%; padding: 8px 10px;
  border: 1px solid rgba(255,255,255,.14); border-radius: var(--r);
  background: transparent; color: var(--rail-fg);
  font: 600 11.5px/1 inherit; letter-spacing: .2px;
  cursor: pointer;
  transition: background .14s, color .14s, border-color .14s;
}
.logout-btn:hover { background: var(--danger); color: #fff; border-color: var(--danger) }
.logout-btn .ico { width: 13px; height: 13px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round }

/* ============================ topbar ============================ */
.content-wrap { margin-left: var(--rail-w); flex: 1; min-width: 0 }
.topbar {
  height: var(--bar-h);
  background: rgba(255,255,255,.9);
  backdrop-filter: saturate(160%) blur(8px);
  border-bottom: 1px solid var(--rule);
  display: flex; align-items: center; gap: 14px;
  padding: 0 28px;
  position: sticky; top: 0; z-index: 40;
}
.burger { display: none }
.page-title { font-weight: 650; font-size: 15px; letter-spacing: -.2px }
.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 12px }

/* conn indicator removed — stale markup keeps it invisible if an old cached
   html still ships the element. */
.conn { display: none !important }

/* ============================ page ============================ */
.view { padding: 26px 28px 34px; max-width: 1340px }
.page-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; margin-bottom: 20px; flex-wrap: wrap;
}
.page-head h2 { font-size: 21px; font-weight: 680; letter-spacing: -.5px; line-height: 1.2 }
.page-head .btn { flex: none }
.page-head-ic {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; margin-right: 10px;
  border-radius: 8px; vertical-align: -7px;
  background: var(--ink); color: #fff;
}
.page-head-ic .ico {
  width: 17px; height: 17px;
  fill: none; stroke: currentColor; stroke-width: 1.8;
  stroke-linecap: round; stroke-linejoin: round;
}
.page-head-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap }
.page-head-meta .badge .ico {
  width: 12px; height: 12px; margin-right: 5px;
  fill: none; stroke: currentColor; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}

/* ============================ stat strip ============================
   Seven counters describing two different subjects: 2 server metrics and 5
   bot metrics. They ship as two labelled panels rather than one flat grid.
   A single run of seven equal tiles cannot say which numbers are servers and
   which are bots, and at any column count that does not divide seven the last
   tile strands itself alone on its own row with an empty cell beside it. Inside
   a panel the tiles wrap on flex-basis instead, so a short final row stretches
   to the full width and leaves no hole. */
.stats {
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  gap: 16px;
  margin-bottom: 26px;
}
.stat-panel {
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--r);
  box-shadow: var(--shadow);
  overflow: hidden;
}
.stat-panel-head {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 16px;
  border-bottom: 1px solid var(--rule);
  background: var(--wash);
}
.stat-panel-title {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 10.5px; font-weight: 700;
  letter-spacing: .9px; text-transform: uppercase;
  color: var(--graphite);
}
.stat-panel-title .ico {
  width: 14px; height: 14px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.9;
  stroke-linecap: round; stroke-linejoin: round;
}
.stat-panel-meta {
  margin-left: auto; font-size: 12px; color: var(--faint);
  font-variant-numeric: tabular-nums;
}
.stat-panel-grid {
  display: flex; flex-wrap: wrap;
  background: var(--rule);   /* shows through the 1px gap as the rule colour */
  gap: 1px;
}
.stat {
  flex: 1 1 138px;          /* wraps before a tile can squeeze below ~138px */
  min-width: 0;
  background: var(--paper);
  padding: 13px 16px 14px;
  display: flex; flex-direction: column; gap: 6px;
}
.stat-label {
  font-size: 12px; font-weight: 500; color: var(--graphite);
  line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.stat-value {
  font-size: 28px; font-weight: 680; line-height: 1.05;
  letter-spacing: -1px; font-variant-numeric: tabular-nums;
}
.stat .meter { margin-top: 2px }

/* Tone is semantic, and each tone answers a different question:
   `total` is a neutral count, `ok` a healthy live count, `off` a plain count
   of bots that are simply not running, `wait` a self-healing transition the
   backend resolves on its own, `danger` the one state that needs a human.
   Any zero count collapses to `idle` so the strip stays quiet until there is
   something to look at, instead of seven coloured tiles shouting at once. */
.stat.is-total  .stat-value { color: var(--ink) }
.stat.is-ok     .stat-value { color: var(--ok) }
.stat.is-off    .stat-value { color: var(--ink-2) }
.stat.is-wait   .stat-value { color: var(--warn) }
.stat.is-danger .stat-value { color: var(--danger) }
.stat.is-idle   .stat-value { color: var(--faint) }

/* Only the two states worth glancing at get a tinted cell. Everything else
   stays on white paper. */
.stat.is-wait   { background: var(--warn-bg) }
.stat.is-danger { background: var(--danger-bg) }
.stat.is-danger .stat-label { color: var(--danger) }

/* ============================ cards ============================ */
.card {
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--r);
  padding: 18px 20px;
  margin-bottom: 20px;
  box-shadow: var(--shadow);
  overflow: hidden;
}
.card:last-child { margin-bottom: 0 }
.card-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 14px;
  padding-bottom: 10px; border-bottom: 2px solid var(--wash);
}
.card-head h3 {
  font-size: 14px; font-weight: 680; letter-spacing: -.2px;
  display: flex; align-items: center; gap: 9px; min-width: 0;
}
/* Themed square badge in front of every section title. */
.card-head-ic {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; flex: none;
  border-radius: 7px;
  background: var(--wash); color: var(--ink-2);
  border: 1px solid var(--rule);
}
.card-head-ic .ico {
  width: 15px; height: 15px;
  fill: none; stroke: currentColor; stroke-width: 1.8;
  stroke-linecap: round; stroke-linejoin: round;
}
/* Inline glyph inside a normal-weight button or link. */
.btn .ico, .btn-sm .ico {
  width: 14px; height: 14px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.9;
  stroke-linecap: round; stroke-linejoin: round;
}
.btn .ico { margin-right: 7px }
.btn-sm .ico { margin-right: 6px }
.btn > .ico:last-child:not(:first-child) { margin-right: 0; margin-left: 6px }
/* Buttons carrying a glyph lay their text out in a row. */
.btn { display: inline-flex; align-items: center; justify-content: center }

.card-item {
  position: relative;
  display: flex; flex-direction: column;
  padding: 15px 16px 15px 18px;
  border: 1px solid var(--rule);
  border-radius: var(--r);
  background: var(--paper);
  /* Without this the grid track keeps the card at its min-content width, so a
     long IP line made a 360px card inside a 320px screen. */
  min-width: 0;
  transition: border-color .14s ease, box-shadow .14s ease;
}
.card-item::before {
  content: ""; position: absolute; left: 0; top: 12px; bottom: 12px;
  width: 4px; border-radius: 0 3px 3px 0;
  background: var(--faint);
}
.card-item.state-online::before   { background: var(--ok) }
.card-item.state-starting::before,
.card-item.state-stopping::before,
.card-item.state-restarting::before,
.card-item.state-connecting::before { background: var(--warn) }
.card-item.state-crashed::before,
.card-item.state-error::before     { background: var(--danger) }
.card-item:hover { border-color: var(--rule-2); box-shadow: 0 6px 20px rgba(20,22,26,.1) }

.card-item-head { display: flex; align-items: center; gap: 11px; margin-bottom: 12px; min-width: 0 }
/* Server glyph in a tinted square — the anchor that makes each tile scannable
   at a glance instead of a wall of small text. */
.card-item-ic {
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; flex: none;
  border-radius: 10px;
  background: var(--wash); color: var(--graphite);
  border: 1px solid var(--rule);
  transition: background .14s ease, color .14s ease, border-color .14s ease;
}
.card-item-ic .ico {
  width: 19px; height: 19px;
  fill: none; stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
}
.card-item.state-online  .card-item-ic { background: var(--ok-bg);     color: var(--ok);     border-color: rgba(15,157,99,.25) }
.card-item.state-crashed .card-item-ic,
.card-item.state-error   .card-item-ic { background: var(--danger-bg); color: var(--danger); border-color: rgba(207,58,58,.25) }
.card-item.state-starting .card-item-ic,
.card-item.state-stopping .card-item-ic,
.card-item.state-restarting .card-item-ic,
.card-item.state-connecting .card-item-ic { background: var(--warn-bg); color: var(--warn); border-color: rgba(217,119,6,.25) }

.card-item-id { min-width: 0; flex: 1 }
.card-item-title { font-weight: 680; font-size: 15px; letter-spacing: -.2px; line-height: 1.25; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
.card-item-sub {
  font-size: 12px; color: var(--graphite); margin-top: 2px;
  font-variant-numeric: tabular-nums;
  display: flex; align-items: center; gap: 5px; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.card-item-ip { display: inline-flex; flex: none }
.card-item-ip .ico {
  width: 12px; height: 12px;
  fill: none; stroke: currentColor; stroke-width: 1.8;
  stroke-linecap: round; stroke-linejoin: round;
  opacity: .7;
}
.card-item-meta {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(104px, 1fr));
  gap: 12px 14px; font-size: 12.5px; padding-top: 12px;
  margin-top: auto;
  border-top: 1px solid var(--rule);
}
.card-item-meta > div { min-width: 0 }
.card-item-meta .meta-label {
  display: flex; align-items: center; gap: 5px;
  font-size: 11px; color: var(--faint);
  font-weight: 500; margin-bottom: 4px; letter-spacing: .1px;
}
.card-item-meta .meta-label .ico {
  width: 12px; height: 12px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.9;
  stroke-linecap: round; stroke-linejoin: round;
}
/* the value slot: meters and plain values start on the same line */
.card-item-meta [data-m] { display: block }
.bot-line { display: flex; align-items: center; gap: 7px; flex-wrap: wrap }
.bot-count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 28px; height: 24px; padding: 0 8px; border-radius: 999px;
  background: var(--wash); color: var(--ink);
  border: 1px solid var(--rule-2);
  font-weight: 700; font-size: 12.5px; font-variant-numeric: tabular-nums;
}
.meta-kv { font-weight: 550; font-variant-numeric: tabular-nums }
.card-item-actions {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: 6px; margin-top: 12px; padding-top: 10px;
  border-top: 1px solid var(--rule);
}
.card-item-actions .btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.card-item-actions .btn .ico {
  width: 13px; height: 13px; flex: none; margin-right: 0;  /* gap does the spacing */
  fill: none; stroke: currentColor; stroke-width: 1.8;
  stroke-linecap: round; stroke-linejoin: round;
}
/* Colour is reserved for semantics only: one accent for the primary action,
   one danger for the destructive one. Everything else stays neutral. */
.card-item-actions .btn-primary { background: var(--accent); border-color: var(--accent); color: #fff }
.card-item-actions .btn-primary:hover { background: #255fe0; border-color: #255fe0 }
.card-item-actions .btn-ghost {
  background: var(--paper); border-color: var(--rule-2); color: var(--ink-2); box-shadow: none;
}
.card-item-actions .btn-ghost:hover { border-color: var(--ink); color: var(--ink); background: var(--wash) }
.card-item-actions .btn-danger { background: var(--danger-bg); border-color: rgba(217,59,59,.35); color: var(--danger); box-shadow: none }
.card-item-actions .btn-danger:hover { background: var(--danger); border-color: var(--danger); color: #fff }

.grid { display: grid; gap: 14px }
.grid.cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)) }
.grid.cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)) }

/* ============================ settings ============================
   The settings cards used to be a single 560px column pinned to the left of a
   1340px page, so everything ran down the screen with dead space beside it.
   An auto-fit grid of six equal cards was no better: a tall users table sat
   beside a one-line profile, leaving a column of holes, and "О программе"
   stranded itself across the full width on its own.
   Two explicit columns fix both — a narrow one for settings about *you*, a wide
   one for settings that manage the fleet. Each column packs top to bottom, so
   the bottoms line up and nothing is left stranded. */
.settings-layout {
  display: grid;
  grid-template-columns: minmax(280px, 360px) minmax(0, 1fr);
  gap: 16px;
  align-items: start;
}
.settings-col { display: flex; flex-direction: column; gap: 16px; min-width: 0 }
/* The side column is much shorter than the fleet column beside it. Pinning it
   keeps the account and password fields in view while a long users table is
   scrolled, so the empty space below it reads as breathing room rather than a
   hole in the layout. Desktop only — on a phone it would fight the scroll. */
@media (min-width: 1001px) {
  .settings-col-side { position: sticky; top: calc(var(--bar-h) + 18px) }
}
.set-card { margin-bottom: 0 }
.set-card .card-head { margin-bottom: 12px }
.set-card .card-body { padding-top: 0 }
/* The two settings tables carry several columns. Below their natural width they
   would crush "Удалить" into an ellipsis, so they keep their width and scroll
   sideways inside the card instead. */
.set-card .table-wrap table { min-width: 400px }
.set-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 0 14px }
/* Explanatory copy sits under the button it qualifies, not above it. */
.set-hint { margin-top: 10px }
/* Full-width primary button inside the narrow column. */
.btn-block { width: 100% }

/* A bordered sub-panel inside a card, for the "add new" forms. Keeps the
   primary action visually separate from the list it appends to. */
.sub-form {
  margin-top: 16px; padding: 14px 16px 16px;
  background: var(--wash);
  border: 1px solid var(--rule); border-radius: var(--r);
}
.sub-form .field:last-of-type { margin-bottom: 12px }
.sub-form-title {
  display: flex; align-items: center; gap: 7px;
  font-size: 12.5px; font-weight: 680; letter-spacing: -.1px;
  color: var(--ink-2); margin-bottom: 12px;
}
.sub-form-ic { display: inline-flex }
.sub-form-ic .ico {
  width: 14px; height: 14px;
  fill: none; stroke: currentColor; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}

/* Account summary at the top of settings. */
.profile { display: flex; align-items: center; gap: 14px }
.profile-av {
  display: inline-flex; align-items: center; justify-content: center;
  width: 52px; height: 52px; flex: none;
  border-radius: 14px;
  background: var(--ink); color: #fff;
}
.profile-av .ico {
  width: 26px; height: 26px;
  fill: none; stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
}
.profile-meta { min-width: 0 }
.profile-name { font-size: 17px; font-weight: 680; letter-spacing: -.3px; line-height: 1.2 }
.profile-badges { margin: 5px 0 4px }

/* Version line: a slim full-width footer strip, not a card. As a card it was
   one line of text floating in a box the width of the page. */
.settings-about {
  display: flex; align-items: center; gap: 9px;
  margin-top: 16px; padding: 12px 16px;
  background: var(--paper);
  border: 1px solid var(--rule); border-radius: var(--r);
  font-size: 12.5px; color: var(--graphite);
}
.settings-about-ic { display: inline-flex; flex: none }
.settings-about-ic .ico {
  width: 15px; height: 15px;
  fill: none; stroke: currentColor; stroke-width: 1.8;
  stroke-linecap: round; stroke-linejoin: round;
}

/* ============================ table ============================ */
.table-wrap { overflow: auto; margin: 0 -20px; padding: 0 20px }
table { border-collapse: collapse; width: 100%; font-size: 13.5px }
thead th {
  font-weight: 600; text-align: left; padding: 9px 12px 9px 0;
  color: var(--graphite); font-size: 12px;
  background: var(--wash);
  border-bottom: 2px solid var(--rule-2);
  white-space: nowrap;
}
thead th:first-child { padding-left: 8px }
thead th + th, td + td { padding-left: 14px }
td { padding: 11px 12px 11px 0; border-bottom: 1px solid var(--rule); vertical-align: middle }
td:first-child { padding-left: 8px }
tbody tr:last-child td { border-bottom: 0 }
tbody tr:hover td { background: var(--wash) }
td strong { font-weight: 640 }
td.actions { white-space: nowrap; text-align: right; padding-right: 0 }
td.actions .btn + .btn { margin-left: 4px }

.bulk-bar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-bottom: 14px; padding: 10px 14px;
  background: var(--ink); color: #fff; border-radius: var(--r);
  font-size: 13px; font-weight: 550;
}
.bulk-bar .btn { border-color: rgba(255,255,255,.3); background: transparent; color: #fff; box-shadow: none }
.bulk-bar .btn:hover { background: rgba(255,255,255,.12); border-color: rgba(255,255,255,.6); filter: none }
.bulk-bar .btn-danger { background: #fff; color: var(--danger); border-color: #fff }

/* Группы ботов: VPS -> игровой сервер -> таблица */
.bot-group { padding: 0; overflow: hidden }
.bot-group[open] { box-shadow: var(--shadow) }
.bot-group-head {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 14px; cursor: pointer; list-style: none;
  border-bottom: 1px solid var(--rule); font-size: 13px;
}
.bot-group-head::-webkit-details-marker { display: none }
.bot-group-head::before {
  content: "▸"; color: var(--faint); font-size: 10px;
  transition: transform .16s ease;
}
.bot-group[open] > .bot-group-head::before { transform: rotate(90deg) }
.bot-group-title { font-weight: 650; letter-spacing: -.15px; flex: 1 }
.bot-group-server { padding: 10px 0 0; border-top: 1px solid var(--rule) }
.bot-group-server:first-of-type { border-top: 0 }
.bot-group-server-name {
  display: flex; align-items: center; gap: 8px;
  font-size: 12.5px; font-weight: 600; color: var(--graphite);
  padding: 0 14px 6px;
}

/* Load metres — a track with an inline label on the right. Colour is
   semantic by threshold: green <60%, amber 60–85%, red ≥85%. */
.meter { display: flex; align-items: center; gap: 8px; margin-top: 5px }
.meter .bar { flex: 1 1 auto; min-width: 0 }
.meter .bar-label { flex: none; width: 38px; text-align: right; margin-left: 0 }
.bar {
  display: inline-block; width: 74px; height: 7px; border-radius: 4px;
  background: var(--rule); vertical-align: middle; overflow: hidden;
}
.bar-fill { display: block; height: 100%; min-width: 2px; border-radius: 4px; background: var(--ok); transition: width .35s ease }
.bar.lvl-warn .bar-fill { background: var(--warn) }
.bar.lvl-hot  .bar-fill { background: var(--danger) }
.bar-lg { display: block; width: 100%; height: 7px; margin-top: 0 }
.bar-label { font-size: 11.5px; margin-left: 8px; color: var(--graphite); font-variant-numeric: tabular-nums }
.bar.lvl-warn + .bar-label, .meter.lvl-warn .bar-label { color: var(--warn); font-weight: 650 }
.bar.lvl-hot  + .bar-label, .meter.lvl-hot  .bar-label { color: var(--danger); font-weight: 650 }

/* ============================ buttons ============================ */
.btn {
  appearance: none; border: 1px solid transparent; border-radius: var(--r);
  padding: 8px 14px; background: var(--paper); color: var(--ink);
  font: 600 13px/1.2 inherit; cursor: pointer;
  transition: background .14s ease, color .14s ease, border-color .14s ease,
              box-shadow .14s ease, transform .1s ease, opacity .14s;
}
.btn:active { transform: translateY(1px) }
.btn-primary {
  background: var(--accent); color: #fff; border-color: var(--accent);
}
.btn-primary:hover { background: #255fe0; border-color: #255fe0 }
.btn-success { background: var(--ok); color: #fff; border-color: var(--ok) }
.btn-success:hover { background: #107952; border-color: #107952 }
.btn-warning { background: var(--warn); color: #fff; border-color: var(--warn) }
.btn-warning:hover { background: #b46104; border-color: #b46104 }
.btn-ghost {
  background: var(--paper); border-color: var(--rule-2); color: var(--ink-2);
  box-shadow: none;
}
.btn-ghost:hover { border-color: var(--ink); color: var(--ink); background: var(--wash) }
.btn-danger { background: var(--danger-bg); border-color: rgba(217,59,59,.35); color: var(--danger); box-shadow: none }
.btn-danger:hover { background: var(--danger); border-color: var(--danger); color: #fff }
.btn-default { background: var(--paper); border-color: var(--rule-2); color: var(--ink-2); box-shadow: none }
.btn-default:hover { background: var(--wash); border-color: var(--rule-2) }
.btn-sm { padding: 6px 11px; font-size: 12.5px }
.btn-xs { padding: 4px 9px; font-size: 11.5px; border-radius: 5px }
.btn:disabled { opacity: .5; cursor: not-allowed; transform: none }

/* ============================ badges ============================ */
.badge {
  display: inline-flex; align-items: center;
  padding: 3px 10px; border-radius: 999px;
  font-size: 11.5px; font-weight: 650; line-height: 1.45;
  white-space: nowrap;
}
.badge::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: currentColor; margin-right: 6px; flex: none;
}
.badge-sm { padding: 1px 8px; font-size: 10.5px }
.badge.pill,
.badge:not([class*="pill-"]) { background: var(--wash); color: var(--graphite); border: 1px solid var(--rule-2) }
.badge.pill-ok     { background: var(--ok-bg);     color: var(--ok);     border: 1px solid rgba(15,157,99,.3) }
.badge.pill-alarm  { background: var(--danger-bg); color: var(--danger); border: 1px solid rgba(217,59,59,.32) }
.badge.pill-warn   { background: var(--warn-bg);   color: var(--warn);   border: 1px solid rgba(230,131,10,.32) }
.badge.pill-info   { background: var(--accent-bg); color: var(--accent); border: 1px solid rgba(59,91,219,.3) }
.badge.pill-off    { background: var(--wash);      color: var(--graphite); border: 1px solid var(--rule-2) }
.badge.pill-check  { background: var(--check-bg);  color: var(--check);  border: 1px solid rgba(124,58,237,.3) }
.muted { color: var(--faint); font-size: 12.5px }

/* ============================ forms ============================ */
.field { margin-bottom: 14px }
.field label {
  display: block; font-size: 13px; font-weight: 600;
  color: var(--ink-2); margin-bottom: 6px;
}
.field input, .field select, .field textarea,
.log-toolbar select, .log-toolbar input {
  width: 100%; padding: 9px 11px;
  border: 1px solid var(--rule-2); border-radius: var(--r);
  font: inherit; font-size: 13.5px; outline: none; background: var(--paper);
  color: var(--ink);
  transition: border-color .14s ease, box-shadow .14s ease;
}
.field input:focus, .field select:focus, .field textarea:focus,
.log-toolbar select:focus, .log-toolbar input:focus {
  border-color: var(--ink); box-shadow: 0 0 0 3px rgba(20,22,26,.1);
}
.field input[type=checkbox] { width: auto; margin-right: 8px; accent-color: var(--accent) }
textarea { resize: vertical }
.inline { display: inline-flex; align-items: center; gap: 2px; font-size: 12.5px; color: var(--ink-2) }
.inline input[type=checkbox] { accent-color: var(--accent) }
.token-box {
  background: var(--wash); border: 1px solid var(--rule);
  border-radius: var(--r); padding: 10px 12px; overflow: auto; word-break: break-all;
  border-left: 3px solid var(--accent);
}
.token-box code { font-size: 12px; color: var(--ink) }

/* ============================ logs ============================ */
.log-toolbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 12px }
.log-toolbar select, .log-toolbar input { width: auto; padding: 6px 9px; font-size: 12.5px }
.log-toolbar input { min-width: 180px }
.log-view {
  height: 520px; overflow: auto;
  background: var(--paper); border: 1px solid var(--rule); border-radius: var(--r);
  padding: 8px 12px;
  font-family: ui-monospace, "Cascadia Mono", Consolas, "Liberation Mono", monospace;
  font-size: 12px; line-height: 1.65;
}
.log-view .log-row { display: flex; align-items: flex-start; gap: 9px; padding: 2px 0 }
.log-view .log-row + .log-row { border-top: 1px solid var(--wash) }
.log-ts { color: var(--faint); flex: none }
.log-msg { word-break: break-word; white-space: pre-wrap }
.log-empty { color: var(--faint); text-align: center; padding: 48px 0 }

/* ============================ modal ============================ */
.modal-root { position: relative; z-index: 200 }
.modal-backdrop {
  position: fixed; inset: 0; background: rgba(23,26,33,.55);
  display: flex; align-items: center; justify-content: center;
  z-index: 200; padding: 20px;
  animation: fade .14s ease;
}
.modal {
  background: var(--paper); border-radius: 10px;
  width: 100%; max-width: 580px; max-height: 90vh; overflow: auto;
  box-shadow: var(--shadow-lg);
  animation: rise .16s ease;
}
.modal-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 15px 20px; border-bottom: 1px solid var(--rule);
  position: sticky; top: 0; background: var(--paper); z-index: 1;
}
.modal-head h3 {
  font-size: 15px; font-weight: 680; letter-spacing: -.2px;
  position: relative; padding-left: 12px;
}
.modal-head h3::before {
  content: ""; position: absolute; left: 0; top: 50%; transform: translateY(-50%);
  width: 4px; height: 16px; border-radius: 2px;
  background: var(--ink);
}
.modal-close {
  width: 28px; height: 28px; border-radius: var(--r);
  border: 1px solid var(--rule); background: var(--paper);
  color: var(--graphite); font-size: 17px; line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.modal-close:hover { border-color: var(--danger); color: var(--danger); background: var(--danger-bg) }
.modal-body { padding: 18px 20px }
.modal-foot {
  display: flex; justify-content: flex-end; gap: 8px;
  padding: 14px 20px; border-top: 1px solid var(--rule);
  position: sticky; bottom: 0; background: var(--paper);
}
@keyframes fade { from { opacity: 0 } to { opacity: 1 } }
@keyframes rise { from { opacity: 0; transform: translateY(8px) } to { opacity: 1; transform: none } }

/* Wide modal for the R-Лог live log viewer. */
.modal.modal-wide { max-width: 880px; }

/* R-Лог toolbar row: caption left, count right, clear below on narrow. */
.rlog-toolbar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-bottom: 10px;
}
.rlog-toolbar .badge { margin-left: auto; }

/* ============================ toasts ============================ */
.toast-stack { position: fixed; right: 20px; bottom: 20px; z-index: 300; display: flex; flex-direction: column; gap: 8px }
.toast {
  position: relative; overflow: hidden;
  padding: 12px 16px 12px 18px; border-radius: var(--r);
  background: var(--paper); border: 1px solid var(--rule);
  box-shadow: 0 8px 28px rgba(23,26,33,.16);
  font-size: 13px; font-weight: 550; color: var(--ink);
  transform: translateY(8px); opacity: 0;
  transition: transform .18s ease, opacity .18s ease;
  pointer-events: none; max-width: 380px; word-break: break-word;
}
.toast::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 4px; background: var(--accent) }
.toast.show { transform: none; opacity: 1 }
.toast-success::before { background: var(--ok) }
.toast-success { color: var(--ok) }
.toast-error::before { background: var(--danger) }
.toast-error { color: var(--danger) }
.toast-warning::before { background: var(--warn) }
.toast-warning { color: var(--warn) }

/* ============================ misc ============================ */
.empty { text-align: left; color: var(--graphite); padding: 34px 2px; font-size: 13.5px }
.card-body { padding: 2px 0 0 }
.card-sub {
  margin-top: 16px; padding: 14px 16px;
  border: 1px solid var(--rule); border-left: 3px solid var(--ink);
  border-radius: var(--r); background: var(--wash);
}
.card-sub .card-head { margin: 0 0 10px; padding: 0; border: 0 }
.card-sub .card-head h3 { font-size: 13.5px; padding-left: 0 }
.card-sub .card-head h3::before { display: none }
.card-sub label { display: block; font-size: 13px; margin-bottom: 8px; color: var(--ink-2) }
.card-sub code { font-size: 12px; background: var(--paper); padding: 1px 5px; border-radius: 4px; border: 1px solid var(--rule) }

/* Compact single-line numeric setting: label left, value + unit right-aligned
   on one baseline. Used for "Задержка реконнекта" (reconnect= in the INI). */
.field-inline {
  display: grid; align-items: center; gap: 8px;
  grid-template-columns: 1fr 108px auto;
  margin-bottom: 12px;
}
.field-inline label { margin: 0; font-size: 13px; font-weight: 600; color: var(--ink-2) }
.field-inline input { width: 100%; text-align: right; font-variant-numeric: tabular-nums }
.field-inline .unit { font-size: 12px; color: var(--ink-3); margin-left: -4px }
.field-inline .hint {
  grid-column: 1 / -1; font-size: 11.5px; color: var(--ink-3);
  margin-top: -2px;
}
@media (max-width: 620px) {
  .field-inline { grid-template-columns: 1fr 96px auto; }
}

.skeleton { display: flex; flex-direction: column; gap: 8px }
.skeleton-row { height: 40px; border-radius: var(--r); background: var(--rule); animation: pulse 1.5s ease-in-out infinite }
.icon-btn { appearance: none; border: 0; background: none; cursor: pointer; color: inherit; display: inline-flex }
.sidebar-backdrop { display: none; position: fixed; inset: 0; background: rgba(23,26,33,.45); z-index: 90 }
.sidebar-backdrop.show { display: block }
@keyframes pulse { 0%,100% { opacity: 1 } 50% { opacity: .5 } }

/* ============================ login ============================ */
.login-wrap {
  display: flex; align-items: center; justify-content: center;
  min-height: 100vh; padding: 24px;
  background: var(--bg);
}
.login-card {
  width: 100%; max-width: 380px;
  background: var(--paper);
  border: 1px solid var(--rule); border-radius: 14px;
  padding: 34px 32px;
  box-shadow: 0 24px 60px rgba(23,26,33,.18);
}
.login-brand { display: flex; align-items: center; gap: 10px; margin-bottom: 6px }
/* The mark is white line art for the dark rail; on the white login card it is
   inverted so the same asset stays legible. */
.login-brand .brand-mark { filter: invert(1); }
.login-brand .brand-name {
  font-size: 22px; color: var(--ink);
}
@media (prefers-reduced-motion: reduce) {
  .brand-name .l { animation: none !important; opacity: 1; transform: none; }
}
.login-card .sub { color: var(--graphite); font-size: 13.5px; margin-bottom: 24px }
.login-card .field { margin-bottom: 16px }
.login-card .btn { display: block; width: 100%; padding: 10px 14px; font-size: 14px }
.login-error { color: var(--danger); font-size: 13px; margin-top: 12px }

/* ============================ accounts ============================ */
.acc-bar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-bottom: 14px;
}
.acc-bar input, .acc-bar select {
  padding: 7px 10px; font-size: 12.5px;
  border: 1px solid var(--rule-2); border-radius: var(--r);
  background: var(--paper); color: var(--ink); font: inherit; outline: none;
}
.acc-bar input { min-width: 220px; flex: 1 1 220px }
.acc-bar select { width: auto }
.acc-bar input:focus, .acc-bar select:focus { border-color: var(--ink); box-shadow: 0 0 0 3px rgba(20,22,26,.1) }
.th-sort {
  appearance: none; border: 0; background: none; cursor: pointer;
  font: 600 12px/1 inherit; color: inherit; padding: 0;
  display: inline-flex; align-items: center; gap: 5px;
}
.th-sort:hover { color: var(--ink) }
.th-sort.is-active { color: var(--ink) }
.sort-ico { font-size: 9px; color: var(--accent) }
.sort-ico-idle { color: var(--faint); opacity: .55 }
.custom-server.is-hidden, .is-hidden { display: none !important }
/* Accounts row helpers: inline copy button + unlock date under the tag cell.
   The copy button is deliberately quiet — same type size as the nickname, no
   extra border — because in this dense table a loud button would draw the eye
   away from the data itself. Accent on hover signals «this is clickable». */
.acc-cell { display: flex; align-items: center; gap: 6px; margin: 2px 0 }
.acc-copy {
  appearance: none; border: 0; background: none; cursor: pointer;
  font: inherit; font-size: 12.5px; color: var(--faint);
  padding: 1px 4px; border-radius: 4px; line-height: 1.4;
}
.acc-copy:hover { color: var(--accent); background: var(--accent-bg) }
.acc-unlock { font-size: 11.5px; color: var(--faint); margin-top: 3px }
.acc-unlock.is-over { color: var(--danger); font-weight: 650 }
.acc-unlock-field input { width: auto; min-width: 180px }

/* ============================ responsive ============================ */
/* Breakpoints follow the layout, not specific devices:
     1240 — the content column stops growing, so the widest two-up grids tighten
     1000 — stat panels stack; settings drop to a single readable column
      980 — two-up card grids become one column
      760 — the rail leaves the flow and becomes an overlay drawer
      560 — phones: buttons go full width, dense rows stack
      400 — the narrowest supported width (html sets min-width:320px) */

/* The stat panels wrap on flex-basis the whole way down, so only the split
   between the two panels is worth overriding here. */
@media (max-width: 1240px) {
  .view { max-width: 100% }
}
@media (max-width: 1000px) {
  .stats { grid-template-columns: 1fr }
  .settings-layout { grid-template-columns: 1fr; gap: 14px }
  .settings-col { gap: 14px }
  .settings-about { margin-top: 14px }
}
@media (max-width: 1180px) {
  /* Two-up cards get narrow enough here that three action buttons in a row clip
     their labels ("Перезапустить" needs ~115px and only gets ~100px). Two columns
     keeps every label on one line. */
  .card-item-actions { grid-template-columns: repeat(2, minmax(0, 1fr)) }
}
@media (max-width: 980px) {
  .grid.cols-2, .grid.cols-3 { grid-template-columns: 1fr }
}
@media (max-width: 760px) {
  .sidebar { transform: translateX(-100%); transition: transform .18s ease }
  .sidebar.open { transform: none }
  .content-wrap { margin-left: 0 }
  .burger {
    display: inline-flex; align-items: center; justify-content: center;
    width: 32px; height: 32px; border-radius: var(--r);
    border: 1px solid var(--rule); background: var(--paper);
  }
  .burger .ico { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round }
  .topbar { padding: 0 16px }
  .view { padding: 18px 16px 28px }
  .stats { gap: 12px }
  .stat { padding: 12px 14px 13px }
  .stat-value { font-size: 25px }
  .card { padding: 15px 16px }
  .table-wrap { margin: 0 -16px; padding: 0 16px }
  table { font-size: 12.5px }
  td.actions { white-space: normal }

  /* Page title shrinks and its badge shrinks with it. */
  .page-head h2 { font-size: 19px }
  .page-head-ic { width: 26px; height: 26px; margin-right: 8px; vertical-align: -5px }
  .page-head-ic .ico { width: 15px; height: 15px }
  /* The dashboard summary badges get their own line instead of squeezing. */
  .page-head-meta { width: 100% }

  .card-item { padding: 13px 13px 13px 15px }
  .card-item-meta { grid-template-columns: repeat(2, minmax(0, 1fr)) }
}
@media (max-width: 560px) {
  /* A phone-width button cannot fit "Перезапустить" next to an icon in three
     columns, and full-width rows are easier to hit with a thumb anyway. */
  .page-head .btn { width: 100% }
  .card-item-actions { grid-template-columns: 1fr }
  .set-row { grid-template-columns: 1fr }
  .sub-form { padding: 13px 13px 14px }
  .profile-av { width: 44px; height: 44px; border-radius: 12px }
  .profile-av .ico { width: 22px; height: 22px }
  .profile-name { font-size: 15px }
  .modal { max-height: 94vh }
  .modal-body { padding: 15px 16px }
  .modal-head, .modal-foot { padding-left: 16px; padding-right: 16px }
  .toast-stack { left: 16px; right: 16px; bottom: 16px }
  .toast { max-width: none }
  .log-view { height: 60vh; font-size: 11.5px }
  .acc-bar input { min-width: 0; flex: 1 1 100% }
}
@media (max-width: 420px) {
  .stat-panel-head { padding: 9px 14px }
  /* Two tiny columns squeeze "Всего серверов" into an ellipsis; one is
     readable at this width. */
  .card-item-meta { grid-template-columns: 1fr; gap: 10px }
  .card-item-actions .btn { padding-left: 10px; padding-right: 10px }
  .card-head-ic { width: 24px; height: 24px }
  .card-head-ic .ico { width: 14px; height: 14px }
  .view { padding: 16px 12px 24px }
  .card { padding: 14px 13px }
  .table-wrap { margin: 0 -13px; padding: 0 13px }
}

/* ============================ mobile: server tiles ============================
   Mobile only, 480px and down. On a phone the tile head had to share its width
   three ways — a 36px server badge, the name/IP, and a ~95px status pill — which
   left the name column about 85px on a 320px screen, so long names and IP:port
   pairs were cut off mid-string with nothing indicating there was more to read.
   Dropping the status pill onto its own line, indented to sit under the name,
   hands the full width to the text and tightens the tile's padding so the card
   itself is inset a little further from the screen edge. Nothing above 480px is
   touched, so tablets and desktops keep the inline layout. */
@media (max-width: 480px) {
  .card-item { padding: 13px 12px 14px }
  .card-item-ic { width: 32px; height: 32px; border-radius: 9px }
  .card-item-ic .ico { width: 17px; height: 17px }
  /* The head becomes a two-row grid instead of a wrapping flex row. A flex item
     with `flex-basis:100%` claims the whole line and the pill stretches across
     it; explicit grid areas put the pill on row two while `justify-self:start`
     keeps it hugging its own text. */
  .card-item-head {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas: "ic id" "badge badge";
    column-gap: 9px; row-gap: 8px;
    align-items: center;
  }
  .card-item-ic { grid-area: ic }
  .card-item-id { grid-area: id; min-width: 0 }
  /* 32px icon + 9px gap lines the pill up with the name, not the tile edge. */
  .card-item-head > .badge { grid-area: badge; justify-self: start; margin-left: 41px }
  .card-item-title { font-size: 14.5px }
  /* "ip:port · агент v1.4" is one long unbreakable run, so nowrap cut it in the
     middle on a phone. Let it use a second line instead — two short lines read,
     one clipped line does not. */
  .card-item-sub { white-space: normal; overflow: visible; text-overflow: clip; flex-wrap: wrap }
  .card-item-actions { gap: 7px; padding-top: 11px; margin-top: 11px }
  .card-item-actions .btn { padding-left: 12px; padding-right: 12px }

  /* Status pills lose a little of their air on a phone so the word and its dot
     read as one tight label rather than a wide empty bar. */
  .badge { padding: 2px 8px; font-size: 11px }
  .badge::before { width: 5px; height: 5px; margin-right: 5px }
}

/* Touch devices get bigger hit areas without changing the desktop layout. */
@media (hover: none) and (pointer: coarse) {
  .nav-item { padding: 11px 11px }
  .card-item-actions .btn { padding-top: 9px; padding-bottom: 9px }
  .btn { padding-top: 9px; padding-bottom: 9px }
  /* Hover shadows stick on touch and read as a stuck state. */
  .card-item:hover { box-shadow: none }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important }
}
