/*
 * wincc_extractor — admin UI stylesheet.
 *
 * Design constraints (see architecture.md §11 + UI spec):
 *   - Dark theme is the only theme. No prefers-color-scheme toggle.
 *   - Every color/spacing/radius is a Mantine token from
 *     /static/mantine-tokens.css — do NOT inline hex values here.
 *   - No external CDN, no @font-face, no @import.
 *   - Desktop-first operator console, but stacks below 900px so a
 *     tablet still works on shift.
 *
 * All historic class names are preserved (`.card`, `.btn`, `.badge`,
 * `.grid`, `.modal`, `.sidebar`, `.topbar`, `.auth-shell`, `.field`,
 * `.alert`); only their visual rendering is re-skinned onto the
 * Mantine palette. New shared patterns (.filters, .filters-actions,
 * .toolbar, .toolbar-actions, .form-inline, .notice) are appended.
 */

/* --------------------------------------------------------------- reset */

*,
*::before,
*::after {
  box-sizing: border-box;
}

/* Tell the browser the UI is dark — flips native form widgets
 * (date/datetime pickers, scroll bars, autofill highlight) to the
 * dark variant in Chromium/Edge/Firefox. */
:root { color-scheme: dark; }

html,
body {
  margin: 0;
  padding: 0;
  min-height: 100%;
}

body {
  background: var(--mantine-color-body);
  color: var(--mantine-color-text);
  font: 14px/1.5 var(--mantine-font-family);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4 {
  margin: 0 0 12px 0;
  font-weight: 600;
  line-height: 1.25;
}
h1 { font-size: 22px; }
h2 { font-size: 16px; }
h3 { font-size: 14px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--mantine-color-dimmed); }

p { margin: 0 0 12px 0; }

a {
  color: var(--mantine-color-blue-4);
  text-decoration: none;
}
a:hover,
a:focus-visible {
  color: var(--mantine-color-blue-3);
  text-decoration: underline;
}

code, .mono, .text-mono {
  font-family: var(--mantine-font-family-monospace);
}

/* ----------------------------------------------------------- app shell */

/* Two-column grid: 240px sidebar + fluid content. The display:-ms-grid
 * fallback is for IE11; the CSS-grid spec there uses the old syntax,
 * so we provide both -ms-* and the modern equivalents.
 */
.shell {
  display: -ms-grid;
  display: grid;
  -ms-grid-columns: 240px 1fr;
  grid-template-columns: 240px 1fr;
  min-height: 100vh;
}

.sidebar {
  -ms-grid-column: 1;
  grid-column: 1;
  background: var(--mantine-color-chrome);
  border-right: 1px solid var(--mantine-color-default-border);
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  position: sticky;
  top: 0;
}

.sidebar-brand {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 16px 16px;
  border-bottom: 1px solid var(--mantine-color-default-border);
  color: var(--mantine-color-text);
  font-weight: 600;
  letter-spacing: 0.01em;
}
.sidebar-brand svg,
.sidebar-brand .tabler-icon { color: var(--mantine-color-blue-4); flex-shrink: 0; }

.sidebar-nav {
  flex: 1;
  padding: 8px 0;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
}
/* Mantine NavLink — flat, no left-border accent. Hover dims to the
 * neutral hover surface; the active state uses the blue-light tint so
 * the row reads as "selected" at a glance, with both the icon and
 * label promoted to blue-4. */
.sidebar-nav .nav-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 16px;
  margin: 1px 8px;
  border-radius: var(--mantine-radius-sm);
  color: var(--mantine-color-dimmed);
  transition: background-color 120ms ease, color 120ms ease;
}
.sidebar-nav .nav-item:hover {
  background: var(--mantine-color-default-hover);
  color: var(--mantine-color-text);
  text-decoration: none;
}
.sidebar-nav .nav-item.active {
  background: var(--mantine-color-blue-light);
  color: var(--mantine-color-blue-4);
}
.sidebar-nav .nav-item.active .tabler-icon,
.sidebar-nav .nav-item.active svg {
  color: var(--mantine-color-blue-4);
}
.sidebar-nav .nav-item svg,
.sidebar-nav .nav-item .tabler-icon { flex-shrink: 0; }

.sidebar-section { display: flex; flex-direction: column; }
.sidebar-section + .sidebar-section { margin-top: 12px; }
.sidebar-section-title {
  padding: 10px 16px 6px;
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--mantine-color-dark-3);
}

.sidebar-footer {
  padding: 12px 16px;
  border-top: 1px solid var(--mantine-color-default-border);
  font-size: 11px;
}

.content {
  -ms-grid-column: 2;
  grid-column: 2;
  display: flex;
  flex-direction: column;
  min-width: 0; /* allow children to shrink inside grid track */
}

.topbar {
  height: 56px;
  border-bottom: 1px solid var(--mantine-color-default-border);
  background: var(--mantine-color-chrome);
  padding: 0 24px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  position: sticky;
  top: 0;
  z-index: 10;
}
.topbar-left,
.topbar-right {
  display: flex;
  align-items: center;
  gap: 12px;
}

/* Global service-status indicator in the topbar. Carries an explicit
 * label ("Сервис") so the operator does not have to guess what the
 * green dot is about, plus a status word ("работает" / "на паузе"). */
.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 4px 12px;
  border-radius: 999px;
  background: var(--mantine-color-surface-1);
  border: 1px solid var(--mantine-color-default-border);
  font-size: 12px;
  font-weight: 500;
}
/* Timezone picker in the topbar — applies site-wide to all displayed
 * times. Styled on Mantine tokens to match the surrounding chrome. */
.tz-picker {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 8px 3px 10px;
  border-radius: 999px;
  background: var(--mantine-color-surface-1);
  border: 1px solid var(--mantine-color-default-border);
  color: var(--mantine-color-dimmed);
}
.tz-picker-select {
  border: none;
  background: transparent;
  color: var(--mantine-color-text);
  font-size: 12px;
  font-weight: 500;
  padding: 2px 2px;
  cursor: pointer;
  appearance: auto;
}
.tz-picker-select:focus { outline: none; }

/* Active-zone suffix shown after a "Время"/"Час" column header, e.g.
 * "(местное)" / "(UTC)" / "(GMT+6)". Dimmed + normal weight so it reads
 * as a unit hint, not a second header word. */
.th-tz {
  font-weight: 400;
  font-size: 11px;
  color: var(--mantine-color-dimmed);
  white-space: nowrap;
}

.status-pill-label { color: var(--mantine-color-dimmed); }
.status-pill-value { color: var(--mantine-color-text); }
.status-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}
.status-dot-ok   { background: var(--mantine-color-green-6); box-shadow: 0 0 0 3px rgba(64, 192, 87, 0.18); }
.status-dot-warn { background: var(--mantine-color-yellow-6); box-shadow: 0 0 0 3px rgba(250, 176, 5, 0.18); }
.status-dot-crit,
.status-dot-err  { background: var(--mantine-color-red-6); box-shadow: 0 0 0 3px rgba(250, 82, 82, 0.18); }
.status-dot-idle { background: var(--mantine-color-default-border); box-shadow: 0 0 0 3px rgba(120, 120, 120, 0.15); }

/* ============================================================
   Status pills — shared between /agents cards and the modal.
   ============================================================ */
.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: 0.2px;
  text-transform: lowercase;
  background: var(--mantine-color-default);
  white-space: nowrap;
}
.status-pill .status-dot { display: inline-block; width: 6px; height: 6px; box-shadow: none; }
.status-pill-ok   { color: var(--mantine-color-green-4); background: rgba(64, 192, 87, 0.14); }
.status-pill-err  { color: var(--mantine-color-red-4);   background: rgba(250, 82, 82, 0.16); }
.status-pill-idle { color: var(--mantine-color-dimmed); background: rgba(120, 120, 120, 0.12); }

/* ============================================================
   /agents — card-list layout.

   Each agent is a horizontal card with:
   - 3px left-edge stripe coloured by data-status (live state)
   - identity row: status pill + name (bold) + endpoint (mono muted)
     + right-aligned action group
   - meta row: label/value pairs (version, last seen, notes)
   - error row (only when status=error): full-width red callout

   Hover lifts the border + tints background — Linear/Vercel idiom.
   ============================================================ */
.agent-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.agent-card {
  position: relative;
  display: flex;
  background: var(--mantine-color-surface-2, var(--mantine-color-default));
  border: 1px solid var(--mantine-color-default-border);
  border-radius: 10px;
  overflow: hidden;
  transition: border-color 120ms ease, background 120ms ease, transform 120ms ease;
}
.agent-card:hover {
  border-color: var(--mantine-color-default-border-strong, var(--mantine-color-blue-7));
  background: var(--mantine-color-surface-3, rgba(255, 255, 255, 0.02));
}
.agent-card-stripe {
  flex: 0 0 3px;
  background: var(--mantine-color-default-border);
}
.agent-card[data-status="online"] .agent-card-stripe { background: var(--mantine-color-green-6); }
.agent-card[data-status="error"]  .agent-card-stripe { background: var(--mantine-color-red-6); }
.agent-card[data-enabled="0"] .agent-card-stripe     { background: var(--mantine-color-yellow-6); }

.agent-card-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 14px 18px;
}
.agent-card-row {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}
.agent-card-identity {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 1;
  min-width: 0;
  flex-wrap: wrap;
}
.agent-name {
  font-size: 15px;
  font-weight: 600;
  color: var(--mantine-color-text);
  letter-spacing: 0.2px;
}
.agent-endpoint code {
  font-size: 12px;
  color: var(--mantine-color-dimmed);
  background: transparent;
  padding: 0;
}

.agent-card-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}
.actions-divider {
  display: inline-block;
  width: 1px;
  height: 18px;
  background: var(--mantine-color-default-border);
}
.actions-safe .btn-icon:hover       { color: var(--mantine-color-blue-4); }
.actions-destructive .btn-icon:hover { color: var(--mantine-color-yellow-4); }
.actions-destructive .btn-icon.btn-danger:hover { color: var(--mantine-color-red-4); }

