/* ============================================================
   ELX MQTT — dashboard styles
   Two themes, both authored; the theme toggle stamps data-theme
   on <html> and always wins over the OS preference.
   ============================================================ */

/* ---------------------------------------------- tokens: dark (default) */
:root,
:root[data-theme="dark"] {
  color-scheme: dark;

  /* Deep dark-blue palette: the surfaces are navy rather than neutral grey,
     which is what gives the theme its blue cast under the glass panels. */
  --bg:            #0a0f1e;
  --bg-deep:       #060a15;
  --surface:       #0f1729;
  --surface-2:     #141d33;
  --surface-3:     #1c2942;
  --border:        #20304f;
  --border-strong: #2c436e;

  --text:          #e8effb;
  --text-2:        #9fb2ce;
  --text-3:        #61748f;

  --accent:        #22d3ee;
  --accent-2:      #3b82f6;
  --accent-ink:    #04121a;
  --accent-soft:   rgba(34, 211, 238, 0.15);

  --good:          #22c55e;
  --warn:          #eab308;
  --danger:        #f43f5e;
  --info:          #38bdf8;

  /* Chart series. Validated against the #0f1729 surface: OKLCH L inside the
     dark band, chroma above the floor, adjacent-pair CVD separation above
     target, contrast >= 3:1. */
  --series-1: #14a89a;
  --series-2: #3987e5;
  --series-3: #d95926;
  --series-4: #9085e9;

  --grid:      rgba(120, 170, 255, 0.07);
  --axis:      rgba(140, 180, 255, 0.18);

  --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.45);
  --shadow-2: 0 8px 24px -8px rgba(0, 0, 0, 0.6);
  --shadow-3: 0 24px 60px -20px rgba(2, 6, 20, 0.8);

  /* Glow: a coloured halo used on interactive accents. In light mode these
     collapse to plain shadows so the UI does not look neon in daylight. */
  --glow:      0 0 22px -4px rgba(34, 211, 238, 0.5);
  --glow-soft: 0 0 30px -10px rgba(34, 211, 238, 0.4);
  --glow-ring: 0 0 0 1px rgba(34, 211, 238, 0.28), 0 0 20px -4px rgba(34, 211, 238, 0.4);
  --text-glow: 0 0 18px rgba(34, 211, 238, 0.35);

  --radius:    14px;
  --radius-sm: 9px;
  --radius-lg: 20px;

  --ease:      cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in:   cubic-bezier(0.55, 0, 0.35, 1);
  --dur:       0.28s;

  --sidebar-w: 254px;
  --topbar-h:  66px;

  --orb-1: rgba(34, 211, 238, 0.18);
  --orb-2: rgba(59, 130, 246, 0.18);
  --orb-3: rgba(96, 130, 246, 0.12);
}

/* ---------------------------------------------------- tokens: light */
:root[data-theme="light"] {
  color-scheme: light;

  --bg:            #eef2f8;
  --bg-deep:       #e4eaf3;
  --surface:       #ffffff;
  --surface-2:     #f7f9fc;
  --surface-3:     #eef2f7;
  --border:        #dde4ee;
  --border-strong: #c3cede;

  --text:          #0f172a;
  --text-2:        #55617a;
  --text-3:        #8794ad;

  --accent:        #0aa08a;
  --accent-2:      #2563eb;
  --accent-ink:    #ffffff;
  --accent-soft:   rgba(10, 160, 138, 0.12);

  --good:          #15803d;
  --warn:          #a16207;
  --danger:        #dc2626;
  --info:          #0369a1;

  --series-1: #0d9488;
  --series-2: #2563eb;
  --series-3: #c2410c;
  --series-4: #7c3aed;

  --grid: rgba(15, 23, 42, 0.07);
  --axis: rgba(15, 23, 42, 0.18);

  --shadow-1: 0 1px 2px rgba(15, 23, 42, 0.06);
  --shadow-2: 0 8px 24px -10px rgba(15, 23, 42, 0.18);
  --shadow-3: 0 24px 60px -22px rgba(15, 23, 42, 0.28);

  /* In daylight the glow collapses to ordinary shadows: neon halos look wrong
     on a light surface. */
  --glow:      var(--shadow-2);
  --glow-soft: var(--shadow-2);
  --glow-ring: 0 0 0 3px var(--accent-soft);
  --text-glow: none;

  --orb-1: rgba(10, 160, 138, 0.18);
  --orb-2: rgba(37, 99, 235, 0.14);
  --orb-3: rgba(124, 58, 237, 0.10);
}

/* ------------------------------------------------------------- base */
*, *::before, *::after { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: "Segoe UI", system-ui, -apple-system, Roboto, "Helvetica Neue", Arial, sans-serif;
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}

h1, h2, h3, h4 { margin: 0; font-weight: 650; letter-spacing: -0.015em; line-height: 1.25; }
p { margin: 0; }
button { font: inherit; color: inherit; }

/* The `hidden` attribute must always win. Elements like .modal-root, .app,
   .auth and .cookie-bar set their own `display`, which by ordinary specificity
   overrides `hidden` and leaves them on screen — most visibly the modal
   backdrop, which then veils the whole UI in a murky blur. This one rule keeps
   `hidden` authoritative everywhere. */
[hidden] { display: none !important; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 6px;
}

svg { fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }

/* Text inside an SVG must not inherit the icon stroke above. Without this,
   chart axis labels are painted with a 1.8px outline on top of their fill,
   which reads as thick, smeared glyphs. Fill only. */
svg text { stroke: none; fill: currentColor; }

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 8px; border: 3px solid transparent; background-clip: content-box; }
::-webkit-scrollbar-thumb:hover { background: var(--text-3); background-clip: content-box; }

/* ---------------------------------------------------------- backdrop */
.backdrop {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  background: radial-gradient(1200px 800px at 80% -10%, var(--bg-deep), var(--bg) 60%);
}

.orb {
  position: absolute;
  border-radius: 50%;
  filter: blur(80px);
  will-change: transform;
}
.orb-1 { width: 46vw; height: 46vw; left: -10vw; top: -12vw; background: var(--orb-1); animation: drift-1 34s var(--ease) infinite alternate; }
.orb-2 { width: 38vw; height: 38vw; right: -8vw; top: 12vh; background: var(--orb-2); animation: drift-2 42s var(--ease) infinite alternate; }
.orb-3 { width: 34vw; height: 34vw; left: 34vw; bottom: -16vw; background: var(--orb-3); animation: drift-3 50s var(--ease) infinite alternate; }

@keyframes drift-1 { to { transform: translate3d(8vw, 10vh, 0) scale(1.15); } }
@keyframes drift-2 { to { transform: translate3d(-10vw, 14vh, 0) scale(1.2); } }
@keyframes drift-3 { to { transform: translate3d(6vw, -12vh, 0) scale(0.9); } }

