:root {
  --bg: #f5f6f8;
  --panel: #ffffff;
  --ink: #1a1d23;
  --muted: #6b7280;
  --line: #e3e6ea;
  --brand: #3B7FC4;
  --brand-light: #57B4E2;
  --brand-deep: #3663AC;
  --brand-navy: #22325F;
  --ok: #15803d;
  --ok-bg: #dcfce7;
  --warn: #b45309;
  --warn-bg: #fef3c7;
  --bad: #b91c1c;
  --bad-bg: #fee2e2;
  /* Blue: a state worth noticing that is not yet a problem — a trial still
     running. Deliberately not the accent colour, which marks links. */
  --info: #1d4ed8;
  --info-bg: #dbeafe;
  --sidebar: #1B2A4E;
  --radius: 4px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 14px/1.45 "Segoe UI", system-ui, -apple-system, sans-serif;
}

a { color: var(--brand); }

.shell { display: flex; min-height: 100vh; }

/* ---------- sidebar ---------- */
.sidebar {
  width: 200px;
  flex: 0 0 200px;
  background: var(--sidebar);
  color: #cfd4dc;
  display: flex;
  flex-direction: column;
  padding: 14px 0;
}
.brand {
  font-size: 20px; font-weight: 700; letter-spacing: .5px;
  color: #fff; padding: 0 20px 20px;
}
.brand span { color: #7aa2f7; font-weight: 400; }
.sidebar nav { display: flex; flex-direction: column; flex: 1; }
.sidebar nav a {
  color: #cfd4dc; text-decoration: none;
  padding: 7px 16px; border-left: 3px solid transparent; font-size: 13.5px;
}
.sidebar nav a:hover { background: #24365F; color: #fff; }
/* The active item. It used to be #2A3D6B against a #1B2A4E rail — four
   percent of lightness apart, which on a dim screen was no signal at all.
   Now three things agree: a tinted wash in the brand blue, a wider and
   brighter edge marker, and the icon picked out in the same blue. The
   marker widens from 3px to 4px and the padding loses the same 1px, so
   the label does not shift as you move between items. */
.sidebar nav a.on {
  background: linear-gradient(90deg, rgba(87,180,226,.30) 0%, rgba(87,180,226,.09) 100%);
  color: #fff;
  font-weight: 650;
  border-left-color: var(--brand-light);
  border-left-width: 4px;
  padding-left: 15px;
}
/* stroke is currentColor, so this tints the glyph and nothing else. */
.sidebar nav a.on .nav-ico { color: #8FCBEF; }
.who { padding: 16px 20px 0; border-top: 1px solid #2E4272; font-size: 13px; }
.who .name { color: #fff; font-weight: 600; }
.who .role { color: #8b93a1; text-transform: capitalize; margin-bottom: 8px; }
.who .out { color: #8b93a1; font-size: 13px; }

/* ---------- main ---------- */
.main { flex: 1; padding: 18px 22px 44px; min-width: 0; }
.main h1 { margin: 0 0 13px; font-size: 20px; font-weight: 650; }
.main h2 { margin: 22px 0 9px; font-size: 15.5px; font-weight: 620; }

.banner {
  padding: 7px 12px; border-radius: var(--radius);
  font-size: 12.5px; margin-bottom: 14px; border: 1px solid transparent;
}
.banner.ok   { background: var(--ok-bg);   color: var(--ok);   border-color: #bbf7d0; }
.banner.warn { background: var(--warn-bg); color: var(--warn); border-color: #fde68a; }
.banner.bad  { background: var(--bad-bg);  color: var(--bad);  border-color: #fecaca; }

/* ---------- tiles ---------- */
/* Tile rows are usually the first thing under the page title, so this sits
   tighter than a mid-page heading — 22px of top margin against the banner
   above it read as a gap rather than as a label for what follows. */
.main h2.tiles-head { margin-top: 4px; }

.tiles {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
  margin-bottom: 8px;
}
.tile {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 10px 12px;
}
.tile .k { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .4px; }
.tile .v { font-size: 21px; font-weight: 680; margin-top: 2px; line-height: 1.1; }
.tile .s { font-size: 11px; color: var(--muted); margin-top: 2px; }
.tile.accent-ok   .v { color: var(--ok); }
.tile.accent-warn .v { color: var(--warn); }
.tile.accent-bad  .v { color: var(--bad); }

/* ---------- panels & charts ---------- */
.grid2 { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); }
.panel {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 12px 14px;
}
/* The panel heading: a hairline rule with a short accent mark.
   The title still has to separate itself from the content — on a panel that is
   nothing but table rows it would otherwise read as the first row — but it no
   longer needs a filled band to do it. The rule does the separating and the
   mark gives the eye something to catch when scanning a page of eleven panels.

   This replaced an edge-to-edge gradient band. The gradient was the dated part:
   a tinted, shaded strip belonged to a period when a panel needed visible
   chrome to read as a panel, where the border and background now do that on
   their own. The negative horizontal margins went with it, so the heading sits
   inside the panel's padding like everything else and no longer has to round
   its own corners to follow the panel's. */
.panel h3 {
  margin: -2px 0 11px;
  padding: 0 0 9px 10px;
  font-size: 13.5px;
  font-weight: 640;
  letter-spacing: -0.01em;
  color: var(--ink);
  border-bottom: 1px solid var(--line);
  position: relative;
}
/* Stops short of the rule rather than meeting it, so the mark reads as a tick
   against the title and not as a corner joining two borders. */
.panel h3::before {
  content: "";
  position: absolute;
  left: 0;
  top: 1px;
  bottom: 10px;
  width: 3px;
  border-radius: 2px;
  background: var(--brand);
}
/* A hint sits directly under the heading rule, so it keeps only its own gap. */
.panel .hint { margin: -4px 0 9px; font-size: 11.5px; color: var(--muted); }
.chart-wrap { position: relative; height: 215px; }
/* A supporting chart rather than the point of its panel — the hourly traffic
   strip on a gateway page sits under its details, not instead of them. */
.chart-wrap.chart-short { height: 140px; }


/* A panel standing on its own in the page flow, rather than inside a grid.
   These used to be introduced by an <h2>, which supplied the space above
   them; now that the title lives in the panel's own header band there is
   nothing between the panel and whatever precedes it, and the ping charts
   sat flush against the grid above. A panel that still follows its own
   heading keeps that heading's spacing instead of adding to it. */
.main > .panel,
.main > .grid2,
.main > .grid3 { margin-top: 16px; }
/* A block introduced by its own heading takes that heading's spacing
   rather than adding to it. Adjacent margins collapse, so this is about
   intent as much as arithmetic. */
.main > h2 + .panel,
.main > h2 + .grid2,
.main > h2 + .grid3 { margin-top: 0; }


/* ---------- tables ---------- */
.table-wrap { overflow-x: auto; background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); }
table { border-collapse: collapse; width: 100%; font-size: 12.5px; }
th, td { padding: 5px 10px; text-align: left; border-bottom: 1px solid var(--line); white-space: nowrap; }
th { background: #fafbfc; font-weight: 620; color: #444b57; position: sticky; top: 0; }
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover { background: #f8fafc; }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }

.pill {
  display: inline-block; padding: 2px 8px; border-radius: 3px;
  font-size: 12px; font-weight: 600;
}
.pill.ok      { background: var(--ok-bg);   color: var(--ok); }
.pill.info    { background: var(--info-bg); color: var(--info); }
.pill.warn    { background: var(--warn-bg); color: var(--warn); }
.pill.bad     { background: var(--bad-bg);  color: var(--bad); }
.pill.neutral { background: #eef1f5;        color: #5a6270; }

/* ---------- forms & buttons ---------- */
.btn {
  display: inline-block; background: var(--brand); color: #fff; border: 0;
  padding: 7px 14px; border-radius: 3px; font-size: 13.5px; font-weight: 600;
  cursor: pointer; text-decoration: none;
}
.btn:hover { background: var(--brand-deep); }
.btn[disabled] { background: #9aa4b2; cursor: not-allowed; }
.btn.secondary { background: #eef1f5; color: #333a45; }
.btn.secondary:hover { background: #e2e6ec; }

.filters { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; align-items: center; }
.filters input, .filters select {
  padding: 6px 9px; border: 1px solid var(--line); border-radius: 3px;
  font-size: 13.5px; background: #fff; color: var(--ink);
}
.filters input[type=search] { min-width: 240px; }

/* ---------- login ---------- */
.login-page { display: flex; align-items: center; justify-content: center; min-height: 100vh; background: var(--sidebar); }
.login-card { background: #fff; padding: 30px 32px; border-radius: 6px; width: 340px; box-shadow: 0 10px 40px rgba(0,0,0,.3); }
.login-card h1 { margin: 0 0 4px; font-size: 22px; }
.login-card p.sub { margin: 0 0 20px; color: var(--muted); font-size: 13.5px; }
.login-card label { display: block; font-size: 13px; font-weight: 600; margin-bottom: 5px; }
.login-card input { width: 100%; padding: 10px; border: 1px solid var(--line); border-radius: 3px; margin-bottom: 14px; font-size: 14px; }
.login-card .btn { width: 100%; }
.login-card .check {
  display: flex; align-items: center; gap: 8px;
  /* The inputs above carry 14px of bottom margin; this sits in that gap
     rather than adding its own, so the button keeps its spacing. */
  margin: -4px 0 16px;
  font-size: 13px; font-weight: 400; color: var(--muted); cursor: pointer;
}
.login-card .check input {
  width: auto; margin: 0; accent-color: var(--brand); cursor: pointer;
}

.err { background: var(--bad-bg); color: var(--bad); padding: 9px 12px; border-radius: 3px; font-size: 13.5px; margin-bottom: 14px; }

.muted { color: var(--muted); }
.small { font-size: 12.5px; }

/* Text-weight status colours, for a figure inside a dense table that needs to
   read as a warning without a pill's background box around it. Same palette as
   .pill and .banner, so a gateway near its connection ceiling looks the same
   shade of wrong wherever it is shown. */
.txt-ok   { color: var(--ok); }
.txt-warn { color: var(--warn); font-weight: 600; }
.txt-bad  { color: var(--bad);  font-weight: 600; }
pre.log {
  background: #11151c; color: #d7dce5; padding: 14px; border-radius: var(--radius);
  font: 12.5px/1.55 Consolas, "Cascadia Mono", monospace; overflow-x: auto; max-height: 420px;
}

@media (max-width: 780px) {
  .shell { flex-direction: column; }
  .sidebar { width: 100%; flex: none; flex-direction: row; align-items: center; padding: 10px; gap: 10px; overflow-x: auto; }
  .sidebar nav { flex-direction: row; }
  .brand { padding: 0 10px 0 6px; }
  .who { border-top: 0; padding: 0 10px; display: flex; gap: 10px; align-items: center; }
  .main { padding: 18px 14px 40px; }
}

/* ---------- sortable tables ---------- */
th.sortable { cursor: pointer; user-select: none; position: relative; padding-right: 17px; }
th.sortable:hover { background: #f1f4f8; }
th.sortable::after {
  content: "⇅"; position: absolute; right: 8px; color: #b3bac4; font-size: 11px;
}
th.sortable.sorted::after { color: var(--brand); }
th.sortable[data-dir="asc"]::after  { content: "↑"; }
th.sortable[data-dir="desc"]::after { content: "↓"; }
th.sortable:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }

tr.row-selected > td { background: #eff5ff; }

/* ---------- stacked form (gateway details) ---------- */
.stack label {
  display: block; font-size: 13px; font-weight: 600; margin: 12px 0 5px; color: #444b57;
}
.stack label:first-child { margin-top: 0; }
.stack input[type=text],
.stack textarea {
  width: 100%; padding: 9px 10px; border: 1px solid var(--line);
  border-radius: 3px; font-size: 14px; font-family: inherit; background: #fff; color: var(--ink);
}
.stack textarea { resize: vertical; }
.stack .btn { margin-top: 14px; }

/* Key/value tables inside panels have no header row of their own: their <th>
   is the label at the start of each row, inside the body.

   Scoped to `tbody th` for exactly that reason. Written as `.panel table th` it
   also caught the column headings of every data table in a panel, and at
   specificity (0,1,2) it beat `th.rowlink { width: 1% }` — so the 19px icon
   column on the relay table claimed 315px, a third of the width, and the
   latency matrix needed per-column widths to fight it off. A column heading
   lives in a thead and is not this. */
.panel table tbody th {
  background: transparent; font-weight: 600; color: #444b57;
  width: 38%; white-space: nowrap; position: static;
}
.panel table td { white-space: normal; word-break: break-word; }



/* ---------- three-up panel row (recent lists) ---------- */
.grid3 { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }

/* Compact tables inside a panel.
   table-layout:fixed so the icon column keeps the width it is given. Under
   auto layout the browser ignored width:1% here and handed the empty icon
   column ~165px of slack, pushing the details far to the right. */
table.mini { font-size: 13px; table-layout: fixed; width: 100%; }
table.mini th, table.mini td { padding: 6px 8px; }
table.mini th { background: transparent; border-bottom: 1px solid var(--line); position: static; }
table.mini td { overflow: hidden; text-overflow: ellipsis; }

/* ---------- row actions & external links ---------- */
td.actions { white-space: nowrap; }
td.actions a + a { margin-left: 8px; }

a.ext::after {
  content: "↗";
  font-size: 11px;
  margin-left: 2px;
  vertical-align: super;
  opacity: .7;
}
a.ext { white-space: nowrap; }

/* ---------- row icons ---------- */
th.rowlink, td.rowlink {
  width: 1%;
  white-space: nowrap;
  padding-left: 10px;
  padding-right: 6px;
}
td.rowlink a.icon + a.icon { margin-left: 4px; }

a.icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 3px;
  color: var(--muted);
  vertical-align: middle;
}
a.icon svg {
  display: block;
  width: 14px;
  height: 14px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}
a.icon:hover        { background: #e8effb; color: var(--brand); }
a.icon.ext-icon:hover { background: #e6f6f2; color: #0f766e; }

/* ---------- Evolution Anywhere portal marks ---------- */
.ea-mark { display: block; width: auto; object-fit: contain; }

/* The other row icons are 22px squares; the cloud mark is 1.81:1, so at 13px
   tall it needs 24px and overflowed the square. This slot is widened rather
   than the mark squashed. */
a.icon.ea-icon { width: 28px; }

/* An image, not a glyph, so hover cannot tint it - it lifts instead. */
a.icon.ea-icon:hover { background: #EAF2FB; }
a.icon.ea-icon .ea-mark { transition: transform .12s ease; }
a.icon.ea-icon:hover .ea-mark { transform: scale(1.08); }

/* Inline 'portal' link: the mark leads, so the trailing arrow would be
   saying the same thing twice. */
a.ext.ext-ea { display: inline-flex; align-items: center; gap: 4px; }
a.ext.ext-ea::after { content: none; }
a.icon:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 1px;
  color: var(--brand);
}

/* Compact panel tables get a tighter icon cell.
   The icon button is a 22px box around a 14px glyph, which on top of the
   normal cell padding left a visible gap between the icon and the first
   column of detail. In the panels the box hugs the glyph and the following
   cell loses most of its left padding. */
table.mini a.icon { width: 18px; height: 18px; }
table.mini th.rowlink,
table.mini td.rowlink { padding-left: 8px; padding-right: 0; }
table.mini th.rowlink + th,
table.mini td.rowlink + td { padding-left: 4px; }
table.mini td.rowlink a.icon + a.icon { margin-left: 2px; }

/* ---------- clickable tiles ---------- */
a.tile {
  display: block;
  text-decoration: none;
  color: inherit;
  transition: border-color .12s, box-shadow .12s, transform .12s;
}
a.tile:hover {
  border-color: #c3d4f5;
  box-shadow: 0 2px 10px rgba(37, 99, 235, .10);
  transform: translateY(-1px);
}
a.tile:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
a.tile .k::after {
  content: "›";
  float: right;
  color: #c2c9d4;
  font-weight: 700;
  font-size: 14px;
  line-height: 1;
}
a.tile:hover .k::after { color: var(--brand); }

/* Numbers inside the type/status matrix link like plain text. */
td.num a { text-decoration: none; }
td.num a:hover { text-decoration: underline; }

/* A cell marked .grow absorbs the table's spare width.
   Without it, `width: 1%` on the icon column is only a hint under
   table-layout:auto and the browser hands it the slack instead — which left
   a ~140px gap between the icon and the first real column. */
th.grow, td.grow { width: 100%; }

/* Fixed layout needs a real width on the icon column. One icon in the server
   and client panels, two in the POS panel (open + portal). */
table.mini th.rowlink, table.mini td.rowlink { width: 34px; padding-left: 10px; padding-right: 0; }
table.mini.two-icons th.rowlink, table.mini.two-icons td.rowlink { width: 56px; }
table.mini th.grow, table.mini td.grow { width: auto; }

/* Under fixed layout every column except the .grow one needs a width; the
   leftover then lands on .grow (the name), which is the column worth the room. */
table.mini th.w-tag  { width: 74px; }
table.mini th.w-date { width: 116px; }
table.mini th.w-num  { width: 66px; }
table.mini th.w-info { width: 110px; }

/* ---------- Evolution Anywhere branding ---------- */

/* Sidebar lockup: gradient cloud mark + light wordmark. The supplied logo has a
   navy wordmark, which would vanish on this background, so the sidebar sets it
   in white and keeps the full-colour logo for light surfaces. */
.brand {
  display: flex; align-items: center; gap: 10px;
  padding: 2px 18px 20px;
}
.brand .mark { width: 44px; height: 24px; flex: 0 0 44px; display: block; }
.brand .word { font-size: 15px; line-height: 1.12; letter-spacing: .2px; }
.brand .word b { display: block; font-weight: 700; color: #fff; }
.brand .word i { display: block; font-style: italic; font-weight: 400; color: #8FC4E8; }

.sidebar { background: linear-gradient(180deg, #24376A 0%, var(--sidebar) 60%); }

/* Sign-in */
.login-page {
  background:
    radial-gradient(1100px 620px at 50% -12%, #33518F 0%, rgba(51,81,143,0) 70%),
    linear-gradient(160deg, #1E2E57 0%, #16223F 100%);
}
.login-card { width: 360px; padding: 32px 34px 30px; }
.login-brand { text-align: center; margin-bottom: 20px; }
.login-brand img { width: 208px; max-width: 100%; height: auto; display: block; margin: 0 auto; }
.login-card h1 { font-size: 15px; font-weight: 600; text-align: center; color: var(--muted); margin: 0 0 18px; }
.login-card p.sub { text-align: center; }
.brand { text-decoration: none; }
.brand:hover .word b { color: #DCEBF8; }

/* ---------- collapsible sidebar ---------- */

.side-top { display: flex; align-items: flex-start; gap: 6px; padding: 0 14px 14px; }
.side-top .brand { flex: 1; min-width: 0; padding: 0; }

.rail-toggle {
  flex: 0 0 24px; width: 24px; height: 24px; margin-top: 3px; padding: 0;
  display: grid; place-items: center;
  background: transparent; border: 0; border-radius: 4px;
  color: #8FA6C8; cursor: pointer;
}
.rail-toggle svg {
  width: 15px; height: 15px;
  fill: none; stroke: currentColor; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
  transition: transform .15s ease;
}
.rail-toggle:hover { background: #2A3D6B; color: #fff; }
.rail-toggle:focus-visible { outline: 2px solid var(--brand-light); outline-offset: 1px; }

/* The phone menu button. It is in the markup on every page so the drawer needs
   no separate template, but above the breakpoint the sidebar is always open
   and the rail toggle beside it does the collapsing, so it stays hidden. */
.nav-toggle { display: none; }

.sidebar nav a, .who .out { display: flex; align-items: center; gap: 9px; }
.nav-ico {
  flex: 0 0 16px; width: 16px; height: 16px;
  fill: none; stroke: currentColor; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
}
.nav-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Collapsed to an icon rail. The active marker moves to the right edge
   because a 3px left border on a 52px rail crowds the icon. */
html.rail .sidebar { width: 52px; flex: 0 0 52px; }
html.rail .side-top { flex-direction: column; align-items: center; gap: 8px; padding: 0 6px 12px; }
html.rail .brand { justify-content: center; }
html.rail .brand .word { display: none; }
html.rail .rail-toggle svg { transform: rotate(180deg); }
html.rail .sidebar nav a {
  justify-content: center; gap: 0; padding: 8px 0;
  border-left-width: 0; border-right: 3px solid transparent;
}
html.rail .sidebar nav a.on {
  border-right-color: var(--brand-light);
  border-right-width: 4px;
  /* The left marker and its padding compensation do not apply here. */
  border-left-width: 0;
  padding-left: 0;
}
html.rail .nav-label { display: none; }
html.rail .who { padding: 12px 4px 0; }
html.rail .who .name, html.rail .who .role { display: none; }
html.rail .who .out { justify-content: center; }

/* ---------- tile trends ---------- */

.tile .trend {
  margin-left: 6px; font-size: 11px; font-weight: 700;
  letter-spacing: .2px; vertical-align: middle; white-space: nowrap;
}
.tile .trend.up   { color: var(--brand); }
.tile .trend.down { color: var(--muted); }

/* The rail is a desktop affordance; the narrow layout already stacks the
   nav horizontally, so neutralise it rather than let the two fight. */
@media (max-width: 780px) {
  .rail-toggle { display: none; }
  .side-top { padding: 0; }
  html.rail .sidebar { width: 100%; flex: none; }
  html.rail .side-top { flex-direction: row; padding: 0; }
  html.rail .brand .word,
  html.rail .nav-label { display: block; }
  html.rail .sidebar nav a {
    justify-content: flex-start; gap: 9px; padding: 8px 12px;
    border-right-width: 0;
  }
}
/* The full-width mark overruns a 52px rail; scale it to fit the narrower box. */
html.rail .brand .mark { width: 34px; height: 19px; flex: 0 0 34px; }

/* The 22px row icon, not the cell padding, was setting the row height floor
   once the table went compact. Trimming it is what actually buys the rows. */
a.icon { width: 19px; height: 19px; }
a.icon svg { width: 13px; height: 13px; }
.pill { padding: 1px 7px; font-size: 11.5px; }

/* ---------- user management ---------- */

.user-form { margin-bottom: 16px; max-width: 900px; }
.field-row { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; }
.field-row label { display: flex; flex-direction: column; gap: 4px; flex: 1 1 200px;
                   font-size: 12.5px; font-weight: 600; }
.field-row input, .field-row select {
  padding: 6px 9px; border: 1px solid var(--line); border-radius: 3px;
  font-size: 13.5px; font-family: inherit; background: #fff; color: var(--ink);
}

.perms { border: 1px solid var(--line); border-radius: 3px; padding: 9px 12px 11px; margin: 0 0 12px; }
.perms legend { font-size: 12.5px; font-weight: 600; padding: 0 5px; }
.perms-note { margin: 0 0 8px; }
.perms.off { opacity: .42; }
.perm-grid { display: grid; gap: 6px 14px; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
.perm { display: flex; align-items: center; gap: 7px; font-size: 13px; font-weight: 400; }
.perm input { margin: 0; }
/* The portal grant is not a page, so it sits apart from the page list. */
.perm-portal {
  grid-column: 1 / -1; margin-top: 5px; padding-top: 8px;
  border-top: 1px solid var(--line); font-weight: 600;
}
.form-actions { display: flex; gap: 8px; align-items: center; }

.inline-form { display: inline; }
.linklike {
  background: none; border: 0; padding: 0; margin: 0; cursor: pointer;
  color: var(--brand); font: inherit; text-decoration: underline;
}
.linklike:hover  { color: var(--brand-deep); }
.linklike.danger { color: var(--bad); }
td.actions > * + * { margin-left: 8px; }

/* ---------- banners ---------- */

/* The icon is an absolutely positioned mask rather than markup: banners carry
   arbitrary inline content (links, <code>, <strong>) and making the banner a
   flex container would split those into separate flex items and wreck the
   inline flow. Absolute positioning leaves the text flow untouched and keeps
   wrapped lines aligned past the icon. */
.banner {
  position: relative;
  padding: 12px 15px 12px 42px;
  border: 1px solid transparent;
  border-left-width: 3px;
  border-radius: var(--radius);
  font-size: 13.5px;
  line-height: 1.45;
  margin-bottom: 16px;
}
.banner[hidden] { display: none; }

.banner::before {
  content: "";
  position: absolute;
  left: 14px;
  top: 12px;
  width: 18px;
  height: 18px;
  background: currentColor;
  -webkit-mask-image: var(--bico);
          mask-image: var(--bico);
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: center;
          mask-position: center;
  -webkit-mask-size: contain;
          mask-size: contain;
}

.banner.ok {
  background: var(--ok-bg); color: #14652f; border-color: #a7e8bd; border-left-color: var(--ok);
  --bico: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='m8 12.2 2.8 2.8L16 9'/%3E%3C/svg%3E");
}
.banner.warn {
  background: var(--warn-bg); color: #94430a; border-color: #f6d788; border-left-color: var(--warn);
  --bico: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 3.8 21.5 20.2H2.5z'/%3E%3Cpath d='M12 10.2v3.8'/%3E%3Cpath d='M12 17.4h.01'/%3E%3C/svg%3E");
}
.banner.bad {
  background: var(--bad-bg); color: #9c1c1c; border-color: #f7bcbc; border-left-color: var(--bad);
  --bico: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='m9.2 9.2 5.6 5.6M14.8 9.2l-5.6 5.6'/%3E%3C/svg%3E");
}

/* ---------- page title banner ---------- */

/* Negative margins pull the band out past .main's padding so it spans the
   full content width rather than floating inside it. */
.page-head {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: -18px -22px 16px;
  padding: 13px 22px;
  background: linear-gradient(90deg, #E4EEFA 0%, #F0F6FC 55%, var(--panel) 100%);
  border-bottom: 1px solid #D3E1F1;
}
.page-head h1 {
  margin: 0;
  font-size: 20px;
  font-weight: 650;
  color: var(--brand-navy);
}
.page-ico {
  flex: 0 0 34px;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  border-radius: 4px;
  background: linear-gradient(150deg, var(--brand-light), var(--brand-deep));
  color: #fff;
}
/* The data-freshness strip: standing status, not an alert.
   Sits flush under the title band and spans the same full content width,
   so the two read as one header block. The negative top margin cancels the
   band's bottom margin; the rule below restores spacing for whatever
   follows. */
.freshbar {
  margin: -16px -22px 16px;
  padding: 4px 22px;
  font-size: 11.5px;
  line-height: 1.5;
  border-bottom: 1px solid transparent;
  display: flex;
  align-items: baseline;
  gap: 6px;
  flex-wrap: wrap;
}
.freshbar strong { font-weight: 620; }
.freshbar .sep { opacity: .45; }
.freshbar a { color: inherit; }
.freshbar.ok   { background: #F1F8F3; color: #2F6B45; border-bottom-color: #D8EADF; }
.freshbar.warn { background: var(--warn-bg); color: var(--warn); border-bottom-color: #FDE68A; }
.freshbar.bad  { background: var(--bad-bg);  color: var(--bad);  border-bottom-color: #FECACA; }

.page-ico svg {
  width: 19px; height: 19px;
  fill: none; stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
}

@media (max-width: 780px) {
  .page-head { margin: -18px -14px 14px; padding: 11px 14px; gap: 10px; }
  .freshbar { margin: -14px -14px 14px; padding: 4px 14px; }
  .page-ico { flex-basis: 30px; width: 30px; height: 30px; }
  .page-head h1 { font-size: 18px; }
}

/* Client-to-server link summary above the Clients filters. */
.link-summary { margin: -2px 0 12px; }
.link-summary a { text-decoration: none; border-bottom: 1px solid rgba(59,127,196,.4); }
.link-summary a:hover { border-bottom-color: var(--brand); }

/* Gateway ping cell: a compact pill with the reading time beneath it. */
.ping-cell .pill { font-size: 10.5px; padding: 1px 6px; }
.ping-when {
  display: block; margin-top: 2px;
  font-size: 10.5px; line-height: 1.2; color: var(--muted);
  white-space: nowrap;
}

/* A 24-hour comparison needs more vertical room than a category chart. */
.chart-tall { height: 300px; }
/* Reachable readings are plain text; only a failure gets pill emphasis. */
.ping-ok { font-size: 11px; font-weight: 600; color: var(--ok); white-space: nowrap; }

/* ---------- instance action bar ---------- */

/* One row near the top of a detail page holding everything an operator does to
   the object: the jump-out to the management portal, whatever opens a panel
   below, and the actions that change it.

   Deliberately quiet. The portal used to be a 520px gradient card of its own,
   which on a server page read as the point of the page - it is one action among
   several, so here it is sized like the rest and the bar itself carries no
   colour that would compete with a banner appearing under it. */
.action-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin: 0 0 12px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
}
.action-bar .pill { vertical-align: baseline; }

/* A bar button whose panel is open, so the bar says which of its buttons is
   responsible for what has appeared below it. */
.action-bar .btn.is-on {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
}

/* The portal link wears the button shape so it lines up with the real buttons
   beside it, and keeps the Evolution Anywhere mark so it still reads as a jump
   out of this application rather than another control within it. */
.portal-action { display: inline-flex; align-items: center; gap: 7px; text-decoration: none; }
.portal-action .ea-mark { display: block; }

/* What a bar button opens: an ordinary panel, tucked up against the bar so the
   two read as one unit rather than as a panel that happens to follow. */
.action-panel { margin-top: -4px; }

/* ---------- slide-down sections ---------- */

/* The height is set in pixels by the script, which measures the content and
   animates to that figure, then hands the element back to `auto` once it has
   settled. A max-height instead has to be guessed, and the guess is wrong in
   both directions: too small clips a long log, too large makes the open look
   slow because most of the transition is spent crossing empty space.

   Preferred over the grid-template-rows 0fr/1fr technique for two reasons that
   matter here rather than for any doubt about it: that technique needs the
   section to be a grid, which would override the `hidden` attribute's own
   display and quietly stop it hiding anything, and it holds the open section
   at the track size rather than letting it grow. This one hands the element
   back to height:auto once open, so a panel that gains content afterwards — a
   log arriving in the diagnostics section — simply gets taller.

   No `display` is set, so the `hidden` attribute still works normally: a
   collapsed section is out of the accessibility tree rather than merely nought
   pixels tall. The script removes it a frame before opening and puts it back
   once the collapse has run. */
.slide {
  overflow: hidden;
  transition: height .26s ease;   /* keep in step with DURATION in app.js */
}

/* A wrapper whose only job is to establish a block formatting context: a
   margin on the first child would otherwise collapse out through the section's
   own box and be missing from the height the script measures, so the panel
   would open a little short of its content. */
.slide > .slide-inner { display: flow-root; }

/* The clip is what makes the slide work, and it also cuts off any tooltip that
   escapes the section's box. Released once it has finished opening, and
   re-applied before it closes. Height goes back to auto at the same moment, so
   a panel that grows afterwards — a log arriving — is not held to the height
   it had when it opened. */
.slide[data-settled="true"] { overflow: visible; height: auto; }

@media (prefers-reduced-motion: reduce) {
  .slide { transition: none; }
}

/* ---------- portal viewer: a window over the list ---------- */

/* A <dialog> in the top layer, so it clears the sticky table headers and the
   sidebar without a z-index anywhere in this file.

   Sized to the viewport rather than to the portal: it is a full application
   with its own two-pane layout, and the usable width is what decides whether
   the frame reads as a window onto it or as a letterbox. */
.portal-dialog {
  width: min(1180px, 94vw);
  max-width: none;
  height: min(860px, 88vh);
  max-height: none;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 6px;
  box-shadow: 0 18px 50px rgba(15, 23, 42, .28);
  background: var(--panel);
  /* Column, so the frame takes whatever the header leaves. */
  flex-direction: column;
}
/* display:flex only while open — a dialog's closed state is display:none, and
   setting flex unconditionally would show it. */
.portal-dialog[open] { display: flex; }

.portal-dialog::backdrop { background: rgba(15, 23, 42, .45); }

.portal-dialog-head {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
  padding: 10px 12px;
  border-bottom: 1px solid var(--line);
  font-size: 13px;
  color: var(--muted);
}
#portal-viewer-label { font-weight: 600; color: var(--ink); }
/* The two controls travel together on the right. */
.portal-dialog-head .btn:nth-last-of-type(2) { margin-left: auto; }

/* Inside the dialog the frame fills what is left, so its own standalone
   height and border do not apply. */
.portal-dialog .portal-frame {
  flex: 1;
  height: auto;
  min-height: 0;
  border: 0;
  border-radius: 0 0 6px 6px;
}

/* The row icon whose Point Of Service is the one currently in the panel. The
   mark is an image and cannot be tinted, so the slot is filled instead — the
   same treatment its hover already uses, held on. */
a.icon.ea-icon.is-on {
  background: #DCE9F8;
  box-shadow: inset 0 0 0 1px var(--brand);
}

/* The management portal, embedded. Tall enough to use — it is a full
   application with its own two-pane layout, and anything shorter turns into a
   scrollbar inside a scrollbar. */
.portal-frame {
  display: block;
  width: 100%;
  height: 70vh;
  min-height: 420px;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: #fff;
}

/* ---------- back to list ---------- */

.backlink {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin: 0 0 14px;
  padding: 5px 12px 5px 9px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--muted);
  font-size: 12.5px;
  font-weight: 600;
  text-decoration: none;
  transition: color .12s ease, border-color .12s ease;
}
.backlink:hover {
  color: var(--brand);
  border-color: #BFD5EC;
  background: #F4F8FC;
}
.backlink:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

.backlink-ico {
  width: 14px; height: 14px; flex: 0 0 14px;
  fill: none; stroke: currentColor; stroke-width: 1.8;
  stroke-linecap: round; stroke-linejoin: round;
  /* Nudges toward the direction it takes you. */
  transition: transform .12s ease;
}
.backlink:hover .backlink-ico { transform: translateX(-2px); }

/* Gateway switch control on a SQL instance page. */
.gw-switch { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 0 0 12px; }
.gw-switch label { font-size: 12.5px; font-weight: 600; color: var(--muted); }
.gw-switch select {
  padding: 6px 9px; border: 1px solid var(--line); border-radius: 3px;
  font-size: 13.5px; font-family: inherit; background: #fff; color: var(--ink);
  max-width: 380px;
}

/* ---------- destructive action ---------- */

.danger-zone {
  display: flex; align-items: center; gap: 16px; justify-content: space-between;
  max-width: 720px; margin: 0 0 16px; padding: 12px 14px;
  border: 1px solid #F0C9C9; border-left: 3px solid var(--bad);
  border-radius: var(--radius); background: #FDF6F6;
}
.danger-zone strong { font-size: 13.5px; color: #8E1B1B; }
.danger-zone .hint { margin: 3px 0 0; }
.btn.danger { background: var(--bad); }
.btn.danger:hover { background: #8E1B1B; }
/* Delete switched off in config: present, visibly inert. */
.danger-zone.is-off { border-color: var(--line); border-left-color: #B9C2CE; background: #FAFBFC; }
.danger-zone.is-off strong { color: var(--muted); }
.btn.danger[disabled] { background: #C9A3A3; cursor: not-allowed; }

/* ---------- copy to clipboard ---------- */

.copyable { display: inline-flex; align-items: center; gap: 5px; }
.copy-btn {
  display: inline-grid; place-items: center;
  width: 20px; height: 20px; padding: 0;
  border: 0; border-radius: 3px; background: transparent;
  color: #A8B2BF; cursor: pointer;
  transition: color .12s ease, background .12s ease;
}
.copy-btn svg {
  width: 13px; height: 13px;
  fill: none; stroke: currentColor; stroke-width: 1.5;
  stroke-linecap: round; stroke-linejoin: round;
}
.copy-btn:hover        { color: var(--brand); background: #EAF2FB; }
.copy-btn:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.copy-btn.copied       { color: var(--ok); background: var(--ok-bg); }
.copy-btn.copy-failed  { color: var(--bad); background: var(--bad-bg); }


/* ---- live attribute lookup ------------------------------------------- */
/* Sits under the imported detail panels, so it is styled as an extension of
   them: same border, same radius, and the values in the same two-column shape
   as the tables above it. */
.live-lookup {
  margin: 12px 0 4px;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 10px 12px;
}
.live-lookup-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.live-lookup-head .btn { display: inline-flex; align-items: center; gap: 6px; }
.live-ico { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 1.6; }
.live-lookup-out { margin-top: 10px; }
.live-lookup-out .banner { margin: 0; }

.live-tiles {
  display: grid; gap: 10px; margin-bottom: 10px;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.live-tiles .tile { padding: 8px 10px; }
.live-tiles .tile .v { font-size: 18px; }
/* Licence usage is the one tile that carries a verdict, so it is the one tile
   that takes colour. The rest stay neutral and let the numbers speak. */
.live-tiles .tile.is-warn { border-color: #fde68a; background: var(--warn-bg); }
.live-tiles .tile.is-bad  { border-color: #fecaca; background: var(--bad-bg); }

.live-meta     { margin: 0 0 2px; }
.live-withheld { margin: 0 0 10px; }

.live-group { border: 1px solid var(--line); border-radius: var(--radius); margin-bottom: 8px; }
.live-group-head {
  width: 100%; display: flex; align-items: center; justify-content: space-between;
  gap: 8px; padding: 7px 11px; background: #f6f8fa; border: 0;
  border-radius: var(--radius); cursor: pointer; text-align: left; font: inherit;
}
.live-group-head:hover { background: #eef2f7; }
.live-group-title { font-size: 12.5px; font-weight: 640; }
/* A caret that turns, so a collapsed group looks collapsed rather than empty. */
.live-group-title::before {
  content: '▸'; display: inline-block; margin-right: 6px;
  color: var(--muted); transition: transform .12s ease;
}
.live-group-head[aria-expanded="true"] .live-group-title::before { transform: rotate(90deg); }
.live-group-body { padding: 2px 11px 8px; }

.live-table { width: 100%; }
.live-table > tbody > tr > th {
  width: 200px; font-weight: 600; color: #414a57; vertical-align: top;
  font-size: 12px; padding-left: 0;
}
/* The source's own field name, kept alongside the friendly label so this panel
   can still be checked against a raw listAtts() call. */
.live-raw-name {
  display: block; font-family: ui-monospace, Consolas, monospace;
  font-size: 10.5px; color: var(--muted); font-weight: 400; margin-top: 1px;
}
.live-table > tbody > tr > td {
  white-space: pre-wrap; word-break: break-word; font-size: 12px; vertical-align: top;
}
.live-table > tbody > tr:last-child > th,
.live-table > tbody > tr:last-child > td { border-bottom: 0; }
.live-sub-note { display: block; margin-top: 1px; }

/* Sub-objects — the traffic counters and the registration block. Indented and
   ruled so they read as one thing rather than as more rows of the parent. */
.live-sub { width: auto; min-width: 260px; border-left: 2px solid var(--line); }
/* The global `th, td { white-space: nowrap }` reaches in here, and these
   cells hold things like "Tue 19 May 2026, 12:53 (100 days ago)" and a
   180-character registration URL — unwrapped, one cell forced the whole
   panel 570px wide and the page scrolled sideways. */
.live-sub th, .live-sub td {
  border-bottom: 0; padding: 2px 10px; font-size: 12px;
  white-space: normal; overflow-wrap: anywhere;
}
.live-sub th { color: var(--muted); font-weight: 500; width: 150px; }
.live-sub td { font-variant-numeric: tabular-nums; }

/* The source value beneath the readable one, wherever this panel has formatted
   something: the path under a resolved gateway name, the counter under "1.4 GB",
   the original string under a parsed date. Quiet enough to ignore while reading
   and present when the question is what the source actually holds. */
.live-raw {
  display: block; font-family: ui-monospace, Consolas, monospace;
  font-size: 10.5px; color: var(--muted); margin-top: 1px;
  white-space: normal; overflow-wrap: anywhere;
}

/* Object paths, resolved to names. Chips rather than a list: a server with 15
   historical clients is a scan, not a read. */
.live-paths { display: flex; flex-wrap: wrap; gap: 5px; }
/* Each chip and its path form one column, so a wrapped row of them keeps every
   path under the name it belongs to. */
.live-path-item { display: flex; flex-direction: column; align-items: flex-start; max-width: 100%; }
.live-path-item .live-raw { padding-left: 2px; }
.live-chip {
  display: inline-flex; align-items: baseline; gap: 5px;
  padding: 2px 7px; border: 1px solid var(--line); border-radius: var(--radius);
  background: #f8fafc; font-size: 11.5px; text-decoration: none; color: var(--brand);
}
.live-chip:hover { background: #EAF2FB; border-color: #cfe0f2; }
.live-chip-kind { font-size: 10px; color: var(--muted); text-transform: uppercase; }
/* A path the last import has never seen: shown, not silently dropped. */
.live-chip.is-unknown {
  color: var(--muted); background: transparent; border-style: dashed;
  font-family: ui-monospace, Consolas, monospace;
}

.live-code {
  margin: 0; padding: 6px 8px; background: #f6f8fa; border-radius: var(--radius);
  font-family: ui-monospace, Consolas, monospace; font-size: 11px;
  white-space: pre-wrap; word-break: break-word; color: #414a57;
}

/* Traffic: the counters and the split, side by side. Stacks on a narrow
   viewport rather than squeezing a 140px ring into 60px. */
.live-split { display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap; }
/* The figures take what they need; the ring takes the rest, within limits.
   min-width matters: a flex item defaults to min-content and will happily
   shrink under its own legend, which is what clipped the labels. */
.live-chart {
  flex: 1 1 230px; min-width: 210px; max-width: 320px;
  height: 230px; position: relative;
}
.live-cached {
  margin: 0 0 10px; padding: 6px 9px;
  background: var(--ok-bg); color: var(--ok);
  border: 1px solid #bbf7d0; border-radius: var(--radius);
}

.live-tiles-head { margin: 0 0 8px; font-size: 13px; font-weight: 640; }

/* ---- whole-hive key search ------------------------------------------- */
.hive-btn { display: inline-flex; align-items: center; gap: 6px; }
.hive-ico { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 1.7; }

.hive-dialog {
  width: min(780px, calc(100vw - 32px));
  max-height: min(80vh, 760px);
  padding: 18px 20px 20px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--ink);
  overflow: auto;
}
.hive-dialog::backdrop { background: rgba(16, 24, 40, .45); }
.hive-dialog h2 { margin: 0 0 4px; font-size: 16px; font-weight: 660; }
.hive-dialog > p { margin: 0 0 12px; }

.hive-close-form { float: right; margin: -6px -6px 0 8px; }
.hive-close {
  border: 0; background: transparent; cursor: pointer;
  font-size: 22px; line-height: 1; color: var(--muted); padding: 2px 6px;
  border-radius: var(--radius);
}
.hive-close:hover { background: #eef1f5; color: var(--ink); }

/* Visually hidden but still read out — the input's placeholder is an example
   key, which is not a label. */
.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;
}

.hive-out .banner { margin: 0; }
.hive-meta { margin: 0 0 10px; }
.hive-head { margin: 14px 0 6px; font-size: 12.5px; font-weight: 640; }
.hive-head:first-of-type { margin-top: 4px; }

.hive-list { display: flex; flex-direction: column; gap: 6px; }
.hive-group {
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 7px 10px; background: #fbfcfd;
}
.hive-obj { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.hive-name { font-weight: 620; font-size: 13px; }
.hive-kind {
  font-size: 10px; text-transform: uppercase; letter-spacing: .4px;
  color: var(--muted); background: #eef1f5; padding: 1px 5px; border-radius: 3px;
}
/* The path is the thing you paste elsewhere, so it stays selectable and whole
   rather than being trimmed to fit. */
.hive-path { font-size: 10.5px; color: var(--muted); word-break: break-all; }

.hive-hits { list-style: none; margin: 5px 0 0; padding: 0; display: flex;
             flex-direction: column; gap: 3px; }
.hive-hits li { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; font-size: 12px; }
.hive-attr { font-size: 11px; }

/* Key search sits beside the copy button and matches its weight — the labelled
   .hive-btn is too heavy for a table cell. */
.copy-btn.hive-icon:hover { color: var(--brand); background: #EAF2FB; }

/* Free-text key search, in the page head.
   margin-left:auto puts it at the far right of the band without needing a
   spacer element; the title keeps the left edge whatever its length. */
.head-search {
  margin-left: auto;
  position: relative;
  display: flex;
  align-items: center;
  flex: 0 1 260px;
}
.head-search-ico {
  position: absolute; left: 9px;
  width: 13px; height: 13px; pointer-events: none;
  fill: none; stroke: var(--muted); stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
}
.head-search input {
  width: 100%;
  padding: 6px 10px 6px 28px;
  border: 1px solid #CBDCF0;
  border-radius: var(--radius);
  background: rgba(255, 255, 255, .85);
  font-family: ui-monospace, Consolas, monospace;
  font-size: 12px;
  color: var(--ink);
}
.head-search input::placeholder { font-family: inherit; color: var(--muted); }
.head-search input:focus {
  outline: 2px solid var(--brand); outline-offset: 1px;
  background: #fff; border-color: var(--brand);
}

/* On a narrow viewport the title and the field cannot share a row. */
@media (max-width: 620px) {
  .page-head { flex-wrap: wrap; }
  .head-search { flex: 1 1 100%; margin-left: 0; }
}

/* The gateway load summary. Fixed layout with the five figure columns pinned,
   so the name takes whatever is left and the numbers stay where the eye
   expects them regardless of how wide the panel is.

   It used to sit in a half-width panel where six columns shared about 460px
   and the names had to be clipped to stop them wrapping to three lines. It is
   full width now, so the name column gets ~880px and nothing is clipped — the
   ellipsis rules below are kept for the narrow case rather than the normal
   one. */
table.gw-load { table-layout: fixed; }
table.gw-load th:first-child,
table.gw-load td:first-child {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
table.gw-load th:nth-child(2), table.gw-load td:nth-child(2) { width: 44px; }  /* Srv    */
table.gw-load th:nth-child(3), table.gw-load td:nth-child(3) { width: 62px; }  /* GB     */
table.gw-load th:nth-child(4), table.gw-load td:nth-child(4) { width: 56px; }  /* GB/srv */
table.gw-load th:nth-child(5), table.gw-load td:nth-child(5) { width: 52px; }  /* Sess   */
table.gw-load th:nth-child(6), table.gw-load td:nth-child(6) { width: 54px; }  /* Conns  */

/* The (i) beside a panel heading, and the bubble it reveals. */
.info {
  position: relative;
  display: inline-flex;
  align-items: center;
  margin-left: 6px;
  vertical-align: -2px;
  color: #7C93AE;
  cursor: help;
}
.info:hover, .info:focus-visible { color: var(--brand); }
.info:focus-visible { outline: 2px solid var(--brand-light); outline-offset: 2px; border-radius: 50%; }
.info svg { display: block; width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; }
.info .tip {
  position: absolute;
  left: -6px;
  top: calc(100% + 7px);
  z-index: 30;
  width: max-content;
  max-width: 270px;
  padding: 8px 10px;
  border-radius: 4px;
  background: #1F2937;
  color: #E9EDF3;
  font-size: 11.5px;
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: 0;
  text-align: left;
  white-space: normal;
  box-shadow: 0 6px 20px rgba(15,23,42,.28);
  opacity: 0;
  visibility: hidden;
  transition: opacity .12s ease;
  pointer-events: none;
}
.info:hover .tip, .info:focus-visible .tip { opacity: 1; visibility: visible; }
/* A bubble that runs past the viewport does not merely get clipped: it widens
   the document and puts the whole page into horizontal scroll.

   This used to be handled by flipping the bubble on ".panel:last-child", which
   assumed the last panel is the one at the right edge. That holds in a
   two-column grid and nowhere else — the grids are repeat(auto-fit, minmax(...))
   so the column count follows the viewport, and on a wide screen the overview's
   grid is three columns. Adding a seventh panel moved :last-child off the right
   edge and the third panel — sitting in the rightmost column, not last —
   overflowed by 64px.

   So the bubble is anchored to the heading instead of to the icon. The heading
   spans the panel's inner width and the panel is always inside the viewport, so
   a bubble that cannot exceed the heading cannot leave the screen either.
   Position is no longer a guess about which panel happens to be last.

   .info drops to static so the heading becomes the containing block — it is
   already positioned, for the accent tick. */
.panel h3 .info { position: static; }
.panel h3 .info .tip {
  left: 0;
  right: auto;
  top: calc(100% + 7px);
  max-width: min(270px, 100%);
}
@media (prefers-reduced-motion: reduce) { .info .tip { transition: none; } }

/* The gateway latency matrix.
   Gateway names are far wider than the three-digit millisecond figures under
   them, so automatic layout gave each numeric column ~130px and squeezed the
   recommendation — the one column anybody reads — into 51px. Fixed layout
   puts the width where the content is. */
table.latency { table-layout: fixed; }
table.latency th:nth-child(1), table.latency td:nth-child(1) { width: 15%; }
table.latency th:nth-child(2), table.latency td:nth-child(2) { width: 22%; }
table.latency th:nth-child(3), table.latency td:nth-child(3) { width: 13%; }
/* Every column needs a width, not just the first three. `.panel table th`
   sets 38% for the two-column key/value panels, and it applies here too;
   under fixed layout the specified widths are scaled to fit, so leaving
   six columns on 38% each meant the first three were scaled down to a
   third of what they asked for. 15 + 22 + 13 + (6 x 8.3) = 100. */
table.latency th:nth-child(n+4), table.latency td:nth-child(n+4) {
  width: 8.3%;
  overflow: hidden; text-overflow: ellipsis;
}
/* The fastest gateway for that row, marked on the cell rather than only in
   the text — a green number among black ones is easy to miss. */
table.latency td.cell-best {
  background: #EAF6EE;
  box-shadow: inset 2px 0 0 var(--ok);
  font-weight: 650;
}
table.latency sup { font-size: 9px; margin-left: 2px; vertical-align: super; }


/* Sub-items in the rail, shown only under the open section.
   Indented to sit under the parent's label rather than its icon, with no
   icon of their own: two more glyphs in a column of ten would compete with
   the sections instead of reading as parts of one. */
.sidebar nav a.nav-sub {
  /* The parent's label starts at 44px (3px border + 16px padding + a 16px
     icon + 9px gap). 44 put the child in the same column as its parent,
     which is not an indent — this clears it by 14. */
  padding: 5px 16px 5px 55px;
  font-size: 12.5px;
  color: #A8B2C4;
  border-left-color: transparent;
}
.sidebar nav a.nav-sub:hover { background: #24365F; color: #fff; }
/* The open child, marked more quietly than a section: the section is
   already highlighted above it, and two full-strength markers in a row
   read as two selections. */
.sidebar nav a.nav-sub.on {
  background: rgba(87,180,226,.13);
  color: #fff;
  font-weight: 620;
  border-left-color: var(--brand-light);
  border-left-width: 4px;
  /* One less than the inactive padding, so the wider marker does not shift
     the label. */
  padding-left: 54px;
}
/* Collapsed to an icon rail there is no room and no label to hang them on. */
html.rail .sidebar nav a.nav-sub { display: none; }

/* ===========================================================================
   Phones
   ---------------------------------------------------------------------------
   Measured at 375x812. The layout already collapsed correctly — the grids go
   to one column and the charts resize — but four things made the app awkward
   to actually use on a handset. Each is fixed below in the order it hurt.
   =========================================================================== */

@media (max-width: 780px) {

  /* 1. The tooltips were dragging the whole page sideways.
        A .tip is absolutely positioned, 270px wide, and only hidden with
        visibility/opacity — so it is still laid out, and on a 375px screen it
        pushed the document to 461px on the overview and 583px on the gateway
        performance page. Every page then scrolled horizontally for no visible
        reason. Fixed positioning takes them out of the scroll extent entirely,
        and pinning them to the bottom means one rule works wherever the icon
        happens to sit rather than guessing per panel. */
  .info .tip {
    position: fixed;
    left: 12px;
    right: 12px;
    top: auto;
    bottom: 12px;
    width: auto;
    max-width: none;
    font-size: 12.5px;
    padding: 11px 13px;
  }
  /* The desktop rule anchors the bubble to its heading; here it is pinned to
     the viewport instead, so those offsets have to be overridden rather than
     left to fight the pinning above. Keyed to the same selector so it wins on
     specificity regardless of source order.

     This previously targeted .grid2 > .panel:last-child, matching a desktop
     rule that no longer exists — which left every other panel's bubble taking
     its desktop left offset on a phone. */
  .panel h3 .info .tip {
    left: 12px;
    right: 12px;
    top: auto;
    max-width: none;
  }

  /* And they could not be opened at all. The bubble shows on :focus-visible,
     which browsers deliberately withhold from touch, so tapping the (i) put
     focus on it and nothing appeared. Plain :focus is what a tap gives you. */
  .info:focus .tip { opacity: 1; visibility: visible; }

  /* 2. The panel grids had a hard minimum wider than a small phone.
        repeat(auto-fit, minmax(340px, 1fr)) never yields a track under 340px,
        and .grid3 never under 300px, so on a 320px handset the panels stood
        340px wide inside a 292px column and the page scrolled sideways again.
        It only looked fine at 375px because 347px of content area happened to
        clear the floor. One column with a zero minimum both fixes that and
        stops a wide table or canvas re-inflating the track through its
        min-content size. */
  .grid2,
  .grid3 { grid-template-columns: minmax(0, 1fr); }

  /* Most wide tables sit in a .table-wrap and scroll inside it, but 21 of them
     across the app are placed straight into a .panel and had nothing to scroll
     in — "Type against status" on the Servers page was 306px inside a 292px
     panel, and that alone put the whole page back into horizontal scroll.
     Letting the panel scroll fixes every one of them without touching markup.
     Safe here specifically because the heading tooltips are position:fixed on
     this breakpoint; the desktop rules leave .panel unscrolled, which is what
     keeps those bubbles from being clipped there. */
  .panel { overflow-x: auto; }

  /* 3. Filter controls overflowed the screen. A <select> is sized by its
        longest option, and the platform filter carries strings like
        "Windows Server 2008 R2 Version 6.1 (Build 7601: SP1)", which made it
        377px inside a 347px form. Let them shrink and share the row instead. */
  .filters { gap: 6px; }
  .filters input,
  .filters select {
    max-width: 100%;
    min-width: 0;
    flex: 1 1 45%;
  }
  .filters input[type=search] {
    min-width: 0;
    flex: 1 1 100%;
  }

  /* 4. iOS Safari zooms the page whenever a focused control's text is under
        16px, and every control here was 12-13.5px. The zoom then leaves the
        page scrolled sideways with no way back except pinching. This is the
        one place where 16px is not a style choice. */
  .filters input,
  .filters select,
  .head-search input,
  .login-card input,
  input[type=text],
  input[type=search],
  input[type=password],
  input[type=number],
  select,
  textarea {
    font-size: 16px;
  }

  /* 5. The navigation was all but unreachable, and now opens from a button.
        As a horizontal strip the sidebar held 1,777px of links behind a 375px
        window — 12 destinations, of which about two were visible, and nothing
        indicated the rest existed. It is now a drawer: the bar shows the brand
        and a menu button, and the full list drops below it when asked. Closed
        is the default on every page load, so the content starts at the top of
        the screen rather than below a block of navigation. */
  .sidebar {
    flex-wrap: wrap;
    overflow-x: visible;
    row-gap: 0;
    padding: 8px 12px;
    /* The bar stays reachable while a long table scrolls underneath. */
    position: sticky;
    top: 0;
    z-index: 40;
  }
  .side-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    gap: 10px;
  }

  .nav-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* 44px square: the smallest a fingertip hits reliably. */
    width: 44px;
    height: 44px;
    margin-right: -8px;
    padding: 0;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: #cfd4dc;
    cursor: pointer;
  }
  .nav-toggle:hover { background: #24365F; color: #fff; }
  .nav-toggle:focus-visible { outline: 2px solid var(--brand-light); outline-offset: 2px; }
  .nav-toggle svg {
    width: 22px; height: 22px;
    fill: none; stroke: currentColor;
    stroke-width: 1.6; stroke-linecap: round;
  }
  /* Which glyph shows is driven by aria-expanded, so the button's appearance
     and the state screen readers are told can never disagree. */
  .nav-toggle .ico-shut,
  .nav-toggle[aria-expanded="true"] .ico-open { display: none; }
  .nav-toggle[aria-expanded="true"] .ico-shut { display: block; }

  /* The drawer itself. Closed by default; .nav-open on the sidebar reveals it. */
  .sidebar nav,
  .sidebar .who { display: none; }
  .sidebar.nav-open nav {
    display: flex;
    flex-direction: column;
    width: 100%;
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px solid #2E4272;
    /* A long list on a short handset scrolls inside the drawer rather than
       pushing the page down. */
    max-height: calc(100vh - 120px);
    overflow-y: auto;
  }
  .sidebar.nav-open .who {
    display: flex;
    width: 100%;
    align-items: center;
    gap: 12px;
    margin-top: 8px;
    padding: 10px 0 2px;
  }
  .sidebar.nav-open nav a {
    padding: 11px 12px;
    font-size: 14.5px;
    border-left-width: 3px;
  }
  .sidebar.nav-open nav a.on { padding-left: 11px; }
  /* Sub-items keep the indent that shows they belong to the item above. */
  .sidebar.nav-open nav a.nav-sub { padding-left: 34px; }
  .who .role { margin-bottom: 0; }

  /* The rail toggle is a desktop affordance; the drawer replaces it here. */
  .rail-toggle { display: none; }

  /* 6. Touch targets on the sign-in card. The submit button was 29px tall and
        the remember-me box 13px square, both under the ~44px a fingertip
        needs. The card also had a fixed 340px width, which is wider than the
        usable area on the narrowest phones once padding is counted. */
  /* width:auto would let the card shrink to its content inside the centring
     flexbox — it collapsed to 265px, narrower than the 340px it had before.
     100% of the available width, capped, is what fills a handset properly. */
  .login-card {
    width: 100%;
    max-width: 360px;
    margin: 0 14px;
    padding: 26px 22px;
  }
  .login-card input[type=text],
  .login-card input[type=password] { padding: 12px; margin-bottom: 12px; }
  .login-card .btn,
  .login-card button[type=submit] {
    width: 100%;
    padding: 13px;
    font-size: 16px;
  }
  .login-card input[type=checkbox] {
    width: 18px;
    height: 18px;
    vertical-align: -3px;
    margin-right: 6px;
  }
  .login-card label.remember { display: flex; align-items: center; padding: 6px 0; }

  /* Buttons and filter chips generally, so they are comfortable to hit. */
  .btn { padding: 10px 16px; font-size: 14px; }
}

/* Hand-typed links on the SQL Servers grid.

   The box is quiet until touched: a grid with twenty bordered inputs down two
   columns reads as a form, and this is a table that happens to be editable in
   two places. The border appears on hover and focus, which is enough to say
   "you can type here" without the page shouting it. */
.link-cell { min-width: 160px; }
.link-input {
  width: 100%;
  padding: 3px 6px;
  font: inherit;
  font-size: 12px;
  color: var(--ink);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 3px;
}
.link-input::placeholder { color: var(--muted); opacity: .6; }
.link-input:hover  { border-color: var(--line); background: var(--bg); }
.link-input:focus  { border-color: var(--brand); background: #fff; outline: none; }
.link-input.link-bad { border-color: var(--bad); background: #fef2f2; }
.link-state { display: block; margin-top: 2px; line-height: 1.3; }
.link-state a { text-decoration: none; }
.link-state a:hover { text-decoration: underline; }

/* A numeric entry sits right, like the figures it is compared against. */
.link-input.num { text-align: right; }

/* The page you are on, among the run's own tabs. A button rather than a link
   so the three read as one control; the current one is filled rather than
   merely bolded, which survives being glanced at. */
.banner .btn.secondary.on {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
}

/* ---------- the rail stays put ---------- */

/* Desktop only. Below 780px the shell becomes a column and the sidebar a
   horizontal strip, so pinning it to the viewport height there would eat the
   screen; that layout has its own drawer instead.

   align-self matters: a flex child defaults to stretch, which makes the
   sidebar as tall as the whole scrolling page. A box already that tall has
   nothing left to stick within, and position:sticky silently does nothing.
   Giving it its own height is what lets it hold position. */
@media (min-width: 781px) {
  .sidebar {
    position: sticky;
    top: 0;
    align-self: flex-start;
    height: 100vh;
    overflow-y: auto;
    /* A long nav scrolled to its end must not then start scrolling the page
       underneath it — the pointer is over the menu, so the menu is what the
       wheel means. */
    overscroll-behavior: contain;
  }

  /* The default scrollbar is a light slab against a navy rail. Only shown
     when the nav genuinely overflows, which is a short window at small
     heights, so it is kept quiet rather than styled into a feature. */
  .sidebar::-webkit-scrollbar { width: 8px; }
  .sidebar::-webkit-scrollbar-track { background: transparent; }
  .sidebar::-webkit-scrollbar-thumb {
    background: #3A4E7A;
    border-radius: 4px;
  }
  .sidebar:hover::-webkit-scrollbar-thumb { background: #4A6191; }
  .sidebar { scrollbar-width: thin; scrollbar-color: #3A4E7A transparent; }
}

/* Per-row delete in the SQL collection history.

   .btn.danger is solid red, which is right for the one big destructive control
   on a detail page and wrong for a button repeated down thirty rows — a column
   of red reads as an alarm rather than an action. This stays quiet until
   hovered, and only then admits what it does. */
.sql-run-delete {
  padding: 3px 9px;
  font-size: 12px;
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--line);
}
.sql-run-delete:hover {
  color: #fff;
  background: var(--bad);
  border-color: var(--bad);
}
.sql-run-delete:focus-visible { outline: 2px solid var(--bad); outline-offset: 1px; }
.sql-run-delete[disabled] { background: transparent; color: var(--muted); opacity: .6; }

/* --- Script viewer in the hive search -------------------------------------
   A hit inside one of the ic*fn_ hooks is an offset into JavaScript, which
   says nothing on its own. The script opens inline under its own hit rather
   than in a second dialog: you are reading "what references this key", and the
   code is the evidence for that line, not a separate errand. */
.hive-script-go {
  /* Lighter than a normal .btn secondary — this sits inside a list item, next
     to 11px text, and the standard control overpowers the line it belongs to. */
  padding: 1px 7px; font-size: 11px; line-height: 1.6;
}

/* The host is a flex item in .hive-hits li, which lays out inline. Forcing a
   full-width new line keeps the editor under the hit instead of squeezed
   beside the pills. */
.hive-script { flex: 0 0 100%; margin: 5px 0 2px; }

.hive-script-meta { margin: 0 0 4px; }
.hive-script-hits {
  margin-left: 8px; padding-left: 8px; border-left: 1px solid var(--line);
}

.hive-script-box {
  border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden;
  background: var(--panel);
}

/* CodeMirror ships a 300px default height in its own stylesheet; this is the
   one place it has to be overridden or every script is exactly 300px tall
   whether it is four lines or four hundred. */
.hive-script-box .CodeMirror {
  height: auto; max-height: 340px;
  font-family: ui-monospace, Consolas, monospace; font-size: 11px;
  line-height: 1.55;
}
.hive-script-box .CodeMirror-scroll { max-height: 340px; }
.hive-script-box .CodeMirror-gutters { background: #f6f8fa; border-right: 1px solid var(--line); }
.hive-script-box .CodeMirror-linenumber { color: var(--muted); font-size: 10px; }

/* The searched key, wherever it appears in the code. Amber rather than the
   brand blue: blue is what this app uses for "you can click this", and these
   are not links. */
.cm-hive-hit {
  background: #FDF0C8; border-radius: 2px;
  box-shadow: 0 0 0 1px #E9CE7A;
}