.agent-card-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 18px;
  font-size: 12px;
}
.meta-item {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
}
.meta-label {
  color: var(--mantine-color-dimmed);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.meta-value {
  color: var(--mantine-color-text);
}
.meta-value.muted { color: var(--mantine-color-dimmed); }
.meta-value code  {
  font-size: 11px;
  background: var(--mantine-color-default);
  padding: 1px 6px;
  border-radius: 4px;
}
.meta-item-notes {
  flex: 1 1 100%;
  color: var(--mantine-color-dimmed);
  font-style: italic;
}

/* Agent CPU load gauge — a compact track + colour-banded fill that
   grades the host load the way operators expect from a node panel:
   green (normal) < 70% <= amber (high) < 90% <= red (overloaded).
   The band class is set server-side by agentCPUClass; the fill width
   is an inline style (CSP allows 'unsafe-inline' for style-src). */
.meta-item-load { align-items: center; }
.load-gauge { display: inline-flex; align-items: center; gap: 6px; }
.load-gauge-track {
  position: relative;
  width: 84px;
  height: 6px;
  background: var(--mantine-color-default-border);
  border-radius: 999px;
  overflow: hidden;
}
.load-gauge-fill {
  position: absolute;
  inset: 0 auto 0 0;
  min-width: 2px;
  border-radius: 999px;
  background: var(--mantine-color-teal-6, #12b886);
  transition: width 0.3s ease;
}
.load-gauge.load-high .load-gauge-fill { background: var(--mantine-color-yellow-6, #f59f00); }
.load-gauge.load-crit .load-gauge-fill { background: var(--mantine-color-red-6, #fa5252); }
.load-gauge-val {
  color: var(--mantine-color-text);
  font-variant-numeric: tabular-nums;
  min-width: 30px;
}

.agent-card-error {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  margin-top: 4px;
  border-radius: 6px;
  background: rgba(250, 82, 82, 0.08);
  border-left: 3px solid var(--mantine-color-red-6);
  font-size: 12px;
}
.agent-card-error-icon { color: var(--mantine-color-red-4); display: inline-flex; }
.agent-card-error-text { color: var(--mantine-color-red-3); }

.agent-card-noscript {
  display: flex;
  gap: 6px;
  padding: 10px 18px;
  border-top: 1px solid var(--mantine-color-default-border);
}

.main {
  flex: 1;
  padding: 28px 32px;
  overflow-y: auto;
}

.page-header,
.page-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 20px;
}
.page-header h1,
.page-head h1 { font-size: 22px; font-weight: 600; margin: 0 0 4px; line-height: 1.2; }
.page-header .actions,
.page-head-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}
.page-head-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.page-sub { color: var(--mantine-color-dimmed); margin: 0 0 20px; font-size: 14px; }
.page-header .page-sub,
.page-head .page-sub { margin: 0; }

/* ------------------------------------------------------------- cards */

/* Mantine "Paper" with sm shadow — flat in the resting state, the
 * shadow gives the panel just enough lift to separate from the body.
 * Hover lift is intentionally NOT applied to static cards; only
 * clickable Mantine Cards get a transform on hover. */
.card {
  background: var(--mantine-color-surface-1);
  border: 1px solid var(--mantine-color-default-border);
  border-radius: var(--mantine-radius-md);
  padding: 20px 24px;
  margin-bottom: 16px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
}
.card.compact { padding: 12px 16px; }

.card-head,
.card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding-bottom: 12px;
  margin-bottom: 16px;
  border-bottom: 1px solid var(--mantine-color-default-border);
}
.card-head h2,
.card-header h2 {
  margin: 0;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 15px;
  font-weight: 600;
}
.card-head-actions { display: flex; gap: 8px; align-items: center; }
.card-body { display: block; }

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 16px;
  margin-bottom: 16px;
}

.metric {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
/* Two label conventions live side-by-side: `.metric .value/.label` was
 * the v1 shape and `.metric-label / .metric-value / .metric-detail` is
 * what newer templates (status.html) emit. Style both. */
.metric .value,
.metric-value {
  font-size: 24px;
  font-weight: 600;
  color: var(--mantine-color-text);
  font-family: var(--mantine-font-family-monospace);
  line-height: 1.2;
}
.metric .label,
.metric-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--mantine-color-dimmed);
  font-weight: 600;
}
.metric-detail { font-size: 12px; color: var(--mantine-color-dimmed); }
.metric .delta { font-size: 12px; color: var(--mantine-color-dimmed); }
.metric .delta.up   { color: var(--mantine-color-green-4); }
.metric .delta.down { color: var(--mantine-color-red-4); }
/* Pill badges sit a bit larger inside metric cards so they read as the
 * primary status indicator rather than as inline meta. */
.metric .badge { font-size: 12px; padding: 3px 12px; }

/* ------------------------------------------------------------ tables */

.table,
.data-table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  background: var(--mantine-color-surface-1);
  border: 1px solid var(--mantine-color-default-border);
  border-radius: var(--mantine-radius-md);
  overflow: hidden;
}
.table th,
.table td,
.data-table th,
.data-table td {
  text-align: left;
  vertical-align: middle;
}
.table th,
.data-table th {
  position: sticky;
  top: 0;
  background: var(--mantine-color-surface-1);
  color: var(--mantine-color-dimmed);
  text-transform: uppercase;
  font-size: 11px;
  letter-spacing: 0.08em;
  padding: 10px 16px;
  border-bottom: 1px solid var(--mantine-color-default-border);
  font-weight: 600;
}
.table td,
.data-table td {
  padding: 12px 16px;
  border-bottom: 1px solid var(--mantine-color-default-border);
  font-size: 14px;
}
.table tbody tr:last-child td,
.data-table tbody tr:last-child td { border-bottom: 0; }
/* Subtle row hover — Mantine uses the next surface up, not the
 * default-hover token which we keep for buttons. */
.table tbody tr:hover td,
.data-table tbody tr:hover td { background: var(--mantine-color-surface-2); }
.table .text-mono,
.data-table .mono { font-family: var(--mantine-font-family-monospace); }
.table .col-actions,
.data-table .col-actions { text-align: right; white-space: nowrap; }
.table .col-num,
.data-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.break-all { word-break: break-all; }

tr.stale td { color: var(--mantine-color-red-4); }
tr.empty-row td,
.empty-state { color: var(--mantine-color-dimmed); }
.empty-state {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 16px;
}

/* legacy bare-<table> styling — kept so unstyled page snippets do not
 * collapse before we migrate every template to .table. */
table:not(.table):not(.data-table) {
  width: 100%;
  border-collapse: collapse;
  background: var(--mantine-color-surface-1);
  border: 1px solid var(--mantine-color-default-border);
}
table:not(.table):not(.data-table) th,
table:not(.table):not(.data-table) td {
  padding: 6px 10px;
  border-bottom: 1px solid var(--mantine-color-default-border);
  text-align: left;
}
table:not(.table):not(.data-table) th { background: var(--mantine-color-default-hover); font-weight: 600; }

/* -------------------------------------------------------------- forms */

.form {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.form label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--mantine-color-dimmed);
  font-weight: 600;
}
.form-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 12px;
}

/* Mantine TextInput / NumberInput / Select / Textarea baseline.
 * The selector list is exhaustive so HTML <input> defaults don't leak
 * (without an explicit type attribute, Chrome treats <input> as text;
 * we also catch that via `input:not([type])`). */
input[type="text"],
input[type="password"],
input[type="number"],
input[type="search"],
input[type="email"],
input[type="url"],
input[type="tel"],
input[type="datetime-local"],
input[type="date"],
input[type="time"],
input[type="month"],
input[type="week"],
input:not([type]),
select,
textarea {
  background: var(--mantine-color-surface-3);
  border: 1px solid var(--mantine-color-default-border);
  border-radius: var(--mantine-radius-sm);
  padding: 8px 12px;
  color: var(--mantine-color-text);
  font-family: inherit;
  font-size: 14px;
  line-height: 1.4;
  width: 100%;
  transition: border-color 120ms ease, box-shadow 120ms ease;
}
input[type="text"]:focus,
input[type="password"]:focus,
input[type="number"]:focus,
input[type="search"]:focus,
input[type="email"]:focus,
input[type="url"]:focus,
input[type="tel"]:focus,
input[type="datetime-local"]:focus,
input[type="date"]:focus,
input[type="time"]:focus,
input[type="month"]:focus,
input[type="week"]:focus,
input:not([type]):focus,
select:focus,
textarea:focus {
  border-color: var(--mantine-color-blue-6);
  outline: 0;
  box-shadow: 0 0 0 2px rgba(77, 171, 247, 0.25);
}
input::placeholder,
textarea::placeholder { color: var(--mantine-color-dark-3); opacity: 1; }
input:disabled,
select:disabled,
textarea:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}
/* Modal inputs were styled twice (here + .modal .field input). Let the
 * global rule above win; the modal-specific block only contributes
 * spacing tweaks. */

/* ------------------------------------------------------------ buttons */