.grid-lines {
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(var(--grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid) 1px, transparent 1px);
  background-size: 56px 56px;
  mask-image: radial-gradient(circle at 50% 40%, #000 10%, transparent 78%);
  opacity: 0.5;
}

/* -------------------------------------------------------------- boot */
.boot {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 18px;
  background: var(--bg);
  transition: opacity 0.4s var(--ease), visibility 0.4s;
}
.boot.is-gone { opacity: 0; visibility: hidden; }
.boot-mark svg { color: var(--accent); stroke-width: 2.4; }
.boot-ring { stroke: var(--border-strong); stroke-width: 2; opacity: 0.6; }
.boot-dot { fill: currentColor; stroke: none; animation: pulse-dot 1.6s var(--ease) infinite; }
.boot-wave { transform-origin: 32px 32px; animation: spin 2.2s linear infinite; }
.boot-wave-2 { animation-duration: 1.5s; animation-direction: reverse; opacity: 0.6; }
.boot-text { font-weight: 700; letter-spacing: 0.32em; font-size: 13px; color: var(--text-3); text-transform: uppercase; }

@keyframes spin { to { transform: rotate(360deg); } }
@keyframes pulse-dot { 50% { opacity: 0.35; transform: scale(0.8); transform-origin: 32px 32px; } }

/* ------------------------------------------------------------ brand */
.brand-mark { color: var(--accent); stroke-width: 2.6; }
.brand-mark circle { fill: currentColor; stroke: none; }
.brand-name {
  font-weight: 700;
  letter-spacing: -0.02em;
  font-size: 17px;
  background: linear-gradient(100deg, var(--accent), var(--accent-2));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.brand-mark { filter: drop-shadow(var(--text-glow)); }

/* ------------------------------------------------------------- auth */
.auth {
  position: relative;
  z-index: 1;
  min-height: 100dvh;
  display: grid;
  place-items: center;
  padding: 24px;
}

.auth-shell {
  width: min(1000px, 100%);
  display: grid;
  grid-template-columns: 1.05fr 1fr;
  background: color-mix(in srgb, var(--surface) 82%, transparent);
  backdrop-filter: blur(24px) saturate(1.3);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-3);
  overflow: hidden;
  animation: rise 0.6s var(--ease) both;
}

@keyframes rise {
  from { opacity: 0; transform: translateY(18px) scale(0.985); }
}

.auth-hero {
  padding: 44px 40px;
  background:
    radial-gradient(600px 300px at 0% 0%, var(--accent-soft), transparent 70%),
    linear-gradient(160deg, var(--surface-2), var(--surface));
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.auth-brand { display: flex; align-items: center; gap: 11px; }
.auth-hero-title { font-size: 30px; line-height: 1.15; margin-top: 8px; }
.auth-hero-text { color: var(--text-2); font-size: 15px; }
.auth-features { list-style: none; margin: 6px 0 0; padding: 0; display: grid; gap: 11px; }
.auth-features li { display: flex; align-items: flex-start; gap: 11px; color: var(--text-2); font-size: 14px; animation: slide-in 0.5s var(--ease) both; }
.auth-features li:nth-child(1) { animation-delay: 0.10s; }
.auth-features li:nth-child(2) { animation-delay: 0.18s; }
.auth-features li:nth-child(3) { animation-delay: 0.26s; }
.auth-features li:nth-child(4) { animation-delay: 0.34s; }
.tick {
  flex: none;
  width: 18px; height: 18px;
  margin-top: 2px;
  border-radius: 50%;
  background: var(--accent-soft);
  position: relative;
}
.tick::after {
  content: "";
  position: absolute;
  left: 5px; top: 4px;
  width: 5px; height: 9px;
  border: 2px solid var(--accent);
  border-top: 0; border-left: 0;
  transform: rotate(42deg);
}
.auth-version { margin-top: auto; color: var(--text-3); font-size: 12px; }

@keyframes slide-in { from { opacity: 0; transform: translateX(-10px); } }

.auth-panel { padding: 32px 40px 40px; display: flex; flex-direction: column; }
.auth-toolbar { display: flex; justify-content: flex-end; gap: 8px; margin-bottom: 14px; }

.auth-form { display: flex; flex-direction: column; gap: 14px; animation: fade-up 0.4s var(--ease) both; }
.auth-form[hidden] { display: none; }
.auth-form h2 { font-size: 23px; }
.auth-sub { color: var(--text-2); font-size: 14px; margin-top: -8px; }

@keyframes fade-up { from { opacity: 0; transform: translateY(10px); } }

.auth-icon-badge {
  width: 56px; height: 56px;
  display: grid; place-items: center;
  border-radius: 16px;
  background: var(--accent-soft);
  color: var(--accent);
  animation: pop 0.5s var(--ease) both;
}
.auth-icon-badge svg { width: 26px; height: 26px; stroke-width: 2.2; }
.auth-icon-badge.is-bad { background: color-mix(in srgb, var(--danger) 14%, transparent); color: var(--danger); }
@keyframes pop { from { opacity: 0; transform: scale(0.6); } }

.auth-links { display: flex; justify-content: space-between; gap: 12px; margin-top: 2px; }
.first-admin-note {
  padding: 12px 14px;
  border-radius: var(--radius-sm);
  background: var(--accent-soft);
  border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent);
  display: grid; gap: 6px;
}
.first-admin-note p { font-size: 13px; color: var(--text-2); }

.auth-error {
  padding: 10px 13px;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--danger) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--danger) 34%, transparent);
  color: var(--danger);
  font-size: 13.5px;
  animation: shake 0.4s var(--ease);
}
@keyframes shake {
  20% { transform: translateX(-6px); }
  40% { transform: translateX(5px); }
  60% { transform: translateX(-3px); }
  80% { transform: translateX(2px); }
}

/* ------------------------------------------------------- form parts */
/* A .field is itself a grid item, so it stretches to the height of the tallest
   cell in its row. Without align-content its own rows share that height and the
   input grows tall whenever a neighbouring field carries a hint under it —
   which is what made the bridge form look stretched. Pack to the top instead. */
.field { display: grid; gap: 6px; align-content: start; }
.field > span { font-size: 13px; font-weight: 600; color: var(--text-2); }

input[type="text"], input[type="password"], input[type="email"],
input[type="number"], input[type="search"], select, textarea {
  width: 100%;
  padding: 11px 13px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  font: inherit;
  transition: border-color 0.18s var(--ease), box-shadow 0.18s var(--ease), background 0.18s var(--ease);
}
textarea { resize: vertical; min-height: 96px; font-family: "Cascadia Code", "SF Mono", Consolas, monospace; font-size: 13.5px; }
input::placeholder, textarea::placeholder { color: var(--text-3); }