/* Mantine Button variants — default / filled / light / outline / subtle.
 *
 *   .btn               → Mantine "default" (surface-3 fill, 1px border).
 *   .btn-primary       → "filled" (blue-7 fill, no border).
 *   .btn-danger        → "filled" red (red-8 fill, no border).
 *   .btn-light         → "light" (translucent blue tint, blue-4 text).
 *   .btn-outline       → "outline" (transparent, blue-7 border).
 *   .btn-subtle/ghost  → "subtle" (transparent until hover).
 *
 * All variants share the focus ring (2px blue-4 @ 35% alpha) and the
 * disabled treatment (0.5 opacity, no hover). Icons + label are
 * separated by 8px (`gap`); Tabler outline glyphs render at the size
 * the template passes to safeIcon. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 7px 14px;
  border-radius: var(--mantine-radius-sm);
  cursor: pointer;
  font-family: inherit;
  font-size: 14px;
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
  text-decoration: none;
  background: var(--mantine-color-default);
  color: var(--mantine-color-text);
  border: 1px solid var(--mantine-color-default-border);
  transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease, box-shadow 120ms ease;
}
.btn:hover:not(:disabled) {
  background: var(--mantine-color-default-hover);
  color: var(--mantine-color-text);
}
.btn:focus-visible {
  outline: 0;
  box-shadow: 0 0 0 2px rgba(77, 171, 247, 0.35);
}
.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.btn .tabler-icon { flex-shrink: 0; }

.btn-primary {
  background: var(--mantine-color-blue-7);
  color: #fff;
  border-color: transparent;
}
.btn-primary:hover:not(:disabled) {
  background: var(--mantine-color-blue-8);
  border-color: transparent;
  color: #fff;
}
.btn-primary:active:not(:disabled) {
  background: var(--mantine-color-blue-9);
}

.btn-secondary {
  background: transparent;
  border: 1px solid var(--mantine-color-default-border);
  color: var(--mantine-color-text);
}
.btn-secondary:hover:not(:disabled) {
  border-color: var(--mantine-color-default-border);
  color: var(--mantine-color-text);
  background: var(--mantine-color-default-hover);
}

.btn-danger {
  background: var(--mantine-color-red-8);
  color: #fff;
  border-color: transparent;
}
.btn-danger:hover:not(:disabled) {
  background: #c92a2a; /* Mantine red-9 */
  border-color: transparent;
  color: #fff;
}
.btn-danger:active:not(:disabled) {
  background: #b02525;
}

/* Mantine "light" variant — translucent tint background, accented
 * text. Used for secondary actions that need to feel branded but not
 * shout (e.g. "Пауза" next to a filled primary). */
.btn-light {
  background: var(--mantine-color-blue-light);
  color: var(--mantine-color-blue-4);
  border-color: transparent;
}
.btn-light:hover:not(:disabled) {
  background: rgba(17, 70, 120, 0.55);
  color: var(--mantine-color-blue-4);
  border-color: transparent;
}

.btn-outline {
  background: transparent;
  color: var(--mantine-color-blue-4);
  border: 1px solid var(--mantine-color-blue-7);
}
.btn-outline:hover:not(:disabled) {
  background: var(--mantine-color-blue-light);
  color: var(--mantine-color-blue-4);
  border-color: var(--mantine-color-blue-7);
}

.btn-subtle,
.btn-ghost {
  background: transparent;
  border-color: transparent;
  color: var(--mantine-color-blue-4);
}
.btn-subtle:hover:not(:disabled),
.btn-ghost:hover:not(:disabled) {
  background: var(--mantine-color-blue-light);
  color: var(--mantine-color-blue-4);
  border-color: transparent;
}
/* Legacy .btn-ghost rendered grey-on-grey; the new shape uses the
 * subtle variant, but the topbar logout button looks loud as blue.
 * Allow consumers to opt back into the dimmed look with .btn-ghost
 * inside the topbar. */
.topbar .btn-ghost {
  color: var(--mantine-color-dimmed);
}
.topbar .btn-ghost:hover:not(:disabled) {
  background: var(--mantine-color-default-hover);
  color: var(--mantine-color-text);
}

.btn-sm { padding: 4px 10px; font-size: 12px; }
.btn-xs { padding: 2px 8px; font-size: 11px; line-height: 1.4; }
.btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  background: transparent;
  border: 1px solid transparent;
  color: var(--mantine-color-dimmed);
  cursor: pointer;
  border-radius: var(--mantine-radius-sm);
  transition: background-color 120ms ease, color 120ms ease, box-shadow 120ms ease;
}
.btn-icon:hover { color: var(--mantine-color-text); background: var(--mantine-color-default-hover); }
.btn-icon:focus-visible {
  outline: 0;
  box-shadow: 0 0 0 2px rgba(77, 171, 247, 0.35);
}

/* unscoped <button> — keep the old surface working until every page is
 * migrated to .btn. Bare buttons get the secondary look. */
button:not(.btn):not(.btn-icon) {
  background: transparent;
  color: var(--mantine-color-text);
  border: 1px solid var(--mantine-color-default-border);
  padding: 6px 12px;
  border-radius: var(--mantine-radius-sm);
  cursor: pointer;
  font-family: inherit;
  font-size: 13px;
}
button:not(.btn):not(.btn-icon):hover:not(:disabled) {
  border-color: var(--mantine-color-blue-6);
  background: var(--mantine-color-default-hover);
}
button:not(.btn):not(.btn-icon):focus-visible {
  outline: 0;
  box-shadow: 0 0 0 2px rgba(77, 171, 247, 0.35);
}

/* ----------------------------------------------------------- badges */

/* Mantine "light" Badge — pill-shaped, tinted background, accented
 * text and a hair-line border that picks up the same hue. Reads as a
 * status chip rather than a coloured rectangle. */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 10px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border: 1px solid transparent;
  white-space: nowrap;
}
.badge .tabler-icon { flex-shrink: 0; }

.badge-success,
.badge-ok {
  color: var(--mantine-color-green-4);
  background: var(--mantine-color-green-light);
  border-color: rgba(81, 207, 102, 0.25);
}
.badge-warn {
  color: var(--mantine-color-yellow-4);
  background: var(--mantine-color-yellow-light);
  border-color: rgba(255, 212, 59, 0.25);
}
.badge-error,
.badge-crit {
  color: var(--mantine-color-red-4);
  background: var(--mantine-color-red-light);
  border-color: rgba(255, 135, 135, 0.25);
}
.badge-info {
  color: var(--mantine-color-blue-4);
  background: var(--mantine-color-blue-light);
  border-color: rgba(77, 171, 247, 0.25);
}
.badge-muted {
  color: var(--mantine-color-dimmed);
  background: var(--mantine-color-default);
  border-color: var(--mantine-color-default-border);
}

/* Severity badge aliases used by alarms.html (badge-sev-critical etc.). */
.badge-sev-critical {
  color: var(--mantine-color-red-4);
  background: var(--mantine-color-red-light);
  border-color: rgba(255, 135, 135, 0.25);
}
.badge-sev-warning {
  color: var(--mantine-color-yellow-4);
  background: var(--mantine-color-yellow-light);
  border-color: rgba(255, 212, 59, 0.25);
}
.badge-sev-info {
  color: var(--mantine-color-blue-4);
  background: var(--mantine-color-blue-light);
  border-color: rgba(77, 171, 247, 0.25);
}
.alarm-sev-critical td { border-left: 3px solid var(--mantine-color-red-6); }
.alarm-sev-warning  td { border-left: 3px solid var(--mantine-color-yellow-6); }
.alarm-sev-info     td { border-left: 3px solid var(--mantine-color-blue-6); }

/* ---------------------------------------------------------- toggle */

/* Switch-style toggle. The <input> is visually hidden but
 * keyboard-reachable; the .toggle-track is the visible pill. */
.toggle {
  position: relative;
  display: inline-block;
  width: 40px;
  height: 22px;
}
.toggle input {
  opacity: 0;
  width: 0;
  height: 0;
  position: absolute;
}
.toggle-track {
  position: absolute;
  inset: 0;
  background: var(--mantine-color-default-border);
  border-radius: 12px;
  transition: background 120ms ease;
  cursor: pointer;
}
.toggle-track::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 2px;
  width: 18px;
  height: 18px;
  background: var(--mantine-color-text);
  border-radius: 50%;
  transition: transform 120ms ease;
}
.toggle input:checked + .toggle-track { background: var(--mantine-color-green-6); }
.toggle input:checked + .toggle-track::before { transform: translateX(18px); }
.toggle input:focus-visible + .toggle-track {
  box-shadow: 0 0 0 2px rgba(34, 139, 230, 0.40);
}

/* -------------------------------------------------------- alerts */

.alert {
  padding: 10px 14px;
  border-radius: var(--mantine-radius-sm);
  margin-bottom: 12px;
  font-size: 13px;
  border: 1px solid transparent;
}
.alert-error   { background: var(--mantine-color-red-light); color: var(--mantine-color-red-4); border-color: var(--mantine-color-red-8); }
.alert-warn    { background: var(--mantine-color-yellow-light); color: var(--mantine-color-yellow-6); border-color: var(--mantine-color-yellow-8); }
.alert-success { background: var(--mantine-color-green-light); color: var(--mantine-color-green-4); border-color: var(--mantine-color-green-8); }
.alert-info    { background: var(--mantine-color-blue-light); color: var(--mantine-color-blue-4); border-color: var(--mantine-color-blue-8); }

/* ----------------------------------------------------- auth pages */

.auth-shell {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--mantine-color-body);
  padding: 24px;
}
.auth-card {
  width: 360px;
  max-width: 100%;
  padding: 32px;
  background: var(--mantine-color-surface-1);
  border: 1px solid var(--mantine-color-default-border);
  border-radius: var(--mantine-radius-md);
}
.auth-card form { display: flex; flex-direction: column; gap: 12px; }
.auth-card label { display: flex; flex-direction: column; gap: 4px; font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--mantine-color-dimmed); font-weight: 600; }
.auth-logo {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  margin-bottom: 24px;
}
.auth-title {
  font-size: 24px;
  font-weight: 600;
  margin-top: 8px;
  color: var(--mantine-color-text);
}
.auth-subtitle { font-size: 12px; }
.auth-submit  { margin-top: 8px; }

/* legacy login-page hook used by older login.html — kept aliased to
 * the new .auth-shell so reverting either layout still renders. */
.login-page main { display: grid; place-items: center; min-height: 100vh; }
.login-card {
  background: var(--mantine-color-surface-1);
  border: 1px solid var(--mantine-color-default-border);
  padding: 24px;
  min-width: 320px;
  border-radius: var(--mantine-radius-sm);
}
.login-card form { display: grid; gap: 8px; }