input:hover, select:hover, textarea:hover { border-color: var(--border-strong); }
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
  background: var(--surface);
}
input:disabled, select:disabled, textarea:disabled { opacity: 0.55; cursor: not-allowed; }

select {
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--text-3) 50%), linear-gradient(135deg, var(--text-3) 50%, transparent 50%);
  background-position: calc(100% - 17px) 50%, calc(100% - 12px) 50%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 34px;
}

.hint { font-size: 12px; color: var(--text-3); }

.meter { display: block; height: 4px; border-radius: 4px; background: var(--surface-3); overflow: hidden; }
.meter i {
  display: block; height: 100%; width: 0;
  border-radius: 4px;
  background: var(--danger);
  transition: width 0.35s var(--ease), background 0.35s var(--ease);
}
.meter[data-level="2"] i { background: var(--warn); }
.meter[data-level="3"] i { background: var(--info); }
.meter[data-level="4"] i { background: var(--good); }

.check { display: flex; align-items: center; gap: 10px; cursor: pointer; user-select: none; }
.check input { appearance: none; margin: 0; flex: none; width: 40px; height: 22px; border-radius: 12px; background: var(--surface-3); border: 1px solid var(--border); position: relative; cursor: pointer; transition: background 0.22s var(--ease), border-color 0.22s var(--ease); }
.check input::after {
  content: "";
  position: absolute;
  top: 2px; left: 2px;
  width: 16px; height: 16px;
  border-radius: 50%;
  background: var(--text-3);
  transition: transform 0.24s var(--ease), background 0.24s var(--ease);
}
.check input:checked { background: var(--accent); border-color: var(--accent); }
.check input:checked::after { transform: translateX(18px); background: var(--accent-ink); }
.check span { font-size: 14px; }
.check small { display: block; font-size: 12px; color: var(--text-3); }

/* --------------------------------------------------------- buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px 18px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  transition: transform 0.14s var(--ease), background 0.18s var(--ease),
              border-color 0.18s var(--ease), box-shadow 0.18s var(--ease), opacity 0.18s;
}
.btn svg { width: 16px; height: 16px; }
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0) scale(0.985); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
.btn-block { width: 100%; padding: 12px 18px; }

.btn-primary {
  background: linear-gradient(100deg, var(--accent), var(--accent-2));
  color: var(--accent-ink);
  box-shadow: 0 6px 20px -8px color-mix(in srgb, var(--accent) 70%, transparent), var(--glow);
}
.btn-primary:hover { box-shadow: 0 10px 28px -8px color-mix(in srgb, var(--accent) 85%, transparent), var(--glow); }

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

.btn-danger { background: color-mix(in srgb, var(--danger) 14%, transparent); border-color: color-mix(in srgb, var(--danger) 32%, transparent); color: var(--danger); }
.btn-danger:hover { background: color-mix(in srgb, var(--danger) 22%, transparent); }

.btn-sm { padding: 6px 11px; font-size: 12.5px; border-radius: 7px; }

.link {
  background: none; border: 0; padding: 0;
  color: var(--accent);
  font-size: 13.5px; font-weight: 600;
  cursor: pointer;
  position: relative;
}
.link::after {
  content: ""; position: absolute; left: 0; bottom: -2px;
  width: 100%; height: 1px; background: currentColor;
  transform: scaleX(0); transform-origin: right;
  transition: transform 0.24s var(--ease);
}
.link:hover::after { transform: scaleX(1); transform-origin: left; }

.icon-btn {
  display: grid; place-items: center;
  width: 36px; height: 36px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-2);
  cursor: pointer;
  transition: background 0.18s var(--ease), color 0.18s var(--ease);
}
.icon-btn svg { width: 18px; height: 18px; }
.icon-btn:hover { background: var(--surface-2); color: var(--text); }

.chip {
  display: inline-flex; align-items: center; gap: 6px;
  height: 34px; padding: 0 12px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--text-2);
  font-size: 12.5px; font-weight: 700; letter-spacing: 0.04em;
  cursor: pointer;
  transition: background 0.18s var(--ease), color 0.18s var(--ease), border-color 0.18s var(--ease);
}
.chip:hover { color: var(--text); border-color: var(--border-strong); }
.chip-icon { width: 34px; padding: 0; justify-content: center; }
.chip-icon svg { width: 16px; height: 16px; }

/* The sun and moon cross-fade with the theme. */
.icon-moon { display: none; }
:root[data-theme="light"] .icon-sun { display: none; }
:root[data-theme="light"] .icon-moon { display: block; }

/* --------------------------------------------------------- badges */
.badge {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 9px;
  border-radius: 999px;
  background: var(--surface-3);
  border: 1px solid var(--border);
  color: var(--text-2);
  font-size: 11.5px; font-weight: 700; letter-spacing: 0.03em;
  text-transform: uppercase;
  white-space: nowrap;
}
.badge-accent { background: var(--accent-soft); border-color: color-mix(in srgb, var(--accent) 32%, transparent); color: var(--accent); }
.badge-good { background: color-mix(in srgb, var(--good) 14%, transparent); border-color: color-mix(in srgb, var(--good) 32%, transparent); color: var(--good); }
.badge-warn { background: color-mix(in srgb, var(--warn) 14%, transparent); border-color: color-mix(in srgb, var(--warn) 34%, transparent); color: var(--warn); }
.badge-danger { background: color-mix(in srgb, var(--danger) 14%, transparent); border-color: color-mix(in srgb, var(--danger) 32%, transparent); color: var(--danger); }
.badge-info { background: color-mix(in srgb, var(--info) 14%, transparent); border-color: color-mix(in srgb, var(--info) 32%, transparent); color: var(--info); }

/* The online/offline badge in the clients table reads a touch smaller. */
.badge-state { font-size: 10px; letter-spacing: 0.02em; }

.live-dot {
  width: 7px; height: 7px; flex: none;
  border-radius: 50%;
  background: var(--good);
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--good) 60%, transparent);
  animation: ping 2s var(--ease) infinite;
}
.live-dot.is-off { background: var(--text-3); animation: none; box-shadow: none; }
@keyframes ping {
  70%  { box-shadow: 0 0 0 7px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}

/* ----------------------------------------------------------- shell */
.app { position: relative; z-index: 1; min-height: 100dvh; display: flex; }

.sidebar {
  position: fixed;
  inset: 0 auto 0 0;
  width: var(--sidebar-w);
  z-index: 30;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 18px 14px;
  background: color-mix(in srgb, var(--surface) 78%, transparent);
  backdrop-filter: blur(20px);
  border-right: 1px solid var(--border);
  transition: transform var(--dur) var(--ease);
}
.sidebar-head { display: flex; align-items: center; gap: 10px; padding: 6px 8px 14px; }
.sidebar-close { display: none; margin-left: auto; }

.nav { display: grid; gap: 3px; }
.nav-item {
  display: flex; align-items: center; gap: 11px;
  width: 100%;
  padding: 10px 12px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-2);
  font-size: 14px; font-weight: 550;
  text-align: left;
  cursor: pointer;
  position: relative;
  transition: color 0.18s var(--ease), background 0.18s var(--ease);
}
.nav-item svg { width: 18px; height: 18px; flex: none; opacity: 0.85; }
.nav-item:hover { background: var(--surface-2); color: var(--text); }
.nav-item.is-active { color: var(--text); background: var(--accent-soft); }
.nav-item.is-active::before {
  content: "";
  position: absolute; left: 0; top: 50%;
  width: 3px; height: 20px;
  border-radius: 0 3px 3px 0;
  background: var(--accent);
  box-shadow: var(--glow);
  transform: translateY(-50%);
  animation: grow-bar 0.3s var(--ease);
}
@keyframes grow-bar { from { height: 0; } }
.nav-item.is-active svg { color: var(--accent); opacity: 1; }
.nav-count {
  margin-left: auto;
  min-width: 22px;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--surface-3);
  color: var(--text-2);
  font-size: 11px; font-weight: 700;
  text-align: center;
}
.nav-item .live-dot { margin-left: auto; }

.sidebar-foot { margin-top: auto; padding: 12px 10px 4px; border-top: 1px solid var(--border); display: grid; gap: 4px; }
.conn-state { display: flex; align-items: center; gap: 8px; font-size: 12.5px; font-weight: 600; color: var(--text-2); }
.conn-state.is-off { color: var(--text-3); }
.uptime { font-size: 11.5px; color: var(--text-3); font-variant-numeric: tabular-nums; }

.main { flex: 1; min-width: 0; margin-left: var(--sidebar-w); display: flex; flex-direction: column; }

.topbar {
  position: sticky; top: 0; z-index: 20;
  height: var(--topbar-h);
  display: flex; align-items: center; gap: 14px;
  padding: 0 24px;
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  backdrop-filter: blur(16px);
  border-bottom: 1px solid var(--border);
}
.menu-btn { display: none; }
.view-title { font-size: 19px; }
.topbar-actions { margin-left: auto; display: flex; align-items: center; gap: 9px; }

.user-menu { position: relative; }
.avatar {
  width: 36px; height: 36px;
  border: 0; border-radius: 50%;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: var(--accent-ink);
  font-weight: 800; font-size: 14px;
  cursor: pointer;
  transition: transform 0.18s var(--ease), box-shadow 0.18s var(--ease);
}
.avatar { box-shadow: var(--glow-soft); }
.avatar:hover { transform: scale(1.06); box-shadow: 0 6px 18px -6px color-mix(in srgb, var(--accent) 70%, transparent), var(--glow); }

.user-pop {
  position: absolute; right: 0; top: calc(100% + 10px);
  min-width: 226px;
  padding: 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-3);
  animation: pop-down 0.2s var(--ease);
}
@keyframes pop-down { from { opacity: 0; transform: translateY(-6px) scale(0.97); } }
.user-pop-head { padding: 10px 11px 12px; border-bottom: 1px solid var(--border); margin-bottom: 6px; display: grid; gap: 3px; justify-items: start; }
.user-pop-name { font-weight: 650; }
.user-pop-mail { font-size: 12.5px; color: var(--text-3); word-break: break-all; }
.user-pop-item {
  display: block; width: 100%;
  padding: 9px 11px;
  border: 0; border-radius: 8px;
  background: transparent;
  text-align: left; font-size: 14px;
  cursor: pointer;
  transition: background 0.15s var(--ease);
}
.user-pop-item:hover { background: var(--surface-2); }
.user-pop-item.danger { color: var(--danger); }

/* ----------------------------------------------------------- views */
.views { flex: 1; padding: 24px; max-width: 1560px; width: 100%; }
.view { display: grid; gap: 18px; animation: view-in 0.36s var(--ease) both; }
@keyframes view-in { from { opacity: 0; transform: translateY(12px); } }

.section-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.section-head h2 { font-size: 16px; }
.section-head .spacer { flex: 1; }
.section-sub { color: var(--text-2); font-size: 13.5px; }

.card {
  background: color-mix(in srgb, var(--surface) 86%, transparent);
  backdrop-filter: blur(12px);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-1);
  padding: 18px;
}
.card-head { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
.card-head h3 { font-size: 14.5px; }
.card-head .spacer { flex: 1; }

.grid { display: grid; gap: 16px; }
.grid-tiles { grid-template-columns: repeat(auto-fit, minmax(196px, 1fr)); }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); }
.grid-charts { grid-template-columns: repeat(auto-fit, minmax(420px, 1fr)); }

/* ------------------------------------------------------ stat tiles */
.tile {
  position: relative;
  padding: 16px 18px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--surface) 86%, transparent);
  backdrop-filter: blur(12px);
  overflow: hidden;
  transition: transform 0.22s var(--ease), border-color 0.22s var(--ease), box-shadow 0.22s var(--ease);
  animation: tile-in 0.45s var(--ease) both;
}
.tile:hover {
  transform: translateY(-2px);
  border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
  box-shadow: var(--shadow-2), var(--glow-soft);
}
.tile::after {
  content: "";
  position: absolute; inset: 0 auto auto 0;
  width: 100%; height: 2px;
  background: linear-gradient(90deg, var(--accent), transparent);
  opacity: 0.7;
}
@keyframes tile-in { from { opacity: 0; transform: translateY(14px); } }

.tile-label { font-size: 12px; font-weight: 650; color: var(--text-3); text-transform: uppercase; letter-spacing: 0.06em; }
.tile-value { font-size: 30px; font-weight: 700; letter-spacing: -0.03em; font-variant-numeric: tabular-nums; margin-top: 6px; line-height: 1.1; }
.tile-value small { font-size: 14px; font-weight: 600; color: var(--text-3); margin-left: 4px; letter-spacing: 0; }
.tile-foot { display: flex; align-items: center; gap: 7px; margin-top: 8px; font-size: 12.5px; color: var(--text-2); }
.tile-spark { position: absolute; right: 0; bottom: 0; width: 96px; height: 40px; opacity: 0.5; pointer-events: none; }

/* ---------------------------------------------------------- charts */
.chart-card { display: grid; gap: 12px; }
.chart-legend { display: flex; flex-wrap: wrap; gap: 14px; }
.legend-item { display: inline-flex; align-items: center; gap: 7px; font-size: 12.5px; color: var(--text-2); }
.legend-swatch { width: 10px; height: 10px; border-radius: 3px; flex: none; }