/* -------------------------------------------------------- toast */

#toast {
  position: fixed;
  bottom: 16px;
  right: 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  z-index: 300; /* above modals (200) so the toast is never hidden */
}

/* HTML5 [hidden] attribute MUST always win over class-driven `display`
 * values — without this, .btn { display: inline-flex } overrides the
 * wizard's `hidden` toggle on Назад/Далее/Создать. */
[hidden] { display: none !important; }

/* Visible feedback for an in-flight or disabled button. Without this
 * the operator does not see that the click was registered and may
 * click again while the request is still travelling. Pair with the
 * data-busy guard in app.js — the guard prevents the second request,
 * this rule makes the prevention visible. */
button:disabled,
button[disabled],
.btn:disabled,
.btn[disabled],
.btn.is-busy,
button.is-busy {
  opacity: 0.55 !important;
  cursor: progress !important;
  pointer-events: none !important;  /* defence-in-depth vs. the guard */
}

/* ---- Settings page utilities ------------------------------------------- */
.form-narrow { max-width: 480px; }
.code-block {
  background: var(--mantine-color-chrome);
  border: 1px solid var(--mantine-color-default-border);
  border-radius: var(--mantine-radius-sm);
  padding: 12px;
  overflow-x: auto;
}
.danger-zone-card { border-color: var(--mantine-color-red-8); }
.danger-zone-card .card-header { border-bottom-color: var(--mantine-color-red-8); }
.danger-zone-card .card-header h2 { color: var(--mantine-color-red-4); }
.action-row-wrap { flex-wrap: wrap; }

/* ---- Logs viewer (/logs page) ----------------------------------------- */
.logs-tabs { display: flex; gap: 6px; align-items: center; }
.logs-tabs .logs-tab.active {
  background: var(--mantine-color-blue-7);
  color: #fff;
  border-color: transparent;
}
.logs-autoreload {
  display: inline-flex; align-items: center; gap: 6px;
  color: var(--mantine-color-dimmed);
  font-size: 12px;
}
.field-inline-row {
  display: inline-flex !important;
  flex-direction: row !important;
  align-items: center !important;
  gap: 6px !important;
}
.field-inline-row select { padding: 4px 8px; font-size: 12px; }
.logs-meta {
  padding: 4px 0 8px;
  font-size: 11px;
}
.logs-search { margin-bottom: 8px; }
.logs-search input {
  width: 100%;
}
.logs-pane {
  background: var(--mantine-color-chrome);
  border: 1px solid var(--mantine-color-default-border);
  border-radius: var(--mantine-radius-sm);
  padding: 12px;
  max-height: 70vh;
  overflow-y: auto;
  margin: 0;
  font-family: var(--mantine-font-family-monospace);
  font-size: 12px;
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-word;
}
.log-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px;
  padding: 2px 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.03);
}
.log-ts { color: var(--mantine-color-dimmed); flex-shrink: 0; }
.log-lvl {
  flex-shrink: 0;
  font-weight: 600;
  font-size: 10px;
  padding: 1px 6px;
  border-radius: 3px;
  letter-spacing: 0.04em;
}
.log-lvl-debug { color: var(--mantine-color-dimmed); background: var(--mantine-color-default); }
.log-lvl-info  { color: var(--mantine-color-blue-4);  background: var(--mantine-color-blue-light); }
.log-lvl-warn,
.log-lvl-warning { color: var(--mantine-color-yellow-4); background: var(--mantine-color-yellow-light); }
.log-lvl-error,
.log-lvl-err   { color: var(--mantine-color-red-4);  background: var(--mantine-color-red-light); }
.log-lvl-text  { color: var(--mantine-color-dimmed); background: transparent; }
.log-msg { color: var(--mantine-color-text); flex: 1 1 200px; }
.log-row-error .log-msg,
.log-row-err .log-msg { color: var(--mantine-color-red-4); }
.log-row-warn .log-msg,
.log-row-warning .log-msg { color: var(--mantine-color-yellow-4); }
.log-kv {
  font-size: 11px;
  color: var(--mantine-color-dimmed);
  background: var(--mantine-color-default);
  padding: 1px 6px;
  border-radius: 3px;
  white-space: nowrap;
}
.log-empty {
  color: var(--mantine-color-dimmed);
  font-style: italic;
  padding: 12px 0;
}
.toast {
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--mantine-color-surface-1);
  border: 1px solid var(--mantine-color-default-border);
  border-left-width: 3px;
  border-radius: var(--mantine-radius-sm);
  padding: 10px 14px;
  color: var(--mantine-color-text);
  font-size: 13px;
  min-width: 280px;
  max-width: 480px;
  box-shadow: var(--mantine-shadow-md);
}
.toast .toast-icon {
  flex: 0 0 20px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.toast .toast-body {
  flex: 1 1 auto;
  line-height: 1.4;
  word-break: break-word;
}
.toast .toast-close {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  color: var(--mantine-color-dimmed);
  font-size: 14px;
  cursor: pointer;
  border-radius: 4px;
  padding: 0;
}
.toast .toast-close:hover { background: var(--mantine-color-default-hover); color: var(--mantine-color-text); }
.toast.success, .toast.ok    { border-left-color: var(--mantine-color-green-6); }
.toast.success .toast-icon,
.toast.ok      .toast-icon   { color: var(--mantine-color-green-4); }
.toast.warn                  { border-left-color: var(--mantine-color-yellow-6); }
.toast.warn    .toast-icon   { color: var(--mantine-color-yellow-4); }
.toast.error, .toast.crit    { border-left-color: var(--mantine-color-red-6); }
.toast.error   .toast-icon,
.toast.crit    .toast-icon   { color: var(--mantine-color-red-4); }

/* -------------------------------------------------------- modal */

.modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
}
.modal[aria-hidden="true"] { display: none; }
.modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  cursor: pointer;
}
.modal-panel,
.modal-head,
.modal-foot {
  background: var(--mantine-color-surface-1);
}
.modal-panel {
  position: relative;
  border: 1px solid var(--mantine-color-default-border);
  border-radius: var(--mantine-radius-md);
  min-width: 360px;
  max-width: 720px;
  width: 92%;
  max-height: 92vh;
  /* Vertical scroll only. A horizontal scrollbar here means some
     child overflowed (long code token, wide grid) — clip it and let
     the wrap rules below reflow the content instead. */
  overflow-x: hidden;
  overflow-y: auto;
  box-shadow: var(--mantine-shadow-lg);
}
/* Wider variant for content that benefits from horizontal room — e.g.
   the agent log viewer, where wrapping every line would shred
   readability. */
.modal-panel-wide { max-width: 960px; }
/* Header action cluster (refresh + close) on modals that carry more
   than a lone close button. */
.modal-header-actions { display: flex; align-items: center; gap: 8px; }
/* Agent log viewer: a scrollable monospace pane. Lines wrap so a long
   path or stack frame never forces a horizontal scrollbar, and the
   pane caps its height so the modal stays on-screen on the tail. */