.chart { position: relative; width: 100%; }
.chart svg { display: block; width: 100%; height: auto; overflow: visible; }
.chart-grid line { stroke: var(--grid); stroke-width: 1; }
.chart-axis text { fill: var(--text-3); font-size: 10.5px; font-variant-numeric: tabular-nums; }
.chart-line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.chart-area { stroke: none; opacity: 0.16; }
.chart-crosshair { stroke: var(--axis); stroke-width: 1; stroke-dasharray: 3 3; }
.chart-marker { stroke: var(--surface); stroke-width: 2; }

/* The line draws itself in on first paint. */
.chart-line.is-drawing {
  stroke-dasharray: var(--len);
  stroke-dashoffset: var(--len);
  animation: draw 0.9s var(--ease) forwards;
}
@keyframes draw { to { stroke-dashoffset: 0; } }

.chart-tip {
  position: absolute;
  z-index: 5;
  min-width: 132px;
  padding: 9px 11px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  box-shadow: var(--shadow-2);
  font-size: 12.5px;
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, -114%);
  transition: opacity 0.14s var(--ease);
}
.chart-tip.is-on { opacity: 1; }
.chart-tip-time { color: var(--text-3); font-size: 11px; margin-bottom: 5px; }
.chart-tip-row { display: flex; align-items: center; gap: 7px; justify-content: space-between; }
.chart-tip-row b { font-variant-numeric: tabular-nums; font-weight: 650; }
.chart-tip-key { display: inline-flex; align-items: center; gap: 6px; color: var(--text-2); }

.bar-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 10px; align-items: center; padding: 5px 0; }
.bar-label { font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: "Cascadia Code", Consolas, monospace; }
.bar-track { grid-column: 1 / -1; height: 7px; border-radius: 4px; background: var(--surface-3); overflow: hidden; }
.bar-fill { height: 100%; border-radius: 4px; background: var(--series-1); width: 0; transition: width 0.6s var(--ease); }
.bar-value { font-size: 12.5px; color: var(--text-2); font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------- tables */
.table-wrap { overflow-x: auto; border-radius: var(--radius); border: 1px solid var(--border); background: color-mix(in srgb, var(--surface) 86%, transparent); }
table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
thead th {
  position: sticky; top: 0; z-index: 1;
  padding: 11px 14px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  text-align: left;
  font-size: 11.5px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--text-3);
  white-space: nowrap;
}
tbody td { padding: 11px 14px; border-bottom: 1px solid var(--border); vertical-align: middle; }
tbody tr { transition: background 0.15s var(--ease); animation: row-in 0.3s var(--ease) both; }
tbody tr:hover { background: var(--surface-2); }
tbody tr:last-child td { border-bottom: 0; }
@keyframes row-in { from { opacity: 0; transform: translateY(4px); } }

.mono { font-family: "Cascadia Code", "SF Mono", Consolas, monospace; font-size: 12.5px; }
.num { font-variant-numeric: tabular-nums; text-align: right; }
.nowrap { white-space: nowrap; }
.truncate { max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row-actions { display: flex; gap: 6px; justify-content: flex-end; }

/* Tables that refresh in place mark freshly inserted rows, so an arriving
   client is noticed without the rest of the list moving or blinking. */
@keyframes row-enter { from { opacity: 0; } to { opacity: 1; } }
.row-enter { animation: row-enter 0.22s ease-out; }
@media (prefers-reduced-motion: reduce) { .row-enter { animation: none; } }

.empty { padding: 44px 20px; text-align: center; color: var(--text-3); display: grid; gap: 8px; justify-items: center; }
.empty svg { width: 34px; height: 34px; opacity: 0.5; }
.empty p { font-size: 13.5px; }

.skeleton { border-radius: 8px; background: linear-gradient(90deg, var(--surface-2), var(--surface-3), var(--surface-2)); background-size: 200% 100%; animation: shimmer 1.4s linear infinite; }
@keyframes shimmer { to { background-position: -200% 0; } }

/* Список стран: колонки, чтобы 250 галочек не растянулись на экран в высоту. */
.geo-countries {
  display: grid; gap: 4px 16px; max-height: 320px; overflow: auto;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  padding: 4px 0 10px;
}
.geo-country { justify-content: flex-start; gap: 8px; padding: 3px 0; font-size: 13px; }

/* Строка фильтров под шапкой карточки: поля стоят в ряд и не растягиваются
   на всю ширину — иначе три селекта занимают пол-экрана по вертикали. */
.filter-bar { display: flex; gap: 9px; flex-wrap: wrap; align-items: center; padding: 0 0 14px; }
.filter-bar select { width: auto; min-width: 170px; }
.filter-bar input[type="text"], .filter-bar input[type="search"] { width: auto; flex: 1 1 200px; max-width: 320px; }
.filter-bar .check { gap: 8px; white-space: nowrap; }
.filter-bar input[type="number"] { width: auto; }

/* -------------------------------------------------------- toolbars */
.toolbar { display: flex; gap: 9px; flex-wrap: wrap; align-items: center; }
/* Not scoped to .toolbar: the search box is also used inside .card-head (the
   live-traffic panel), where without these rules the magnifier icon loses its
   absolute positioning and drifts out of the field. */
.search { position: relative; flex: 0 1 300px; min-width: 180px; max-width: 340px; }
.search svg { position: absolute; left: 11px; top: 50%; transform: translateY(-50%); width: 15px; height: 15px; color: var(--text-3); pointer-events: none; }
.search input { padding-left: 34px; }

.seg { display: inline-flex; padding: 3px; gap: 3px; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm); }
.seg button {
  padding: 6px 13px;
  border: 0; border-radius: 6px;
  background: transparent;
  color: var(--text-2);
  font-size: 12.5px; font-weight: 600;
  cursor: pointer;
  transition: background 0.18s var(--ease), color 0.18s var(--ease);
}
.seg button.is-on { background: var(--surface); color: var(--text); box-shadow: var(--shadow-1); }

/* ----------------------------------------------------- live stream */
.stream { display: grid; gap: 5px; max-height: 560px; overflow-y: auto; padding-right: 4px; }
.stream-row {
  display: grid;
  grid-template-columns: 66px 74px minmax(0, 1fr) auto;
  gap: 11px;
  align-items: baseline;
  padding: 8px 11px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  font-size: 12.5px;
  animation: stream-in 0.34s var(--ease) both;
}
@keyframes stream-in { from { opacity: 0; transform: translateX(-10px); } }
.stream-time { color: var(--text-3); font-variant-numeric: tabular-nums; font-size: 11.5px; }

/* Broker-event rows carry a variable-width label (e.g. "отказ доступа") that
   overflows the fixed 74px badge column of the message grid and collides with
   the detail text. They lay out with flexbox instead, so the label sizes to
   its own text and the detail flows beside it. */
.stream-row.stream-event { display: flex; align-items: baseline; gap: 11px; }
.stream-event > span:not(.stream-payload) { flex: none; }
.stream-event .stream-payload { flex: 1; min-width: 0; grid-column: auto; }
.stream-topic { font-family: "Cascadia Code", Consolas, monospace; color: var(--accent); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stream-payload { grid-column: 3 / -1; color: var(--text-2); font-family: "Cascadia Code", Consolas, monospace; font-size: 12px; word-break: break-word; white-space: pre-wrap; max-height: 76px; overflow: hidden; }
.stream-meta { display: flex; gap: 5px; align-items: center; }

/* ---------------------------------------------------------- toasts */
.toasts { position: fixed; right: 18px; bottom: 18px; z-index: 80; display: grid; gap: 9px; justify-items: end; }
.toast {
  display: flex; align-items: center; gap: 10px;
  min-width: 250px; max-width: 400px;
  padding: 12px 15px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-3);
  font-size: 13.5px;
  animation: toast-in 0.34s var(--ease) both;
}
.toast.is-out { animation: toast-out 0.26s var(--ease-in) both; }
.toast.is-good { border-left-color: var(--good); }
.toast.is-bad { border-left-color: var(--danger); }
.toast.is-warn { border-left-color: var(--warn); }
.toast svg { width: 17px; height: 17px; flex: none; }
.toast.is-good svg { color: var(--good); }
.toast.is-bad svg { color: var(--danger); }
.toast.is-warn svg { color: var(--warn); }
@keyframes toast-in { from { opacity: 0; transform: translateX(30px) scale(0.96); } }
@keyframes toast-out { to { opacity: 0; transform: translateX(30px) scale(0.96); } }

/* ------------------------------------------------------ cookie bar */
.cookie-bar {
  position: fixed;
  left: 50%;
  bottom: 18px;
  transform: translateX(-50%);
  z-index: 60;
  width: min(720px, calc(100% - 28px));
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 13px 16px;
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  backdrop-filter: blur(18px) saturate(1.3);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow-3);
  animation: cookie-in 0.5s var(--ease) both;
}
.cookie-bar.is-out { animation: cookie-out 0.3s var(--ease-in) both; }
@keyframes cookie-in { from { opacity: 0; transform: translate(-50%, 20px); } }
@keyframes cookie-out { to { opacity: 0; transform: translate(-50%, 20px); } }

.cookie-icon { width: 26px; height: 26px; flex: none; color: var(--accent); }
.cookie-text { flex: 1; font-size: 13px; color: var(--text-2); line-height: 1.5; }
.cookie-actions { display: flex; gap: 8px; flex: none; }

@media (max-width: 560px) {
  .cookie-bar { flex-wrap: wrap; bottom: 12px; }
  .cookie-icon { display: none; }
  .cookie-text { flex-basis: 100%; }
  .cookie-actions { width: 100%; justify-content: flex-end; }
}

/* ---------------------------------------------------------- modals */
.modal-root { position: fixed; inset: 0; z-index: 70; display: grid; place-items: center; padding: 20px; background: rgba(0, 0, 0, 0.55); backdrop-filter: blur(4px); animation: fade 0.2s var(--ease); }
@keyframes fade { from { opacity: 0; } }
.modal {
  width: min(560px, 100%);
  max-height: 88dvh;
  overflow-y: auto;
  padding: 24px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-3), var(--glow-soft);
  animation: modal-in 0.3s var(--ease);
}
/* A hairline of accent light along the top edge of every dialog. */
.modal::before {
  content: "";
  position: sticky;
  top: 0;
  display: block;
  height: 2px;
  /* Отрицательный верхний отступ вытягивает полоску на самую кромку окна, а
     нижний задаёт расстояние до заголовка. Оно должно быть не меньше боковых
     24px, иначе заголовок выглядит прижатым к светящейся линии. */
  margin: -24px -24px 26px;
  background: linear-gradient(90deg, transparent, var(--accent), transparent);
  opacity: 0.7;
}
@keyframes modal-in { from { opacity: 0; transform: translateY(18px) scale(0.97); } }
.modal h3 { font-size: 18px; margin-bottom: 4px; }
.modal-sub { color: var(--text-2); font-size: 13.5px; margin-bottom: 18px; }
.modal-body { display: grid; gap: 14px; }
.modal-foot { display: flex; justify-content: flex-end; gap: 9px; margin-top: 20px; }

/* --------------------------------------------------------- details */
.kv { display: grid; grid-template-columns: auto 1fr; gap: 8px 18px; font-size: 13.5px; }
.kv dt { color: var(--text-3); }
.kv dd { margin: 0; font-variant-numeric: tabular-nums; word-break: break-all; }

/* Grip and position number lead each rule, then filter, access, effect and the
   remove button. The order is meaningful — first match wins — so it is shown
   and can be rearranged. */
.acl-row { display: grid; grid-template-columns: auto auto minmax(0, 1fr) 130px 110px auto; gap: 8px; align-items: center; }

/* -------------------------------------------------------- settings */
.settings-group { display: grid; gap: 16px; }
.settings-row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 260px); gap: 16px; align-items: center; padding: 13px 0; border-bottom: 1px solid var(--border); }
.settings-row:last-child { border-bottom: 0; }
.settings-row h4 { font-size: 14px; font-weight: 600; }
.settings-row p { font-size: 12.5px; color: var(--text-3); margin-top: 2px; }
.settings-row .control { justify-self: end; width: 100%; }

/* Build stamp under the uptime: version, then the time and date of the build. */
.build-line { font-size: 11px; line-height: 1.5; color: var(--text-3); font-variant-numeric: tabular-nums; }
.build-line:first-of-type { margin-top: 6px; }

/* ------------------------------------------- mqtt auth settings */
/* The active backend, with the button that opens the picker. */
.auth-current {
  display: flex;
  gap: 16px;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  padding: 14px 16px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.auth-current-label { display: block; font-size: 11.5px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-3); }
.auth-current-name { display: block; font-size: 16px; font-weight: 650; margin-top: 3px; }
.auth-current-desc { font-size: 12.5px; color: var(--text-3); margin-top: 3px; max-width: 62ch; }

/* The picker itself: one button per backend, stacked inside the modal. */
.auth-picker { display: grid; gap: 8px; margin-top: 4px; }
.auth-option {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 2px 12px;
  padding: 12px 14px;
  text-align: left;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  cursor: pointer;
  color: inherit;
  font: inherit;
  transition: border-color 0.15s, background 0.15s;
}
.auth-option:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, var(--surface-2)); }
.auth-option.is-on { border-color: var(--accent); }
.auth-option-name { grid-column: 1; grid-row: 1; font-weight: 650; font-size: 14px; }
.auth-option-desc { grid-column: 1; grid-row: 2; font-size: 12.5px; color: var(--text-3); line-height: 1.45; }
/* The tick is pinned to the second column so it cannot push the name across. */
.auth-option-tick { grid-column: 2; grid-row: 1 / span 2; align-self: center; color: var(--accent); font-size: 18px; }