.log-view {
  margin: 0;
  max-height: 60vh;
  overflow: auto;
  padding: 12px 14px;
  background: var(--mantine-color-surface-0, #11151c);
  border: 1px solid var(--mantine-color-default-border);
  border-radius: var(--mantine-radius-sm);
  font-family: var(--mantine-font-family-monospace, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
  font-size: 12px;
  line-height: 1.5;
  color: var(--mantine-color-text, #e6e8eb);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  tab-size: 4;
}

/* Agent log viewer — per-line severity colouring (mirrors the agent's LeveledLogWriter
   classification) so an operator SEES problems (red) vs normal (green/neutral) at a glance
   instead of a monochrome wall. */
.log-view .log-line  { display: block; padding-left: 8px; border-left: 2px solid transparent; }
.log-view .log-error { color: var(--mantine-color-red-4, #ff8787); border-left-color: var(--mantine-color-red-6, #fa5252); background: rgba(250, 82, 82, 0.07); }
.log-view .log-warn  { color: var(--mantine-color-yellow-4, #ffd43b); border-left-color: var(--mantine-color-yellow-6, #fab005); }
.log-view .log-ok    { color: var(--mantine-color-green-4, #69db7c); border-left-color: var(--mantine-color-green-7, #2f9e44); }
.log-view .log-debug { color: var(--mantine-color-dimmed, #909296); }
.log-view .log-info  { color: var(--mantine-color-text, #e6e8eb); }

/* Agent load-history (stats) modal — segmented day/week toggle + two
   inline-SVG line charts (CPU, RAM). No chart library; the path is
   built in app.js. */
.seg { display: inline-flex; border: 1px solid var(--mantine-color-default-border); border-radius: var(--mantine-radius-sm); overflow: hidden; }
.seg-btn {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--mantine-color-dimmed);
  font: inherit;
  font-size: 12px;
  padding: 4px 12px;
  cursor: pointer;
}
.seg-btn + .seg-btn { border-left: 1px solid var(--mantine-color-default-border); }
.seg-btn.is-active {
  background: var(--mantine-color-teal-6, #12b886);
  color: var(--mantine-color-white, #fff);
  font-weight: 600;
  box-shadow: inset 0 0 0 1px var(--mantine-color-teal-4, #38d9a9);
}
.stats-block { margin-bottom: 16px; }
.stats-block:last-of-type { margin-bottom: 0; }
.stats-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 4px; }
.stats-cur { font-size: 13px; color: var(--mantine-color-text); font-variant-numeric: tabular-nums; }
.stats-chart {
  display: block;
  width: 100%;
  height: 120px;
  background: var(--mantine-color-surface-0, #11151c);
  border: 1px solid var(--mantine-color-default-border);
  border-radius: var(--mantine-radius-sm);
}
.stats-line { fill: none; stroke: var(--mantine-color-teal-5, #20c997); stroke-width: 1.5; vector-effect: non-scaling-stroke; }
.stats-axis { stroke: var(--mantine-color-default-border); stroke-width: 1; vector-effect: non-scaling-stroke; }
.stats-grid { stroke: var(--mantine-color-default-border); stroke-width: 1; stroke-dasharray: 3 3; opacity: 0.45; vector-effect: non-scaling-stroke; }
.stats-empty { color: var(--mantine-color-dimmed); font-size: 13px; text-align: center; padding: 8px 0; }
.stats-axis-x { display: flex; justify-content: space-between; margin-top: 3px; }
.stats-axis-tick { font-size: 10px; color: var(--mantine-color-dimmed); font-variant-numeric: tabular-nums; white-space: nowrap; }
.stats-chart-row { display: flex; align-items: flex-start; gap: 0; }
.stats-chart-main { flex: 1; min-width: 0; }
.stats-yaxis {
  height: 120px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: flex-end;
  padding: 4px 6px 4px 0;
  min-width: 36px;
}
.stats-yaxis-tick { font-size: 10px; color: var(--mantine-color-dimmed); line-height: 1; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* Per-tag raw-data inspector (modal in layout.html, opened from /live row
   click or /tags "Данные" via components/tagdata.js). */
.tagdata-meta { display: flex; flex-wrap: wrap; gap: 16px; margin-bottom: 12px; font-size: 13px; }
.tagdata-meta .m-label { color: var(--mantine-color-dimmed); font-size: 11px; text-transform: uppercase; letter-spacing: .5px; }
.tagdata-meta .m-val { font-variant-numeric: tabular-nums; }
.tagdata-table-wrap { max-height: 56vh; overflow: auto; border: 1px solid var(--mantine-color-default-border); border-radius: 6px; }
.tagdata-table-wrap table { width: 100%; }
.tagdata-table-wrap th { position: sticky; top: 0; background: var(--mantine-color-surface-1, #1a1f27); z-index: 1; }
.tagdata-table-wrap td, .tagdata-table-wrap th { padding: 3px 10px; font-variant-numeric: tabular-nums; }
.tagdata-chart-block { margin-bottom: 14px; }
.tagdata-chart-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px; }
.tagdata-cats { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-bottom: 8px; }
.tagdata-cats-out { display: inline-flex; flex-wrap: wrap; gap: 6px; font-size: 12px; color: var(--mantine-color-dimmed); }
/* Компактный единый тулбар инспектора: каталоги + период + действия в одну строку
   (замена двух раздельных крупных блоков .tagdata-cats/.tagdata-period). */
.tagdata-toolbar {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
  margin-bottom: 10px; padding: 5px 8px;
  border: 1px solid var(--mantine-color-default-border); border-radius: 6px;
  background: var(--mantine-color-surface-1, #1a1f27);
}
.tagdata-toolbar .tagdata-range-input { width: 168px; min-width: 168px; padding: 2px 5px; font-size: 11px; }
.tagdata-toolbar-sep { width: 1px; align-self: stretch; margin: 2px 2px; background: var(--mantine-color-default-border); }
/* «Запрос по периоду»: dedicated toolbar row — leading label + С/По inputs +
   primary TAG:R / secondary re-collect buttons. Wraps gracefully when narrow. */
.tagdata-period { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; padding: 8px 10px; border: 1px solid var(--mantine-color-default-border); border-radius: 6px; background: var(--mantine-color-surface-1, #1a1f27); }
.tagdata-period-lbl { font-size: 12px; font-weight: 600; color: var(--mantine-color-dimmed); margin-right: 2px; }
.tagdata-range-field { display: inline-flex; align-items: center; gap: 4px; margin: 0; }
.tagdata-range-lbl { font-size: 12px; color: var(--mantine-color-dimmed); }
.tagdata-range-input { width: 190px; min-width: 190px; padding: 4px 6px; font-size: 12px; line-height: 1.3; }
/* Phase 1 / Step B: С/По stay in SOURCE time (never shifted) — small caption next to the
   pickers so the operator doesn't mistake them for the aligned display time. */
.tagdata-period-note { font-size: 11px; color: var(--mantine-color-dimmed); margin-left: auto; }
/* Прямой TAG:R — ВЫДЕЛЕННОЕ окно результата (#tagr-result-modal), поверх
   инспектора. Стиль справочной утилиты Siemens «WinCC Database Example»:
   эхо запроса моноширинно, спиннер на время чтения, скролл-сетка точек,
   пустое состояние и чистое сообщение об ошибке. Только Mantine-токены. */
.tagr-query {
  font-size: 12px;
  color: var(--mantine-color-dimmed);
  padding: 6px 10px;
  margin-bottom: 12px;
  border: 1px solid var(--mantine-color-default-border);
  border-radius: 6px;
  background: var(--mantine-color-surface-0, #11151c);
  overflow-wrap: anywhere;
  word-break: break-word;
}
/* Состояния: загрузка/ошибка центрируются; результат — обычный поток. */
.tagr-state { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; min-height: 160px; text-align: center; }
.tagr-state[hidden], .tagr-result[hidden], .tagr-empty[hidden], .tagr-grid-wrap[hidden] { display: none; }
.tagr-loading-text { font-size: 13px; color: var(--mantine-color-dimmed); }
.tagr-spinner {
  width: 32px;
  height: 32px;
  border: 3px solid var(--mantine-color-default-border);
  border-top-color: var(--mantine-color-teal-5, #20c997);
  border-radius: 50%;
  animation: tagr-spin 0.8s linear infinite;
}
@keyframes tagr-spin { to { transform: rotate(360deg); } }
.tagr-error-icon { color: var(--mantine-color-red-4, #ff8787); }
.tagr-error-text { margin: 0; font-size: 13px; color: var(--mantine-color-text); max-width: 480px; overflow-wrap: anywhere; }
.tagr-summary {
  font-size: 12px;
  color: var(--mantine-color-dimmed);
  font-variant-numeric: tabular-nums;
  margin-bottom: 8px;
}
.tagr-empty { padding: 32px 12px; text-align: center; font-size: 13px; color: var(--mantine-color-dimmed); }
/* Скролл-сетка результата: липкая шапка, ограниченная высота — читабельно
   на несколько сотен строк (скролл внутри модалки). */
.tagr-grid-wrap { max-height: 56vh; overflow: auto; border: 1px solid var(--mantine-color-default-border); border-radius: 6px; }
.tagr-grid { width: 100%; }
.tagr-grid th { position: sticky; top: 0; background: var(--mantine-color-surface-1, #1a1f27); z-index: 1; }
.tagr-grid td, .tagr-grid th { padding: 3px 10px; font-variant-numeric: tabular-nums; }
/* Замер скорости (TAG:R): бенчмарк (#multiread-modal). Двухколоночная раскладка
   (выбор тегов + окно/кнопка). ГЛАВНЫЙ результат — крупное время ответа
   (.mread-headline); таблица результата переиспользует .tagr-grid/
   .tagr-summary/.tagdata-table-wrap. Только Mantine-токены, без фреймворка. */
.mread-layout { display: grid; grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr); gap: 16px; align-items: start; margin-bottom: 12px; }
@media (max-width: 720px) { .mread-layout { grid-template-columns: 1fr; } }
.mread-pick-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; margin-bottom: 6px; }
.mread-pick-list { max-height: 40vh; overflow: auto; border: 1px solid var(--mantine-color-default-border); border-radius: 6px; padding: 6px; display: flex; flex-direction: column; gap: 2px; }
.mread-pick-item { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border-radius: 4px; cursor: pointer; font-size: 12px; }
.mread-pick-item:hover { background: var(--mantine-color-default-hover, rgba(255,255,255,.04)); }
.mread-pick-item input[disabled] + span { opacity: .4; }
.mread-controls { display: flex; flex-direction: column; gap: 10px; }
.mread-window { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.mread-radio { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; cursor: pointer; margin: 0; }
.mread-abs { display: flex; flex-wrap: wrap; gap: 10px; }
.mread-abs[hidden] { display: none; }
.mread-result[hidden], .mread-empty[hidden] { display: none; }
.mread-empty { padding: 14px; text-align: center; color: var(--mantine-color-dimmed); }
/* Крупный заголовок-результат: время ответа TAG:R — смысл замера. */
.mread-headline {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 14px 12px; margin-bottom: 10px;
  border: 1px solid var(--mantine-color-default-border); border-radius: 8px;
  background: var(--mantine-color-surface-1, #1a1f27);
}
.mread-headline-time { display: flex; align-items: baseline; gap: 6px; }
.mread-headline-num {
  font-size: 40px; line-height: 1; font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--mantine-color-teal-4, #38d9a9);
}
.mread-headline-unit { font-size: 18px; color: var(--mantine-color-dimmed); }
.mread-headline-lbl { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--mantine-color-dimmed); }
.cat-chip { font-family: var(--mantine-font-family-monospace, monospace); font-size: 11px; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--mantine-color-default-border); color: var(--mantine-color-text); }
.cat-chip-active { border-color: var(--mantine-color-teal-6, #12b886); color: var(--mantine-color-teal-4, #38d9a9); }
/* Clickable catalog chip (the /sources "Каталог" cell): a button styled
   like the active chip — green + clickable, opens the live catalogs modal. */
button.cat-chip { cursor: pointer; background: transparent; font: inherit; font-size: 11px; }
button.cat-chip.cat-chip-active { background: rgba(18,184,134,.12); }
button.cat-chip:hover { filter: brightness(1.2); }
/* Полнота за период (coverage tab in the tag inspector): verdict badge,
   totals line, per-hour status badges + row tint. Built on Mantine tokens,
   no framework. */
.cov-summary { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; margin-bottom: 8px; }
.cov-verdict { font-size: 12px; font-weight: 600; padding: 3px 10px; border-radius: 999px; border: 1px solid var(--mantine-color-default-border); }
.cov-totals { font-size: 12px; color: var(--mantine-color-dimmed); font-variant-numeric: tabular-nums; }
.cov-badge { font-size: 11px; padding: 1px 8px; border-radius: 999px; border: 1px solid var(--mantine-color-default-border); }
.cov-ok { color: var(--mantine-color-teal-4, #38d9a9); border-color: var(--mantine-color-teal-6, #12b886); }
.cov-bad { color: var(--mantine-color-red-4, #ff8787); border-color: var(--mantine-color-red-6, #fa5252); }
.cov-muted { color: var(--mantine-color-dimmed); }
.cov-warn { color: var(--mantine-color-yellow-4, #ffd43b); border-color: var(--mantine-color-yellow-6, #f08c00); }
.cov-bad-row td { background: rgba(250,82,82,.08); }
.cov-warn-row td { background: rgba(250,176,5,.08); }
.cov-ok-row td:first-child { box-shadow: inset 2px 0 0 var(--mantine-color-teal-6, #12b886); }
/* Разрывы данных («плашка»): inline-строка между точками + сводка над таблицей.
   На токенах Mantine, предупреждающий жёлтый, чтобы НЕ путать со строкой данных. */
.data-gap td {
  background: rgba(250, 176, 5, .10);
  color: var(--mantine-color-yellow-4, #ffd43b);
  font-size: 12px;
  font-style: italic;
  text-align: center;
  border-top: 1px dashed var(--mantine-color-yellow-6, #f08c00);
  border-bottom: 1px dashed var(--mantine-color-yellow-6, #f08c00);
}
.data-gap-icon { color: var(--mantine-color-yellow-6, #f08c00); font-style: normal; }
/* «↻ Проверить» — кнопка на строке разрыва: ставит даты дыры в С/По и шлёт TAG:R. */
.data-gap-check {
  margin-left: 10px;
  padding: 1px 8px;
  font-size: 11px;
  font-style: normal;
  cursor: pointer;
  color: var(--mantine-color-blue-3, #74c0fc);
  background: rgba(34, 139, 230, .12);
  border: 1px solid var(--mantine-color-blue-7, #1971c2);
  border-radius: 4px;
  vertical-align: baseline;
}
.data-gap-check:hover { background: rgba(34, 139, 230, .25); color: #fff; }
/* Честный статус разрыва (по scanned_to-границе): 🟢 проверено / 🟡 в обработке /
   🟠 частично (зарезервирован). Перекрашивают жёлтый дефолт .data-gap td. */
.data-gap.data-gap-verified_empty td {
  background: rgba(18, 184, 134, .10);
  color: var(--mantine-color-teal-4, #38d9a9);
  border-color: var(--mantine-color-teal-6, #12b886);
}
.data-gap.data-gap-in_processing td {
  background: rgba(250, 176, 5, .10);
  color: var(--mantine-color-yellow-4, #ffd43b);
  border-color: var(--mantine-color-yellow-6, #f08c00);
}
.data-gap.data-gap-partial_error td {
  background: rgba(253, 126, 20, .12);
  color: var(--mantine-color-orange-4, #ffa94d);
  border-color: var(--mantine-color-orange-6, #fd7e14);
}
/* Подсветка зон разрыва на графике (rect под линией), статус = тот же. */
.gap-shade-verified_empty { fill: var(--mantine-color-teal-5, #20c997); opacity: .10; }
.gap-shade-in_processing { fill: var(--mantine-color-yellow-5, #fab005); opacity: .10; }
.gap-shade-partial_error { fill: var(--mantine-color-orange-5, #fd7e14); opacity: .10; }
.gap-summary {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 12px; font-weight: 600;
  padding: 3px 10px; margin-bottom: 8px;
  border-radius: 999px; border: 1px solid var(--mantine-color-default-border);
}
.gap-summary.has-gaps {
  color: var(--mantine-color-yellow-4, #ffd43b);
  border-color: var(--mantine-color-yellow-6, #f08c00);
  background: rgba(250, 176, 5, .10);
}
.gap-summary.no-gaps {
  color: var(--mantine-color-dimmed);
  font-weight: 500;
  background: transparent;
}
/* Broken-source-clock note (Phase 1 / Step B): shown in the tag inspector when the
   SOURCE has a known clock skew (wincc_source_telemetry) — every displayed time (table
   cell, chart axis, gap captions) is uniformly shifted by that skew; raw source ts is
   always in the cell tooltip. */
.clock-note {
  /* Компактный ℹ-чип (был широкий баннер): краткий текст, полное объяснение — в
     title (tooltip при наведении). cursor:help подсказывает, что есть подробности. */
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 11px; font-weight: 600; line-height: 1.3;
  padding: 2px 8px; margin-bottom: 8px; border-radius: 10px;
  color: var(--mantine-color-orange-4, #ffa94d);
  border: 1px solid var(--mantine-color-orange-7, #e8590c);
  background: rgba(232, 89, 12, .10);
  cursor: help;
}
/* Вспомогательные временные колонки инспектора — плотнее, чтобы 9 колонок влезали. */
.tagdata-grid { font-size: 12px; }
.tagdata-grid td.mono-dim { color: var(--mantine-color-dimmed); font-size: 11px; }
input.is-readonly { opacity: .75; cursor: default; background: var(--mantine-color-default, rgba(255,255,255,.03)); }
/* Long monospace tokens in field hints (ALGVIEWENU / CC_OS_*R / conn
   strings) must wrap, not push the panel wider than the viewport. */
.modal .field-hint code,
.modal .field-hint {
  overflow-wrap: anywhere;
  word-break: break-word;
}
/* Grid cells must be allowed to shrink below their content width so
   the 2-col layout never forces horizontal overflow. */
.modal .form-grid .field { min-width: 0; }
.modal .form-grid input,
.modal .form-grid select,
.modal .form-grid textarea { max-width: 100%; }
.modal-header,
.modal-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 20px; border-bottom: 1px solid var(--mantine-color-default-border);
}
.modal-header h2,
.modal-head h2 { margin: 0; font-size: 15px; font-weight: 600; }
.modal-footer,
.modal-foot {
  display: flex; gap: 8px; justify-content: flex-end;
  padding: 12px 20px; border-top: 1px solid var(--mantine-color-default-border);
}
.modal form,
.modal-body { padding: 16px 20px; }
.modal .form-grid,
.modal-body.grid-2 { display: grid; gap: 12px 20px; }
.modal-body.grid-2 { grid-template-columns: 1fr 1fr; }
.modal .form-grid-2col { grid-template-columns: 1fr 1fr; }
.modal .field { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.modal .field-wide { grid-column: 1 / -1; }
.modal .field-label { font-size: 12px; color: var(--mantine-color-dimmed); }
.modal .field-error { font-size: 12px; color: var(--mantine-color-red-4); min-height: 14px; }
.modal .field-hint  { font-size: 11px; color: var(--mantine-color-dimmed); line-height: 1.4; }

/* Non-modal field-hint: used by /agents/new and other stacked forms
   that wrap inputs in `<label class="field">` outside a modal. Must
   block-display so the hint sits BELOW the input instead of flowing
   inline next to the next field's label (the original bug on
   /agents/new). */
.field-hint {
  display: block;
  font-size: 11px;
  color: var(--mantine-color-dimmed);
  line-height: 1.4;
  margin-top: 2px;
}
.field-hint code {
  font-size: 10px;
  padding: 1px 4px;
  background: var(--mantine-color-default);
  border-radius: 3px;
}
/* Stand-alone .field (outside the wizard modal) — make it a vertical
   stack so label / input / hint don't tile horizontally. */
.stacked-form .field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
  margin-bottom: var(--mantine-spacing-md);
}
.stacked-form .field-label {
  font-size: 12px;
  color: var(--mantine-color-dimmed);
}
/* Two inputs side-by-side (e.g. Host + Port on /agents/new). Children
   share the same vertical .field block-model so their hints still
   stack BELOW each input. */
.stacked-form .field-row {
  display: flex;
  gap: var(--mantine-spacing-md);
  margin-bottom: var(--mantine-spacing-md);
  align-items: flex-start;
}
.stacked-form .field-row .field { margin-bottom: 0; }
.stacked-form .field-grow { flex: 1 1 auto; min-width: 0; }
.stacked-form .field-port { flex: 0 0 110px; }

/* Version note shown right under the header inside the agent modal —
   tells the operator which wincc_agent.exe build the freshly minted
   bundle matches. */
.modal-version-note {
  margin: 0;
  padding: 8px 20px 0;
  color: var(--mantine-color-dimmed);
  font-size: 12px;
}
.modal-version-note code {
  font-size: 11px;
  padding: 1px 4px;
  background: var(--mantine-color-default);
  border-radius: 3px;
}
.modal .modal-notice {
  margin: 12px 20px 0;
}
.notice-list {
  margin: 4px 0 0;
  padding-left: 20px;
  color: var(--mantine-color-dimmed);
  font-size: var(--mantine-font-size-sm);
}
.notice-list li { margin: 2px 0; }
.notice-list code { font-size: 11px; }

/* Compact 1-line variant of .notice — used as a slim "scoreboard"
   summary at the top of /agents when at least one card is in error
   state. No bullet list inside; details live on the cards below. */
.notice-compact {
  padding: 10px 14px;
  align-items: center;
}
.notice-compact .notice-text { margin: 0; line-height: 1.4; }
/* Modal-form layout for the agent-registration dialog. The grid is
   2-column by default; field-host claims 3 of 4 fractional units and
   field-portcell the remaining 1, so host + port read as a single
   semantic pair instead of one input "missing" off the right edge. */
.modal .modal-form { padding: 0 20px 16px; }
.modal .form-grid {
  display: grid;
  gap: 12px;
}
.modal .form-grid-2col {
  grid-template-columns: 3fr 1fr;
}
/* Source wizard wants two equal columns (3fr 1fr is for the
   agents host|port grid). Balanced fills the wide dialog evenly. */
.modal .form-grid-balanced { grid-template-columns: 1fr 1fr; }
.modal .form-grid-balanced .field-date { max-width: 220px; }
.modal .field-wide { grid-column: 1 / -1; }
.modal .field-host { grid-column: 1 / 2; }
.modal .field-portcell { grid-column: 2 / 3; }

/* Edit mode: the source modal is a 2-step wizard for CREATE, but
   editing an existing row should show everything at once (no
   Далее/Назад dance). form.wizard-flat reveals every step as a
   stacked section; JS hides the progress bar + nav buttons. */
form.wizard-flat .wizard-step { display: block !important; }
form.wizard-flat .wizard-step + .wizard-step {
  margin-top: 8px;
  padding-top: 16px;
  border-top: 1px solid var(--mantine-color-default-border);
}

/* Vertical field stack — for wizard steps with a handful of fields
   where a 2-col grid would leave a ragged empty gap under the
   shorter column. Each field is full-width, one per row. */
.modal .form-stack {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.modal .form-stack .field { min-width: 0; }
/* Date input shouldn't stretch the full modal width — a calendar
   field reads better at a natural ~220px. */
.modal .form-stack .field-date { max-width: 220px; }
/* Checkbox row stays horizontal inside the stack. */
.modal .form-stack .field-checkbox { flex-direction: row; align-items: center; gap: 8px; }
.modal .field-portcell input {
  text-align: center;
  letter-spacing: 0.5px;
  font-variant-numeric: tabular-nums;
}
.modal .field-inline-mini {
  display: inline-flex !important;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  color: var(--mantine-color-dimmed);
  margin-top: 4px;
  cursor: pointer;
}
.modal .field-inline-mini input { width: 14px; height: 14px; }

/* Probe-connection row in the new-source wizard. */
.probe-row {
  display: flex;
  align-items: center;
  gap: var(--mantine-spacing-sm);
  padding: 12px 0 4px;
  flex-wrap: wrap;
}
.probe-status {
  font-size: 13px;
  flex: 1;
  min-width: 200px;
}
.probe-status-info  { color: var(--mantine-color-dimmed); }
.probe-status-ok    { color: var(--mantine-color-green-4); }
.probe-status-error { color: var(--mantine-color-red-4); }
.modal .field-hint code { font-size: 10px; padding: 1px 4px; background: var(--mantine-color-default); border-radius: 3px; }
.modal .field-checkbox { flex-direction: row; align-items: center; gap: 8px; }

/* -- wizard modal ---------------------------------------------------------- */
.modal-panel-wizard { max-width: min(1080px, 94vw); width: 100%; }
.wizard-head { padding: 16px 20px; }
.wizard-head-title { display: flex; align-items: center; gap: var(--mantine-spacing-sm); }
.wizard-head-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px;
  background: var(--mantine-color-green-light);
  color: var(--mantine-color-green-4);
  border-radius: var(--mantine-radius-sm);
}
.wizard-progress {
  display: flex; gap: 6px;
  padding: 0 20px 14px;
}
.wizard-progress-seg {
  flex: 1;
  height: 4px;
  border-radius: 2px;
  background: var(--mantine-color-default);
  transition: background 200ms ease;
}
.wizard-progress-seg.active { background: var(--mantine-color-green-6); }
.wizard-step { display: none; padding: 4px 20px 0; }
.wizard-step.active { display: block; }
.wizard-step-head {
  display: flex; gap: var(--mantine-spacing-sm);
  align-items: flex-start;
  margin-bottom: var(--mantine-spacing-md);
}
.wizard-step-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px;
  background: var(--mantine-color-blue-light);
  color: var(--mantine-color-blue-4);
  border-radius: var(--mantine-radius-sm);
  flex-shrink: 0;
}
.wizard-step-title { margin: 0; font-size: 14px; font-weight: 600; color: var(--mantine-color-text); }
.wizard-step-sub { margin: 2px 0 0; font-size: 12px; color: var(--mantine-color-dimmed); }
.wizard-footer { justify-content: space-between; }
.wizard-footer-nav { display: flex; gap: var(--mantine-spacing-sm); align-items: center; }
.field .req { color: var(--mantine-color-red-4); margin-left: 2px; }

/* Per-source collection-tuning modal (#source-tuning-modal). The 5 optional
   load overrides moved out of the create/edit wizard into their own surface,
   opened from the row's gauge button. The intro line above the field grid. */
.tuning-modal-sub {
  margin: 0 0 var(--mantine-spacing-md);
  font-size: 12px;
  color: var(--mantine-color-dimmed);
  line-height: 1.45;
}

.modal .form-sections { display: flex; flex-direction: column; gap: var(--mantine-spacing-lg); }
.modal .form-section { display: flex; flex-direction: column; gap: var(--mantine-spacing-sm); }
.modal .form-section-title {
  margin: 0;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--mantine-color-dark-3);
  padding-bottom: 6px;
  border-bottom: 1px solid var(--mantine-color-default-border);
}

@media (max-width: 640px) {
  .modal .form-grid-2col { grid-template-columns: 1fr; }
}
.modal-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; }

dialog[role="dialog"] {
  background: var(--mantine-color-surface-1);
  color: var(--mantine-color-text);
  border: 1px solid var(--mantine-color-default-border);
  border-radius: var(--mantine-radius-md);
  padding: 0;
  min-width: 360px;
}

/* -------------------------------------------------------- utility */

.text-muted     { color: var(--mantine-color-dark-3); }
.text-secondary,
.muted          { color: var(--mantine-color-dimmed); }
.text-mono      { font-family: var(--mantine-font-family-monospace); }
.text-right     { text-align: right; }
.text-center    { text-align: center; }
.row-flex       { display: flex; align-items: center; }
.gap-8          { gap: 8px; }
.gap-16         { gap: 16px; }
.mt-16          { margin-top: 16px; }
.mb-24          { margin-bottom: 24px; }
.hidden         { display: none !important; }
.sr-only        { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
.error          { color: var(--mantine-color-red-4); }
.ok             { color: var(--mantine-color-green-4); }
.warn           { color: var(--mantine-color-yellow-6); }

.cursor-nav     { display: flex; gap: 8px; padding: 8px 0; }

/* logout form inside the topbar should not consume layout height. */
.logout-form    { margin: 0; padding: 0; display: inline; }

.action-row     { display: inline-flex; gap: 6px; flex-wrap: wrap; }
.bulk-actions   { display: flex; gap: var(--mantine-spacing-sm); margin-top: var(--mantine-spacing-md); }

/* ----------------------------------------------------- tabler icons */

/* All Tabler outline SVGs are 24x24 viewBox; safeIcon sets width/
 * height inline. Wrapper class lets pages tune color via the parent
 * (e.g. `.notice-icon .tabler-icon { color: var(--blue-4); }`). */
.tabler-icon {
  display: inline-block;
  flex-shrink: 0;
  vertical-align: middle;
}

/* -------------------------------------------------- shared patterns */

/* page filters: a SINGLE inline row of inputs + action buttons.
 *
 * Mantine pattern: filter widgets and their submit live in the same
 * horizontal band so the eye doesn't have to scan across the whole
 * page to find the "Apply" target. Each field has a fixed comfortable
 * width (200-240px); the row wraps to a second line if there are too
 * many fields. The submit button (.filters-actions) sits at the right
 * end of the same flex row.
 *
 * Used by /audit/uplink, /audit, /dead-letter, /uplink, /tags, /alarms. */
.filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mantine-spacing-md);
  align-items: flex-end;
  margin-bottom: var(--mantine-spacing-md);
  background: var(--mantine-color-surface-1);
  border: 1px solid var(--mantine-color-default-border);
  border-radius: var(--mantine-radius-md);
  padding: var(--mantine-spacing-md);
}
.filters .field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
  flex: 0 0 auto;
  width: 220px;
}
.filters .field-wide { width: 320px; }
.filters .field > span {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--mantine-color-dimmed);
  font-weight: 600;
}
.filters-actions {
  display: flex;
  gap: var(--mantine-spacing-sm);
  align-items: flex-end;
  margin-left: auto;       /* push to the right edge of the same row */
  padding-bottom: 1px;     /* aligns the button baseline with inputs */
}
/* When .filters-actions is rendered as a sibling of .filters (legacy
 * markup pattern) instead of nested inside it, restore the old behavior:
 * full-width row, right-aligned. */
.filters + .filters-actions {
  margin: calc(-1 * var(--mantine-spacing-md)) 0 var(--mantine-spacing-md);
  padding: 0;
  justify-content: flex-end;
}

/* Legacy alias: .filters used by audit_uplink.html historically wraps
 * its grid in a child .filter-grid + .filter-actions. Keep both working
 * by mapping the legacy class names onto the new tokens. */
.filter-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 240px));
  gap: var(--mantine-spacing-md);
  align-items: end;
}
.filter-actions {
  display: flex;
  gap: var(--mantine-spacing-sm);
  justify-content: flex-end;
  margin-top: var(--mantine-spacing-md);
}

/* page toolbar: title + action buttons inline. */
.toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--mantine-spacing-md);
  margin-bottom: var(--mantine-spacing-md);
}
.toolbar-actions {
  display: flex;
  gap: var(--mantine-spacing-sm);
  align-items: center;
}

/* inline form: input + button(s) on one row. */
.form-inline {
  display: flex;
  gap: var(--mantine-spacing-sm);
  align-items: end;
  flex-wrap: wrap;
}
.form-inline .field {
  flex: 0 0 auto;
  min-width: 200px;
  max-width: 320px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.form-inline .field > span,
.form-inline .field-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--mantine-color-dimmed);
  font-weight: 600;
}
.form-inline .field input,
.form-inline .field select { width: 100%; }
.form-inline .actions {
  display: flex;
  gap: var(--mantine-spacing-sm);
  align-items: center;
  padding-bottom: 1px;
}

/* Inline label that wraps an <input>/<select> as a child — kept so
 * pages that still use it render with the right vertical rhythm. */
.label-inline {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--mantine-color-dimmed);
  font-weight: 600;
}

/* notice / callout */
.notice {
  display: flex;
  gap: var(--mantine-spacing-sm);
  padding: var(--mantine-spacing-md);
  border-radius: var(--mantine-radius-md);
  border-left: 3px solid var(--mantine-color-blue-6);
  background: var(--mantine-color-blue-light);
  margin-bottom: var(--mantine-spacing-md);
}
.notice-info    { border-left-color: var(--mantine-color-blue-6); background: var(--mantine-color-blue-light); }
.notice-warn    { border-left-color: var(--mantine-color-yellow-6); background: var(--mantine-color-yellow-light); }
.notice-danger  { border-left-color: var(--mantine-color-red-6);   background: var(--mantine-color-red-light); }
/* Flash kinds emitted by handlers/flash.go — error == red, success == green. */
.notice-error   { border-left-color: var(--mantine-color-red-6);    background: var(--mantine-color-red-light); }
.notice-success { border-left-color: var(--mantine-color-green-6);  background: var(--mantine-color-green-light); }
.notice-icon {
  flex: 0 0 20px;
  color: var(--mantine-color-blue-4);
  display: flex;
  align-items: flex-start;
  padding-top: 2px;
}
.notice-warn    .notice-icon { color: var(--mantine-color-yellow-6); }
.notice-danger  .notice-icon { color: var(--mantine-color-red-4); }
.notice-error   .notice-icon { color: var(--mantine-color-red-4); }
.notice-success .notice-icon { color: var(--mantine-color-green-5); }
.notice-body  { flex: 1; min-width: 0; }
.notice-title { font-weight: 600; color: var(--mantine-color-text); margin: 0 0 4px 0; }
.notice-text  { color: var(--mantine-color-dimmed); font-size: var(--mantine-font-size-sm); margin: 0; }

/* Legacy `.banner banner-warn` blocks (used by certs.html before the
 * .notice rollout) — map onto the new tokens so the page still looks
 * right without an immediate template migration. */
.banner {
  display: flex;
  gap: var(--mantine-spacing-sm);
  padding: var(--mantine-spacing-md);
  border-radius: var(--mantine-radius-md);
  background: var(--mantine-color-yellow-light);
  border-left: 3px solid var(--mantine-color-yellow-6);
  margin-bottom: var(--mantine-spacing-md);
}
.banner-icon  { flex: 0 0 20px; color: var(--mantine-color-yellow-6); display: flex; align-items: flex-start; padding-top: 2px; }
.banner-body  { flex: 1; min-width: 0; }
.banner-body strong { display: block; margin-bottom: 4px; }
.banner-warn  { background: var(--mantine-color-yellow-light); border-left-color: var(--mantine-color-yellow-6); }
.banner-info  { background: var(--mantine-color-blue-light); border-left-color: var(--mantine-color-blue-6); }
.reauth-inline { display: flex; gap: var(--mantine-spacing-sm); align-items: end; margin-top: var(--mantine-spacing-sm); flex-wrap: wrap; }
.reauth-inline .field { flex: 0 0 220px; }

/* Audit-page key chip (copy idempotency-key inline). */
.key-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--mantine-color-body);
  border: 1px solid var(--mantine-color-default-border);
  border-radius: var(--mantine-radius-sm);
  padding: 4px 8px;
  color: var(--mantine-color-text);
  cursor: pointer;
}
.key-chip:hover { background: var(--mantine-color-default-hover); }

/* /metrics-live page tiles. */
.metric-tile {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: var(--mantine-spacing-sm) 0;
}
.metric-tile-name {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--mantine-color-dimmed);
}
.metric-tile-value {
  font-size: 22px;
  font-weight: 600;
  color: var(--mantine-color-text);
  font-family: var(--mantine-font-family-monospace);
  line-height: 1.2;
}
.metric-tile-sub  { font-size: 11px; color: var(--mantine-color-dimmed); }
.metric-tile-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--mantine-spacing-md);
}

/* ------------------------------------------------------- responsive */

/* Below 900px the sidebar drops out of the grid track and the content
 * column expands. The sidebar is rendered as a horizontal strip so
 * navigation remains reachable on tablet without a hamburger toggle
 * (the operator console rarely runs this small, but the brief mandates
 * a graceful stack). */
@media (max-width: 900px) {
  .shell {
    -ms-grid-columns: 1fr;
    grid-template-columns: 1fr;
  }
  .sidebar {
    position: static;
    border-right: 0;
    border-bottom: 1px solid var(--mantine-color-default-border);
    min-height: 0;
  }
  .sidebar-nav {
    flex-direction: row;
    overflow-x: auto;
    padding: 4px 8px;
  }
  .sidebar-nav .nav-item {
    margin: 0;
    border-radius: 0;
    padding: 8px 12px;
  }
  .sidebar-nav .nav-item.active,
  .sidebar-nav .nav-item[aria-current="page"] {
    background: var(--mantine-color-blue-light);
    color: var(--mantine-color-blue-4);
  }
  .sidebar-footer { display: none; }
  .main { padding: 16px 20px; }
  .topbar { padding: 0 16px; }
  .card-grid { grid-template-columns: 1fr; }
  /* .filters is display:flex; under 900px stack the controls vertically
     rather than rely on the legacy grid-template-columns (the wrap-aware
     flex layout collides with grid templates on Safari < 17). */
  .filters { flex-direction: column; align-items: stretch; }
  .filters .field { width: 100%; }
  .modal-body.grid-2 { grid-template-columns: 1fr; }
  /* Wide data tables horizontally scroll inside the card instead of
     bleeding past the viewport. min-width: max-content keeps columns
     from collapsing into illegible widths. */
  .card.table-card,
  .card .data-table { max-width: 100%; overflow-x: auto; }
  .grid,
  table.data-table { min-width: max-content; }
}

/* Row numbering (/live + /tags): a CSS counter on the leading #-cell numbers the
   server-rendered rows 1..N per page. No template arithmetic; reset per tbody so
   the count always starts at 1. */
tbody[data-live-rows],
tbody[data-tags-rows] { counter-reset: rownum; }
.grid th.col-num { width: 1%; text-align: right; white-space: nowrap; }
.grid td.row-num {
  counter-increment: rownum;
  text-align: right;
  color: var(--mantine-color-dimmed);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.grid td.row-num::before { content: counter(rownum); }

/* /live source grouping: a sticky-ish section band per source so the rows are
   visually split by источник instead of one undifferentiated heap. The header
   row carries no .row-num cell, so it never advances the # counter. */
.grid tr.group-row td {
  background: var(--mantine-color-default-hover, rgba(255,255,255,0.04));
  border-top: 2px solid var(--mantine-color-default-border, rgba(255,255,255,0.08));
  padding-top: 0.55rem;
  padding-bottom: 0.55rem;
}
.grid tr.group-row .group-name {
  font-weight: 600;
  letter-spacing: 0.01em;
}
.grid tr.group-row .group-count {
  color: var(--mantine-color-dimmed);
  font-variant-numeric: tabular-nums;
  font-size: 0.85em;
}

/* aria-current="page" mirrors the .active class outside the responsive
   media query so keyboard focus + screen-reader announcement land on
   the correct row. Both selectors stay live during the rollout so a
   stale template that still emits only .active keeps working. */
.sidebar-nav .nav-item[aria-current="page"] {
  background: var(--mantine-color-blue-light);
  color: var(--mantine-color-blue-4);
}
.sidebar-nav .nav-item[aria-current="page"] .tabler-icon,
.sidebar-nav .nav-item[aria-current="page"] svg {
  color: var(--mantine-color-blue-4);
}

/* Keyboard reach for the inline-edit toggle: focus-within outlines the
   surrounding label so a switch-without-button can still be tabbed to. */
.switch[role="switch"]:focus-within {
  outline: 2px solid var(--mantine-color-blue-4);
  outline-offset: 2px;
}

@media print {
  .sidebar, .topbar, #toast { display: none; }
  .main { padding: 0; }
  body { background: #ffffff; color: #000000; }
}

/* /reference — dataset tabs (chips) + counts */
.reference-tabs { display: flex; flex-wrap: wrap; gap: 8px; margin: 14px 0; }
.chip-tab {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 12px;
  border: 1px solid var(--mantine-color-default-border);
  border-radius: var(--mantine-radius-sm);
  color: var(--mantine-color-dimmed);
  text-decoration: none; font-size: 13px;
  transition: color 120ms, border-color 120ms, background 120ms;
}
.chip-tab:hover { color: var(--mantine-color-text); border-color: var(--mantine-color-blue-6); }
.chip-tab.active {
  color: var(--mantine-color-blue-3);
  border-color: var(--mantine-color-blue-5);
  background: var(--mantine-color-blue-light, rgba(34,139,230,0.08));
}
.chip-count {
  font-variant-numeric: tabular-nums; font-size: 11px;
  color: var(--mantine-color-dimmed);
  background: var(--mantine-color-default-hover, rgba(255,255,255,0.04));
  border-radius: 999px; padding: 1px 7px;
}
.field-grow { flex: 1 1 320px; }

/* Сводка по установкам источника на /process — живёт в строке фильтров, между
 * выбором источника и кнопкой. Ставится ПЕРЕД .filters-actions, поэтому
 * margin-left:auto у кнопки продолжает прижимать её вправо, а сводка занимает
 * пустоту посередине. Переиспользует .meta-item/.meta-label/.meta-value со
 * страницы агентов — отдельного языка для чисел заводить не за чем. */
.proc-stats {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 18px;
  font-size: 12px;
  padding-bottom: 6px;   /* садит строку на базовую линию поля выбора */
  min-width: 0;
}
.proc-stats .meta-value { font-variant-numeric: tabular-nums; }

/* Итог ручного обновления справочников. Живёт под строкой фильтров, рядом с кнопкой,
 * которая его порождает: операция идёт в фоне, и без видимого исхода кнопка врёт —
 * «запущено» звучит одинаково и когда всё легло, и когда упало через девять секунд. */
.field-hint.is-error { color: var(--mantine-color-red-6, #e03131); }