/* Stacked fields: title, help, examples, then the control at full width. */
.auth-fields { display: grid; gap: 4px; }
.auth-field { display: grid; gap: 8px; padding: 16px 0; border-bottom: 1px solid var(--border); }
.auth-field:last-child { border-bottom: 0; }
.auth-field h4 { font-size: 14px; font-weight: 650; }
.auth-field-hint { font-size: 12.5px; color: var(--text-3); line-height: 1.55; max-width: 84ch; }
.auth-field input[type="text"], .auth-field select, .auth-field textarea, .auth-field input[type="number"] { width: 100%; }
.auth-field-spacer { display: none; }

/* Clickable ready-made values: clicking one fills the field below. */
.auth-examples { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.auth-examples-label { font-size: 11.5px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--text-3); }
.auth-example {
  padding: 5px 9px;
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 12px;
  color: var(--text-2);
  background: var(--surface-2);
  border: 1px dashed var(--border);
  border-radius: 7px;
  cursor: pointer;
  max-width: 100%;
  overflow-wrap: anywhere;
  text-align: left;
  transition: border-color 0.15s, color 0.15s;
}
.auth-example:hover { border-color: var(--accent); border-style: solid; color: var(--text); }

/* Collapsible reference: schema, sample files, request and response shapes. */
.auth-snippet > summary { cursor: pointer; font-size: 12.5px; color: var(--accent); padding: 4px 0; }
.auth-snippet pre {
  margin-top: 6px;
  padding: 12px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow-x: auto;
  font-size: 12px;
  line-height: 1.5;
}
.auth-area { min-height: 76px; resize: vertical; line-height: 1.5; padding: 10px 12px; }

/* A hint line that carries a verdict — the auth backend test result. */
.hint.is-good { color: var(--good); }
.hint.is-bad { color: var(--danger); }
.hint.is-warn { color: var(--warn); }

/* ------------------------------------------------------ language menu */
.lang-menu { position: relative; }
.lang-pop {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 40;
  min-width: 150px;
  padding: 6px;
  display: grid;
  gap: 2px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-3);
}
.lang-option {
  padding: 8px 10px;
  text-align: left;
  font-size: 13.5px;
  background: none;
  border: 0;
  border-radius: 7px;
  color: var(--text-2);
  cursor: pointer;
}
.lang-option:hover { background: var(--surface-2); color: var(--text); }
.lang-option.is-on { color: var(--accent); font-weight: 650; }

/* -------------------------------------------------- reorderable rows */
/* Rules are evaluated top to bottom, so the order is part of the setup: each
   row carries a grip to drag it by and its position number. */
.drag-handle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 30px;
  padding: 0;
  color: var(--text-3);
  background: none;
  border: 0;
  border-radius: 6px;
  cursor: grab;
}
.drag-handle:hover { color: var(--text); background: var(--surface-2); }
.drag-handle:active { cursor: grabbing; }
.drag-handle:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.is-dragging { opacity: 0.45; }
.acl-index, .rule-index {
  min-width: 18px;
  font-size: 11.5px;
  font-weight: 700;
  color: var(--text-3);
  font-variant-numeric: tabular-nums;
  text-align: center;
}
.rule-head { display: flex; align-items: center; gap: 4px; margin-bottom: 10px; }

/* ---------------------------------------------------------- rules */
/* One card per rule: a grid of labelled controls with its actions beneath. */
.rule-list { display: grid; gap: 12px; }
.rule-card { padding: 14px; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm); }
.rule-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; }
.rule-field { display: grid; gap: 5px; align-content: start; min-width: 0; }
.rule-field > span { font-size: 12.5px; font-weight: 600; color: var(--text-2); }
.rule-field > small { font-size: 11.5px; color: var(--text-3); line-height: 1.4; }
.rule-field input[type="text"], .rule-field select { width: 100%; }
.rule-foot { display: flex; gap: 8px; justify-content: flex-end; margin-top: 12px; align-items: center; }

/* What a limit rule has actually done, on the card that does it. A rule that
   silently eats messages should be seen eating them. */
.rule-stats { display: flex; flex-wrap: wrap; gap: 4px 14px; font-size: 12px; }
.rule-stats .is-warn { color: var(--warn); }
/* The secret is shown once, so it gets room to be selected and copied. */
.token-secret { padding: 12px; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm); word-break: break-all; font-size: 13px; }

/* ---------------------------------------------------------- help */
.help-layout { display: grid; grid-template-columns: 232px minmax(0, 1fr); gap: 24px; align-items: start; }

.help-toc {
  position: sticky;
  top: calc(var(--topbar-h) + 16px);
  align-self: start;
  padding: 14px;
  background: color-mix(in srgb, var(--surface) 86%, transparent);
  backdrop-filter: blur(12px);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.help-toc-title { font-size: 11.5px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-3); margin-bottom: 8px; padding: 0 8px; }
.help-toc a {
  display: block;
  padding: 7px 10px;
  border-radius: 8px;
  color: var(--text-2);
  font-size: 13.5px;
  text-decoration: none;
  cursor: pointer;
  border-left: 2px solid transparent;
  transition: background 0.15s var(--ease), color 0.15s var(--ease), border-color 0.15s var(--ease);
}
.help-toc a:hover { background: var(--surface-2); color: var(--text); }
.help-toc a.is-active { color: var(--accent); background: var(--accent-soft); border-left-color: var(--accent); }

.help-content { display: grid; gap: 16px; min-width: 0; }
.help-intro { font-size: 15px; color: var(--text-2); line-height: 1.6; }

.help-section { scroll-margin-top: calc(var(--topbar-h) + 16px); }
.help-section h3 {
  font-size: 17px;
  margin-bottom: 12px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border);
  display: flex; align-items: center; gap: 9px;
}
.help-section h3::before {
  content: "";
  width: 4px; height: 18px;
  border-radius: 3px;
  background: linear-gradient(var(--accent), var(--accent-2));
  box-shadow: var(--glow);
}
.help-sub { font-size: 14px; font-weight: 650; margin: 6px 0 2px; }
.help-p { font-size: 14px; color: var(--text-2); line-height: 1.65; }
.help-p b, .help-list b { color: var(--text); font-weight: 650; }
.help-p code, .help-list code, .help-steps code {
  font-family: "Cascadia Code", Consolas, monospace;
  font-size: 0.88em;
  background: var(--surface-3);
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 1px 6px;
  color: var(--accent);
  word-break: break-word;
}
.help-list, .help-steps { margin: 4px 0; padding-left: 22px; display: grid; gap: 6px; }
.help-list li, .help-steps li { font-size: 14px; color: var(--text-2); line-height: 1.6; }
.help-list li::marker { color: var(--accent); }
.help-steps li::marker { color: var(--text-3); font-weight: 700; }

.help-code {
  margin: 4px 0;
  padding: 13px 15px;
  background: var(--bg-deep);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow-x: auto;
  font-family: "Cascadia Code", Consolas, monospace;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--text);
  white-space: pre;
}

.help-note {
  display: flex; gap: 11px; align-items: flex-start;
  margin: 6px 0;
  padding: 12px 14px;
  border-radius: var(--radius-sm);
  background: var(--accent-soft);
  border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent);
  font-size: 13.5px; color: var(--text-2); line-height: 1.6;
}
.help-note.is-warn { background: color-mix(in srgb, var(--warn) 12%, transparent); border-color: color-mix(in srgb, var(--warn) 34%, transparent); }
.help-note svg { width: 18px; height: 18px; flex: none; margin-top: 1px; color: var(--accent); }
.help-note.is-warn svg { color: var(--warn); }

.help-table-wrap { overflow-x: auto; margin: 4px 0; border: 1px solid var(--border); border-radius: var(--radius-sm); }
.help-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.help-table th { text-align: left; padding: 9px 12px; background: var(--surface-2); border-bottom: 1px solid var(--border); font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-3); white-space: nowrap; }
.help-table td { padding: 9px 12px; border-bottom: 1px solid var(--border); color: var(--text-2); vertical-align: top; }
.help-table tr:last-child td { border-bottom: 0; }
.help-table code { font-family: "Cascadia Code", Consolas, monospace; font-size: 12px; color: var(--accent); }

@media (max-width: 860px) {
  .help-layout { grid-template-columns: 1fr; }
  .help-toc { position: static; }
}

/* ------------------------------------------------------ responsive */
@media (max-width: 1024px) {
  .auth-shell { grid-template-columns: 1fr; }
  .auth-hero { display: none; }
  .auth-panel { padding: 24px; }
}

@media (max-width: 900px) {
  .sidebar { transform: translateX(-100%); box-shadow: var(--shadow-3); }
  .sidebar.is-open { transform: none; }
  .sidebar-close { display: grid; }
  .main { margin-left: 0; }
  .menu-btn { display: grid; }
  .views { padding: 16px; }
  .topbar { padding: 0 14px; }
  .stream-row { grid-template-columns: 60px minmax(0, 1fr); }
  .stream-meta { grid-column: 2; }
  .stream-payload { grid-column: 1 / -1; }
  .settings-row { grid-template-columns: 1fr; }
  .settings-row .control { justify-self: stretch; }
  .acl-row { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 560px) {
  .tile-value { font-size: 25px; }
  .toasts { left: 14px; right: 14px; justify-items: stretch; }
  .toast { max-width: none; }
}

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

/* ------------------------------------------------------------ about */
/* The About page is a single card, so it sits in the middle of the view rather
   than hugging the top-left corner like a data screen. */
/* The About page is a centred stack: the card, then the release history under
   it. "safe" keeps the top of a tall stack reachable instead of centring it
   out of the top of the viewport. */
.view:has(> .about-card) {
  display: grid;
  justify-items: center;
  align-content: safe center;
  gap: 18px;
  min-height: calc(100vh - var(--topbar-h) - 80px);
}
.about-card { width: 100%; max-width: 640px; padding: 26px; display: grid; gap: 22px; }
.about-head { display: flex; align-items: center; gap: 18px; }
.about-mark { color: var(--accent); stroke-width: 2.4; flex: none; }
.about-name { font-size: 22px; letter-spacing: -0.02em; }
.about-tagline { margin-top: 4px; font-size: 13.5px; color: var(--text-2); line-height: 1.5; }
.about-facts { display: grid; gap: 2px; }
.about-row {
  display: grid;
  grid-template-columns: 150px minmax(0, 1fr);
  gap: 12px;
  align-items: center;
  padding: 9px 0;
  border-bottom: 1px solid var(--border);
}
.about-row:last-child { border-bottom: 0; }
.about-label { font-size: 12.5px; color: var(--text-3); }
.about-value { display: flex; align-items: center; gap: 10px; font-size: 14px; font-variant-numeric: tabular-nums; }
.about-mail { color: var(--accent); text-decoration: none; }
.about-mail:hover { text-decoration: underline; }
.about-points { display: grid; gap: 8px; margin: 0; padding: 0; list-style: none; }
.about-points li { display: flex; align-items: center; gap: 9px; font-size: 13.5px; color: var(--text-2); }
.about-points svg { color: var(--good); flex: none; }

/* Release history, under the About card and the same width. */
.about-log { gap: 14px; }
.release-title { font-size: 16px; }
.about-log .section-sub { margin-top: -8px; }
.release-list { display: grid; gap: 18px; }
.release-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 7px; }
.release-date { font-size: 12.5px; color: var(--text-3); }
.release-lines { margin: 0; padding-left: 18px; display: grid; gap: 5px; }
.release-lines li { font-size: 13px; color: var(--text-2); line-height: 1.55; }
.release-lines li::marker { color: var(--text-3); }

/* The machine under the broker: processor, memory, disk. */
.gauge-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 18px; }
.gauge { display: grid; gap: 8px; }
.gauge-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.gauge-label { font-size: 12.5px; color: var(--text-3); }
.gauge-value { font-size: 20px; font-variant-numeric: tabular-nums; letter-spacing: -0.02em; }
.gauge-track { height: 8px; border-radius: 999px; background: var(--surface-3); overflow: hidden; }
.gauge-fill {
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: var(--accent);
  transition: width 0.5s ease, background-color 0.3s ease;
}
.gauge-fill.is-warn { background: var(--warn); }
.gauge-fill.is-bad { background: var(--danger); }
.gauge-detail { font-size: 12px; }
@media (prefers-reduced-motion: reduce) { .gauge-fill { transition: none; } }

/* "Update available" under the build stamp: a quiet line that becomes a link
   to the download. Accent-coloured, because it is the one thing in the footer
   worth noticing. */
.update-line {
  display: block;
  margin-top: 6px;
  font-size: 11.5px;
  color: var(--accent);
  text-decoration: none;
  line-height: 1.4;
}
.update-line::before { content: "↑ "; }
.update-line:hover { text-decoration: underline; }

/* Demo mode: the broker refuses every change, and the menu says so in the one
   place that is visible from every page. Red, because it is a limitation, not
   a feature — a visitor must not spend ten minutes editing a form that will be
   refused on save. */
.demo-badge {
  display: inline-block;
  margin: 0 0 8px;
  padding: 3px 9px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--danger);
  background: color-mix(in srgb, var(--danger) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--danger) 45%, transparent);
  border-radius: var(--radius-sm);
}
