/* Raidiam Status.
   =========================================================================
   Two surfaces share this sheet, deliberately.

   The public page is a document, read by a stranger during an outage on
   whatever connection and device they have. The console is a working tool,
   used by a handful of staff who are usually in a hurry for the same reason.
   They share the palette so that a state means the same thing in both, and
   they share nothing else: the document is narrow, calm and cacheable; the
   console is wide, dense and never cached.

   State colours are always namespaced to the element they colour. A bare
   `.state-x { color }` rule leaks onto anything carrying the class -- it once
   painted the banner's text the same colour as its own background, and there
   is a test that fails if it comes back. */

:root {
  --operational: #1f9d55;
  --degraded: #b7791f;
  --partial: #c05621;
  --major: #c53030;
  --maintenance: #2b6cb0;
  --unknown: #718096;

  /* The state colours above are the fills: bars and panel tints, which are
     graphical and need 3:1. The text tints below are the same states written
     as small type, which needs 4.5:1 against white -- #1f9d55 reaches 3.49 and
     #b7791f 3.64, so using the fill colour for a 13px label fails AA on the
     one page that has to be legible during an incident. Dark mode already
     clears 4.5:1 on every state, so it reuses its own values. */
  --operational-text: #15803d;
  --degraded-text: #7c5800;
  --partial-text: #9a3412;
  --major-text: #b91c1c;
  --maintenance-text: #2b6cb0;
  --unknown-text: #5f6c7d;

  --ink: #10151f;
  --quiet: #5f6c7d;
  --rule: #e3e8ef;
  --rule-strong: #cbd4e1;
  /* The canvas is tinted and the surfaces are white, so a card is a card
     because it is lighter than what is behind it rather than because it has
     a border drawn round it. On the console, where a screen can hold thirty
     of them, that is the difference between structure and a grid of boxes. */
  --canvas: #f4f6f9;
  --bg: #ffffff;
  --surface: #ffffff;
  --surface-sunk: #f7f9fb;
  --accent: #1a4fd6;
  --accent-ink: #ffffff;
  /* The spread between the two latency percentiles, as a wash of the accent.
     Set per theme rather than once: the same translucency over a near-black
     canvas lands at a fraction of the contrast it has over white, and the
     band is the half of the chart that carries the reading. */
  --band-ink: .12;

  --shadow-sm: 0 1px 2px rgb(16 21 31 / 6%), 0 1px 1px rgb(16 21 31 / 4%);
  --shadow-md: 0 2px 4px rgb(16 21 31 / 5%), 0 4px 12px rgb(16 21 31 / 6%);

  --r-sm: .3125rem;
  --r-md: .5rem;
  --r-lg: .75rem;

  /* The focus ring is its own colour rather than a state colour: it has to
     stay visible against every panel on the page, including the tinted
     outage banner. */
  --focus: #1a4fd6;
}

/* Dark mode arrives two ways, and the values have to be identical either way.
   CSS cannot OR a media query with a selector, so the block below is written
   out twice: once for a reader who has expressed no preference and is
   following their system, and once for a reader who has chosen dark
   explicitly. A test compares the two and fails if they drift -- which is the
   failure this duplication would otherwise cause, silently, the first time
   someone adjusts a colour in one of them.

   The `:not([data-theme="light"])` is what lets an explicit light choice win
   against a dark system setting. Without it the media query would keep
   overriding the reader. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --operational: #48bb78;
    --degraded: #ecc94b;
    --partial: #ed8936;
    --major: #fc8181;
    --maintenance: #63b3ed;
    --unknown: #a0aec0;

    --operational-text: #48bb78;
    --degraded-text: #ecc94b;
    --partial-text: #ed8936;
    --major-text: #fc8181;
    --maintenance-text: #63b3ed;
    --unknown-text: #a0aec0;

    --ink: #eef2f7;
    --quiet: #9aa7b8;
    --rule: #262c3a;
    --rule-strong: #38414f;
    --canvas: #0f1219;
    --bg: #161a23;
    --surface: #1a1f2b;
    --surface-sunk: #141823;
    --accent: #7aa7ff;
    --accent-ink: #0f1219;
    --band-ink: .26;

    --shadow-sm: 0 1px 2px rgb(0 0 0 / 40%);
    --shadow-md: 0 2px 4px rgb(0 0 0 / 35%), 0 8px 20px rgb(0 0 0 / 30%);

    --focus: #90cdf4;
  }
}
:root[data-theme="dark"] {
    --operational: #48bb78;
    --degraded: #ecc94b;
    --partial: #ed8936;
    --major: #fc8181;
    --maintenance: #63b3ed;
    --unknown: #a0aec0;

    --operational-text: #48bb78;
    --degraded-text: #ecc94b;
    --partial-text: #ed8936;
    --major-text: #fc8181;
    --maintenance-text: #63b3ed;
    --unknown-text: #a0aec0;

    --ink: #eef2f7;
    --quiet: #9aa7b8;
    --rule: #262c3a;
    --rule-strong: #38414f;
    --canvas: #0f1219;
    --bg: #161a23;
    --surface: #1a1f2b;
    --surface-sunk: #141823;
    --accent: #7aa7ff;
    --accent-ink: #0f1219;
    --band-ink: .26;

    --shadow-sm: 0 1px 2px rgb(0 0 0 / 40%);
    --shadow-md: 0 2px 4px rgb(0 0 0 / 35%), 0 8px 20px rgb(0 0 0 / 30%);

    --focus: #90cdf4;
}

/* Native controls -- form fields, scrollbars, date pickers -- follow this
   rather than our custom properties. Left unset, a dark page renders a white
   scrollbar and a light select, which is the giveaway that a theme was
   painted on rather than declared. */
:root { color-scheme: light dark; }
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"] { color-scheme: dark; }

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

body {
  margin: 0;
  background: var(--canvas);
  color: var(--ink);
  font: 16px/1.55 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4 { letter-spacing: -.012em; line-height: 1.25; }
h1 { font-size: 1.625rem; margin: 0 0 1.25rem; font-weight: 650; }
h2 { font-size: 1.1875rem; font-weight: 640; }
h3 { font-size: 1rem; font-weight: 640; }
a { color: inherit; }
a:hover { text-decoration-thickness: 2px; }

/* The public document sits on a white sheet floating over the tinted canvas;
   the console spans the canvas itself, because it is a workspace rather than
   a page. */
.wrap {
  max-width: 50rem;
  margin: 2rem auto;
  padding: 2.25rem 2rem 2.75rem;
  background: var(--bg);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-md);
}
@media (max-width: 40rem) {
  .wrap { margin: 0; border: 0; border-radius: 0; box-shadow: none; padding: 1.5rem 1.125rem 2rem; }
}

/* ------------------------------------------------------------- banner -- */

.banner {
  display: flex;
  align-items: center;
  gap: .75rem;
  border: 1px solid;
  border-radius: var(--r-md);
  padding: 1rem 1.125rem;
  margin-bottom: 2rem;
}
.banner h2 { margin: 0; font-size: 1.0625rem; font-weight: 640; }
.banner .dot {
  flex: none;
  width: .625rem;
  height: .625rem;
  border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 0 .25rem color-mix(in srgb, currentColor 18%, transparent);
}
/* Tinted panel rather than a solid slab: the text stays high-contrast
   against the page background in both colour schemes, so there is no way for
   it to end up the same colour as its own fill. */
.banner.state-operational       { color: var(--operational-text); border-color: color-mix(in srgb, var(--operational) 45%, transparent); background: color-mix(in srgb, var(--operational) 10%, transparent); }
.banner.state-degraded          { color: var(--degraded-text);    border-color: color-mix(in srgb, var(--degraded) 45%, transparent);    background: color-mix(in srgb, var(--degraded) 10%, transparent); }
.banner.state-partial_outage    { color: var(--partial-text);     border-color: color-mix(in srgb, var(--partial) 45%, transparent);     background: color-mix(in srgb, var(--partial) 10%, transparent); }
.banner.state-major_outage      { color: var(--major-text);       border-color: color-mix(in srgb, var(--major) 45%, transparent);       background: color-mix(in srgb, var(--major) 10%, transparent); }
.banner.state-under_maintenance { color: var(--maintenance-text); border-color: color-mix(in srgb, var(--maintenance) 45%, transparent); background: color-mix(in srgb, var(--maintenance) 10%, transparent); }
.banner.state-unknown           { color: var(--unknown-text);     border-color: color-mix(in srgb, var(--unknown) 45%, transparent);     background: color-mix(in srgb, var(--unknown) 10%, transparent); }

/* --------------------------------------------------------- components -- */

.component { border-top: 1px solid var(--rule); padding: 1rem 0; }
.component:first-child { border-top: 0; padding-top: .25rem; }
/* The rail is what makes a child a child. Indentation alone leaves a reader
   inferring the hierarchy from whitespace, and at three levels that stops
   working. */
.component .children {
  margin-left: .5rem;
  padding-left: 1.125rem;
  border-left: 2px solid var(--rule);
}
.component .children .component { border-top: 0; padding: .625rem 0; }
.row { display: flex; align-items: baseline; gap: .75rem; }
.name { font-weight: 620; }
.state-label { margin-left: auto; font-size: .8125rem; font-weight: 550; white-space: nowrap; }
.state-label.state-operational       { color: var(--operational-text); }
.state-label.state-degraded          { color: var(--degraded-text); }
.state-label.state-partial_outage    { color: var(--partial-text); }
.state-label.state-major_outage      { color: var(--major-text); }
.state-label.state-under_maintenance { color: var(--maintenance-text); }
.state-label.state-unknown           { color: var(--unknown-text); }

.delayed {
  font-size: .6875rem;
  color: var(--quiet);
  background: var(--surface-sunk);
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: .0625rem .5rem;
  white-space: nowrap;
}

/* ------------------------------------------------------------ history -- */

.history svg { width: 100%; height: 34px; display: block; margin-top: .625rem; }
rect.bar { fill: var(--operational); }
rect.bar.state-degraded          { fill: var(--degraded); }
rect.bar.state-partial_outage    { fill: var(--partial); }
rect.bar.state-major_outage      { fill: var(--major); }
rect.bar.state-under_maintenance { fill: var(--maintenance); }
rect.bar.state-unknown, rect.bar.no-data { fill: var(--rule-strong); }

.history .scale {
  display: flex;
  justify-content: space-between;
  color: var(--quiet);
  font-size: .75rem;
  margin: .375rem 0 0;
}
/* The marker under a bar that is not a clean, observed period: the chart's
   meaning must not depend on hue alone. */
rect.mark { fill: var(--ink); }
.bars-note { margin: .375rem 0 0; }

/* Response time. Drawn as a line rather than blocks because latency is a
   magnitude with a shape worth reading, where availability is categorical.
   The stroke does not scale with the view box -- preserveAspectRatio="none"
   stretches the geometry to the column width, and a scaled stroke would come
   out as a smear at one end and a hairline at the other. */
.latency svg { width: 100%; height: 84px; display: block; margin-top: .5rem;
               /* The zero line, drawn. The axis starts at zero so the shape is
                  not exaggerated, and without a baseline a reader has no way
                  to see that it does. */
               border-bottom: 1px solid var(--rule); }
.latency .scale { display: flex; justify-content: space-between; gap: 1rem;
                  color: var(--quiet); font-size: .75rem; margin: .375rem 0 0; }

/* The three figures above the chart: what it is now, what is normal, how bad
   it gets. They lead because they are the questions a reader arrives with;
   the chart only answers which way it is going. */
.latency-figures {
  display: flex;
  flex-wrap: wrap;
  gap: .25rem 1.25rem;
  margin: .625rem 0 0;
  font-size: .8125rem;
}
.latency-figures b { font-size: 1rem; font-weight: 640; font-variant-numeric: tabular-nums; }
.latency-figures .quiet { font-size: .75rem; }

/* The two series, and the spread between them. The band is the half of the
   picture a median-only chart was leaving out: the gap between an ordinary
   response and a slow one is what a complaint is about, and unfilled it is
   something the eye has to measure rather than see. */
polyline.latency-line { stroke: var(--accent); stroke-width: 1.5;
                        stroke-linejoin: round; stroke-linecap: round; }
polyline.latency-line.p95 { stroke: var(--quiet); stroke-width: 1; stroke-dasharray: 3 2; }
polygon.latency-band { fill: var(--accent); opacity: var(--band-ink, .12); }
circle.latency-point { fill: var(--accent); opacity: .55; }
/* The 95th's hover targets sit under the median's and must not compete with
   them: they exist to be interrogated, not read as a series. */
circle.latency-point.p95 { opacity: .35; }

/* The legend is a drawn swatch rather than a colour word, so it works for a
   reader who cannot tell the two lines apart by hue. Shared by the public
   chart and the console's, which is why it is here rather than beside either
   one of them. */
.key { display: inline-block; width: .875rem; height: 0; vertical-align: middle; }
.key.p50 { border-top: 2px solid var(--accent); }
.key.p95 { border-top: 1px dashed var(--quiet); }

/* The cursor and its readout, both driven by latency.js. Neither is drawn
   until the script gives it something: the cursor has no x coordinates in the
   markup, and the tooltip does not exist in it at all. */
/* The days nobody measured. The same grey the availability strip uses for its
   own no-data bars, in the columns directly beneath them, so the two charts
   agree about where we could not see. This is the replacement for a sentence
   under the chart explaining that a break in the line is not a fast day: a
   chart that needs a caption to say what it is not showing has not said it. */
rect.latency-gap { fill: var(--rule-strong); opacity: .3; }

line.latency-cursor { stroke: var(--rule-strong); stroke-width: 1; }
.latency-tip {
  position: fixed;
  left: var(--tip-x, 0);
  top: var(--tip-y, 0);
  /* Centred under the chart, so the cursor line it belongs to is never the
     thing it covers and the figures above the chart stay readable. */
  transform: translate(-50%, .375rem);
  z-index: 20;
  pointer-events: none;
  background: var(--bg);
  color: var(--ink);
  border: 1px solid var(--rule-strong);
  border-radius: .25rem;
  padding: .3125rem .5rem;
  font-size: .75rem;
  line-height: 1.35;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  box-shadow: 0 2px 8px rgb(0 0 0 / .14);
}
.latency-tip-when { color: var(--quiet); }

/* The tenant's own mark, where the title would otherwise be.
   
   Capped by height rather than width so wordmarks of different aspect ratios
   line up on their baseline instead of on their bounding box, and constrained
   on width so a square mark cannot push the nav off a phone. max-width in
   percent, not pixels: the header is already a constrained column. */
/* A definite height rather than a maximum, because a tenant's SVG may carry
   only a viewBox -- which is how most of them are exported, and how
   raidiam.com serves its own. That gives the file a ratio and no intrinsic
   size, and an <img> with height:auto and nothing definite to resolve against
   renders it 0x0: a broken image where the mark should be, from a file that
   downloaded perfectly and passed every check. Fixing one axis gives the
   ratio something to work with, and has the side effect that every tenant's
   logo lines up at the same height.
   object-fit keeps a raster whose proportions differ from the box undistorted
   rather than stretched to fill it. */
.page-logo {
  display: block;
  height: 2.25rem;
  width: auto;
  max-width: min(100%, 22rem);
  margin-bottom: .5rem;
  object-fit: contain;
  object-position: left center;
}

/* ---------------------------------------------------------- incidents -- */

.quiet { color: var(--quiet); font-size: .8125rem; }
.meta  { color: var(--quiet); font-size: .8125rem; }
.incident { border-top: 1px solid var(--rule); padding: 1.125rem 0; }
.incident h3 { margin: 0 0 .375rem; font-size: 1rem; }
.badge {
  display: inline-block;
  border: 1px solid var(--rule-strong);
  border-radius: 999px;
  padding: .0625rem .5rem;
  margin-right: .375rem;
  font-size: .6875rem;
  font-weight: 600;
  letter-spacing: .03em;
  color: var(--quiet);
  white-space: nowrap;
  vertical-align: .0625rem;
}
/* The impact class was already on every incident article and had never been
   drawn. A list of incidents where a CRITICAL and a MINOR look identical
   makes the reader open both to find out which mattered. */
.incident.impact-critical, .incident.impact-major,
.incident.impact-minor, .incident.impact-none {
  border-left: 3px solid var(--rule-strong);
  padding-left: .875rem;
}
.incident.impact-critical { border-left-color: var(--major); }
.incident.impact-major    { border-left-color: var(--partial); }
.incident.impact-minor    { border-left-color: var(--degraded); }
.incident.impact-none     { border-left-color: var(--rule-strong); }

.update {
  border-left: 2px solid var(--rule);
  padding-left: .875rem;
  margin-top: .875rem;
}
.update p { margin: .25rem 0 0; }
footer { margin-top: 2.5rem; border-top: 1px solid var(--rule); padding-top: 1rem; }

/* The section navigation. Plain links in a list, so the pages are reachable
   from each other without JavaScript and a screen reader can list them. */
ul.site-nav {
  display: flex;
  flex-wrap: wrap;
  gap: .25rem;
  list-style: none;
  padding: .25rem;
  margin: 0 0 1.75rem;
  font-size: .875rem;
  background: var(--surface-sunk);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
}
ul.site-nav a {
  display: block;
  padding: .3125rem .75rem;
  border-radius: var(--r-sm);
  text-decoration: none;
  color: var(--quiet);
  font-weight: 550;
}
ul.site-nav a:hover { background: var(--bg); color: var(--ink); }
ul.site-nav a[aria-current] { background: var(--bg); color: var(--ink); box-shadow: var(--shadow-sm); }
header h1 { margin-bottom: .875rem; }
header h1 a { text-decoration: none; }
header h1 a:hover { text-decoration: underline; }
/* A subtitle sitting directly under the page title, not a section heading:
   the markup has to stay an h2 so the heading ladder is unbroken, but it
   should not read with a section's weight. */
header h1 + h2 { margin: -.5rem 0 1rem; font-size: 1rem; font-weight: 550; color: var(--quiet); }

/* The uptime window switch. Plain links, because switching window must work
   with JavaScript disabled -- the same reason the page itself is rendered
   server-side. */
.window-switch {
  display: flex;
  gap: .75rem;
  margin: 0 0 1rem;
  font-size: .8125rem;
  color: var(--quiet);
}
.window-switch [aria-current] { font-weight: 640; color: var(--ink); }

/* "Load older incidents" is a link that htmx happens to intercept, so it is
   styled as the control it looks like rather than inheriting link colour. */
a.load-more {
  display: inline-block;
  font-size: .875rem;
  font-weight: 550;
  color: var(--accent);
  text-decoration: underline;
  margin-top: 1rem;
}

/* ------------------------------------------------------------- forms -- */

form.stack { display: flex; flex-direction: column; gap: .875rem; max-width: 36rem; margin-top: .875rem; }
form.stack label { display: flex; flex-direction: column; gap: .3125rem; font-size: .8125rem; font-weight: 620; }
form.stack label.check { flex-direction: row; align-items: flex-start; gap: .5rem; font-weight: 400; font-size: .875rem; }
form.stack label.check input { margin-top: .1875rem; }
form.stack small { font-weight: 400; line-height: 1.45; }
form.stack input[type=text], form.stack input:not([type]),
form.stack input[type=url], form.stack input[type=number],
form.stack input[type=date], form.stack input[type=datetime-local],
form.stack input[type=email], form.stack select, form.stack textarea {
  font: inherit;
  font-size: .9375rem;
  padding: .4375rem .625rem;
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-sm);
  background: var(--bg);
  color: var(--ink);
  width: 100%;
}
form.stack textarea { resize: vertical; line-height: 1.5; }
form.stack input:focus, form.stack select:focus, form.stack textarea:focus { border-color: var(--accent); }
form.stack fieldset {
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  padding: .875rem 1rem;
  background: var(--surface-sunk);
  display: flex;
  flex-direction: column;
  gap: .5rem;
}
form.stack legend { font-size: .8125rem; font-weight: 640; padding: 0 .375rem; }

/* One button treatment across both surfaces. A form's submit is the primary
   action; anything destructive says so in its own colour rather than looking
   identical to Save until it is pressed. */
button, form.stack button[type=submit] {
  font: inherit;
  font-size: .875rem;
  font-weight: 600;
  padding: .4375rem 1rem;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  background: var(--accent);
  color: var(--accent-ink);
  cursor: pointer;
}
button:hover { filter: brightness(1.08); }
form.stack button[type=submit] { align-self: flex-start; }
button.danger, form.stack button.danger[type=submit] {
  background: var(--major);
  color: #fff;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) button.danger,
  :root:not([data-theme="light"]) form.stack button.danger[type=submit] { color: #1a0505; }
}
:root[data-theme="dark"] button.danger,
:root[data-theme="dark"] form.stack button.danger[type=submit] { color: #1a0505; }

/* A control that is a link in everything but markup: it posts a form, so it
   has to be a button, but it belongs in a row of links. */
button.link {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: inherit;
  font-weight: 550;
  color: var(--accent);
  text-decoration: underline;
  cursor: pointer;
}
button.link:hover { filter: none; text-decoration-thickness: 2px; }
button.link.danger { background: none; color: var(--major-text); }

.form-error {
  border: 1px solid color-mix(in srgb, var(--major) 55%, transparent);
  border-left-width: 3px;
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--major) 10%, transparent);
  color: var(--major-text);
  padding: .75rem 1rem;
  margin: 0 0 1.5rem;
}
.warn { color: var(--partial-text); font-size: .8125rem; }

/* The outcome of a form submission, carried through the redirect. */
.notice {
  border: 1px solid var(--rule);
  border-left: 3px solid var(--operational);
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--operational) 7%, transparent);
  padding: .75rem 1rem;
  margin: 0 0 1.5rem;
}
.form-notice {
  border: 1px solid color-mix(in srgb, var(--maintenance) 50%, transparent);
  border-left-width: 3px;
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--maintenance) 10%, transparent);
  padding: .75rem 1rem;
  margin: 0 0 1rem;
}
label.inline { display: inline; font-weight: 400; }

/* Nested component checkboxes on the subscribe form: a tree, not bullets. */
ul.plain { list-style: none; padding-left: 1rem; margin: .25rem 0; }
ul.plain > li { margin: .125rem 0; }

details { margin-top: .75rem; }
summary { cursor: pointer; font-size: .875rem; font-weight: 550; color: var(--accent); }
summary::marker { color: var(--quiet); }

/* ======================================================= administration ==
   The console shares the palette so a state means the same thing in both
   places, but it is a working surface rather than a document: wider, denser,
   and never cached. */

body.admin { background: var(--canvas); }

/* The brand bar is fixed furniture. It exists so that every console page says
   where you are before you have read anything on it, and so "back to the
   index" is always in the same place rather than being the first of a row of
   links that differs page to page. */
.brandbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: .625rem;
  padding: .625rem 1.5rem;
  background: var(--surface);
  border-bottom: 1px solid var(--rule);
  box-shadow: var(--shadow-sm);
}
.brandbar .brand {
  font-weight: 700;
  letter-spacing: -.02em;
  text-decoration: none;
  font-size: .9375rem;
}
.brandbar .brand span { color: var(--accent); }
.brandbar .env {
  font-size: .6875rem;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--quiet);
  border: 1px solid var(--rule-strong);
  border-radius: 999px;
  padding: .0625rem .5rem;
}

.wrap.wide {
  max-width: 74rem;
  margin: 1.5rem auto 4rem;
  padding: 0 1.5rem;
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

/* Breadcrumbs, not a row of assorted links. The console's whole difficulty is
   that a monitor lives in a component, in a page, in a tenant, and the old
   header gave no way to tell which of those you were looking at. */
nav.crumbs { margin: 0 0 .625rem; font-size: .8125rem; }
nav.crumbs ol { list-style: none; display: flex; flex-wrap: wrap; align-items: center; gap: .375rem; margin: 0; padding: 0; }
nav.crumbs li { display: flex; align-items: center; gap: .375rem; color: var(--quiet); }
nav.crumbs li + li::before { content: "/"; color: var(--rule-strong); }
nav.crumbs a { color: var(--quiet); text-decoration: none; font-weight: 550; }
nav.crumbs a:hover { color: var(--ink); text-decoration: underline; }
nav.crumbs [aria-current] { color: var(--ink); font-weight: 620; }

.page-head { margin-bottom: 1.5rem; }
.page-head-row { display: flex; align-items: flex-start; gap: 1rem; flex-wrap: wrap; }
.page-head-row h1 { margin: 0; flex: 1 1 18rem; }
.page-head .sub { margin: .375rem 0 0; }

.admin .ident { display: flex; align-items: center; gap: .625rem; flex-wrap: wrap; }
.admin .ident form { display: inline; margin: 0; }

.admin section { margin-bottom: 2.25rem; }
.admin h3 { margin: 0 0 .25rem; }

/* A panel is the console's unit of content: a titled surface. Sections used
   to be separated by nothing but vertical space, so a long page read as one
   undifferentiated column of forms. */
.panel {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-sm);
  padding: 1.25rem 1.375rem;
}
.panel > h2 { margin: 0 0 .25rem; }
.panel > h2 + .quiet { margin-top: 0; }

/* The console's own section navigation. Without JavaScript these are anchor
   links to sections that are all present; the script turns them into tabs
   that show one at a time. Either way every section is reachable. */
.console-nav {
  display: flex;
  gap: .25rem;
  flex-wrap: wrap;
  list-style: none;
  margin: 0 0 1.5rem;
  padding: .25rem;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-sm);
  font-size: .875rem;
}
.console-nav a {
  display: block;
  padding: .375rem .875rem;
  border-radius: var(--r-sm);
  text-decoration: none;
  color: var(--quiet);
  font-weight: 600;
}
.console-nav a:hover { background: var(--surface-sunk); color: var(--ink); }
.console-nav a[aria-current] { background: var(--accent); color: var(--accent-ink); }

/* The shape of the page, counted. A component with no monitor is the item
   worth leading with: it renders green for ever whatever happens to the thing
   it claims to represent, and it is invisible in a list. */
.stats { display: flex; flex-wrap: wrap; gap: .5rem; margin: 0 0 1.25rem; padding: 0; list-style: none; }
.stat {
  display: flex;
  align-items: baseline;
  gap: .4375rem;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: .25rem .875rem;
  font-size: .8125rem;
  color: var(--quiet);
  box-shadow: var(--shadow-sm);
}
.stat b { font-size: .9375rem; font-weight: 680; color: var(--ink); }
.stat.attention { border-color: color-mix(in srgb, var(--partial) 55%, transparent); background: color-mix(in srgb, var(--partial) 9%, transparent); color: var(--partial-text); }
.stat.attention b { color: var(--partial-text); }

/* ------------------------------------------------------- component tree -- */

/* The tree is the whole point of this surface. A page owns components, a
   component owns components and monitors, and a monitor is the only thing
   that can actually change a state. The old console rendered that as a flat
   list of articles with a depth-N left margin, which is a picture of
   indentation rather than a picture of ownership. */
ul.tree, ul.tree ul { list-style: none; margin: 0; padding: 0; }
ul.tree ul.tree {
  margin: .5rem 0 0 1.25rem;
  padding-left: 1.5rem;
  border-left: 2px solid var(--accent);
  border-image: linear-gradient(var(--accent), color-mix(in srgb, var(--accent) 25%, transparent)) 1;
}
ul.tree > li { margin: 0 0 .75rem; position: relative; }
ul.tree ul.tree > li::before {
  /* The elbow joining a child to its parent's rail. */
  content: "";
  position: absolute;
  left: -1.5rem;
  top: 1.4375rem;
  width: 1.25rem;
  height: 2px;
  background: color-mix(in srgb, var(--accent) 55%, transparent);
}

.node-card {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}
/* A component holding an override is lying to the public on purpose. It says
   so on its own edge, not only in a badge that scrolls away. */
.node-card.is-overridden { border-left: 3px solid var(--degraded); }
.node-card.is-unmonitored { border-left: 3px solid var(--rule-strong); }
/* Receded rather than flagged. An undeclared component is not a fault to fix
   -- it is what one is supposed to look like once nothing declares it -- so it
   reads as present and inactive rather than competing with the live tree. */
.node-card.is-orphaned { border-left: 3px solid var(--rule); opacity: .72; }

.node-head {
  display: flex;
  align-items: center;
  gap: .625rem;
  padding: .75rem 1rem;
  flex-wrap: wrap;
}
.node-head .node-title { font-weight: 640; font-size: .9375rem; }
.node-head .spacer { flex: 1 1 auto; }

/* The disclosure toggle. It is a real <button> only when the script is
   present; without it the markup is a plain span and everything is open. */
.node-toggle {
  flex: none;
  width: 1.5rem;
  height: 1.5rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-sm);
  background: var(--surface-sunk);
  color: var(--ink);
  font-size: .8125rem;
  line-height: 1;
  padding: 0;
  cursor: pointer;
}
.node-toggle:hover { background: var(--rule); filter: none; }
.node-toggle::before { content: "\25BE"; }
/* Only the glyph. What a collapsed node actually hides is set by the script,
   on the elements themselves -- see admin.js. A stylesheet rule describing the
   tree's shape a second time is a copy that can fall out of step with the
   markup without anything failing, and this one did: it was written for a flat
   list where the card carried the attribute and the children were its sibling,
   so after the tree was nested it matched nothing and the control silently
   stopped working. The path below is the one admin.js walks. */
[data-collapsed="true"] > .node-card > .node-head > .node-toggle::before { content: "\25B8"; }

.node-body { border-top: 1px solid var(--rule); padding: .75rem 1rem 1rem; background: var(--surface-sunk); }

/* A row of actions, not a paragraph of links squeezed together. */
.toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: .625rem; font-size: .8125rem; }
.toolbar form { display: inline; margin: 0; }
.toolbar a { color: var(--accent); text-decoration: none; font-weight: 550; }
.toolbar a:hover { text-decoration: underline; }
.toolbar .sep { color: var(--rule-strong); }
/* The copy button swaps its own label to "copied" and back. Without a floor
   on the width the whole toolbar reflows twice per click. */
.copy-id { min-width: 5.5rem; text-align: left; }
.move-group { display: inline-flex; gap: .1875rem; }
.move-group button {
  background: var(--surface-sunk);
  border: 1px solid var(--rule-strong);
  color: var(--quiet);
  padding: .0625rem .4375rem;
  font-size: .75rem;
  text-decoration: none;
}
.move-group button:hover { color: var(--ink); filter: none; }

/* The figures a latency panel leads with. They come before the chart because
   they answer the questions somebody arrives with -- what is it now, what is
   normal, how bad does it get -- and the chart answers only "which way is it
   going". */
dl.figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: .5rem 1rem;
  margin: .5rem 0 .75rem;
}
dl.figures > div { min-width: 0; }
dl.figures dt {
  font-size: .6875rem;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--quiet);
}
dl.figures dd { margin: .125rem 0 0; font-size: .8125rem; }
dl.figures dd b { font-size: 1.0625rem; font-weight: 640; font-variant-numeric: tabular-nums; }
dl.figures dd .quiet { display: block; font-size: .75rem; }

/* Something to measure a point against. A polyline in a 48-pixel box conveys a
   shape and no magnitude without them. Faint on purpose: they are a reference,
   not part of the reading. */
/* Named -axis, not -grid: .latency-grid is already the panel layout
   container, and a stroke rule landing on a flex container is the kind of
   quiet collision that survives review. */
.latency-axis {
  stroke: var(--rule);
  stroke-width: 1;
  stroke-dasharray: 2 3;
}
/* ----------------------------------------------------------- monitors -- */

/* Monitors are rendered as the leaves they are: inside the component that
   owns them, marked so they cannot be mistaken for a nested component. */
ul.monitors { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .375rem; }
li.monitor {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  font-size: .8125rem;
}
li.monitor.is-paused { background: var(--surface-sunk); }
li.monitor.is-orphaned { background: var(--surface-sunk); opacity: .72; }
.monitor-row {
  display: flex;
  align-items: center;
  gap: .5rem;
  flex-wrap: wrap;
  padding: .5rem .75rem;
}
.monitor-row .mon-name { font-weight: 620; font-size: .875rem; }
li.monitor.is-paused .mon-name,
li.monitor.is-orphaned .mon-name { color: var(--quiet); }
.monitor-row .mon-target {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .75rem;
  color: var(--quiet);
  overflow-wrap: anywhere;
  /* Its own line below the name and chips. Sharing the row meant a long mTLS
     URL wrapped into three lines and shoved the controls out of alignment
     with every other monitor on the page. */
  flex: 1 0 100%;
  order: 2;
  min-width: 0;
}
.monitor-row .mon-actions { display: flex; gap: .625rem; align-items: center; margin-left: auto; white-space: nowrap; order: 1; }
.monitor-row .mon-actions form { display: inline; margin: 0; }
/* The monitor's own settings, folded under the row it belongs to rather than
   collected into a list of drawers all called "Edit". */

/* The chip carries a monitor's configuration at a glance -- kind, cadence,
   hubs -- because those three are what someone is checking when they open
   this page at all. */
.chip {
  display: inline-block;
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-sm);
  padding: 0 .375rem;
  font-size: .6875rem;
  font-weight: 600;
  color: var(--quiet);
  white-space: nowrap;
}
.chip.kind-https_mtls { border-color: color-mix(in srgb, var(--maintenance) 55%, transparent); color: var(--maintenance-text); background: color-mix(in srgb, var(--maintenance) 9%, transparent); }
.chip.kind-cert_expiry { border-color: color-mix(in srgb, var(--degraded) 55%, transparent); color: var(--degraded-text); background: color-mix(in srgb, var(--degraded) 9%, transparent); }
/* The revocation kinds, sharing the certificate colour: all three are about a
   certificate rather than about a service, which is the distinction worth
   seeing in a list of a component's monitors. */
.chip.kind-ocsp,
.chip.kind-crl { border-color: color-mix(in srgb, var(--degraded) 55%, transparent); color: var(--degraded-text); background: color-mix(in srgb, var(--degraded) 9%, transparent); }

/* The empty state that matters. A component with no monitor is a green light
   wired to nothing, so it is not an absence to be noted in grey -- it is the
   one thing on the page to act on. */
.no-monitors {
  display: flex;
  align-items: center;
  gap: .75rem;
  flex-wrap: wrap;
  border: 1px dashed var(--rule-strong);
  border-radius: var(--r-sm);
  padding: .625rem .875rem;
  font-size: .8125rem;
  color: var(--quiet);
  background: var(--surface);
}

/* Drawers. Every write the console can do is one of these: closed by default
   so the page is readable, open on click, working without script. */
.drawers {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: .25rem;
  margin-top: .875rem;
  align-items: start;
}
/* An open drawer holds a form, so it takes the full width and leaves the
   closed ones packed alongside each other. */
.drawers details[open] { grid-column: 1 / -1; }
.drawers details { margin: 0; border: 1px solid var(--rule); border-radius: var(--r-sm); background: var(--surface); }
.drawers details[open] { box-shadow: var(--shadow-sm); }
.drawers summary { padding: .4375rem .625rem; font-size: .8125rem; list-style-position: inside; }
.drawers details[open] > summary { border-bottom: 1px solid var(--rule); }
.drawers .drawer-body { padding: .25rem .875rem 1rem; }
.drawers summary.danger { color: var(--major-text); }

.badge.excluded { color: var(--degraded-text); border-color: var(--degraded); background: color-mix(in srgb, var(--degraded) 10%, transparent); }

/* A withdrawn incident recedes rather than alarms: it is a settled state, not
   something waiting to be dealt with. Same treatment as an orphaned node. */
.badge.withdrawn { color: var(--quiet); border-color: var(--rule-strong); }
.incident-admin.withdrawn { opacity: .72; }
.incident-admin.withdrawn h3 { text-decoration: line-through; text-decoration-color: var(--rule-strong); }
.incident-admin.withdrawn h3 .badge { text-decoration: none; }

/* One timeline entry in the correction drawer. Ruled off from the next so the
   Save and Delete buttons are unambiguously attached to the text above them. */
.timeline-entry + .timeline-entry { border-top: 1px solid var(--rule); padding-top: .875rem; margin-top: .875rem; }

/* ------------------------------------------------------ lists & tables -- */

.tenant {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-sm);
  padding: 1rem 1.125rem;
  margin-bottom: .875rem;
}
.tenant h3 { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }

ul.pages { list-style: none; padding: 0; margin: .75rem 0 0; display: flex; flex-direction: column; gap: .25rem; }
ul.pages li {
  padding: .5rem .75rem;
  display: flex;
  gap: .625rem;
  align-items: center;
  flex-wrap: wrap;
  background: var(--surface-sunk);
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  font-size: .875rem;
}
ul.pages li a { font-weight: 620; }
ul.pages li form { display: inline; margin: 0; margin-left: auto; }

table.monitors, table.checks { width: 100%; border-collapse: collapse; margin-top: .75rem; font-size: .8125rem; }
table.monitors th, table.checks th {
  text-align: left;
  font-size: .6875rem;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--quiet);
  font-weight: 650;
}
table.monitors td, table.monitors th,
table.checks td, table.checks th { border-bottom: 1px solid var(--rule); padding: .4375rem .625rem .4375rem 0; vertical-align: top; }
table.checks tbody tr:hover { background: var(--surface-sunk); }
td.target, td.detail { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .75rem; overflow-wrap: anywhere; }
td.actions { white-space: nowrap; }
td.actions form { display: inline; margin-left: .75rem; }
.on  { color: var(--operational-text); font-weight: 600; }
.off { color: var(--quiet); }

/* The phase breakdown of a check. Laid out as a row of chips in the order the
   phases happen, so the eye lands on the sequence -- which is the diagnosis --
   rather than on seven numbers it has to sort itself. */
.phases { display: flex; flex-wrap: wrap; gap: .25rem .375rem; margin-top: .25rem; }
.phase {
  display: inline-block; padding: .0625rem .3125rem; border-radius: .1875rem;
  background: var(--surface-sunk); color: var(--quiet); white-space: nowrap;
}
.phase b { color: var(--text); font-weight: 600; }
/* The phase that started and never finished. On a timeout every duration
   beside it is a partial reading and this is the answer, so it is the one
   thing in the row that is not grey. */
.phase.stalled { background: color-mix(in srgb, var(--major) 12%, transparent); color: var(--major-text); font-weight: 600; }
.detail-error { display: block; }

tr.verdict-bad      td { color: var(--major-text); }
tr.verdict-degraded td { color: var(--degraded-text); }
tr.verdict-unknown  td { color: var(--quiet); }

.incident-admin {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-sm);
  padding: 1rem 1.125rem;
  margin-bottom: .75rem;
}
.incident-admin.open { border-left: 3px solid var(--major); }
.incident-admin h3 { display: flex; align-items: center; gap: .375rem; flex-wrap: wrap; }

.controls { display: flex; gap: .75rem; align-items: center; flex-wrap: wrap; margin: .5rem 0; }
.controls form { display: inline; margin: 0; }

/* ------------------------------------------------------- filter (JS) -- */

/* Added by the script and removed with it: a console with forty components
   is unusable without a way to find one, and a console with four does not
   need a search box cluttering it. */
.tree-tools { display: none; gap: .625rem; align-items: center; flex-wrap: wrap; margin: 0 0 1rem; }
.tree-tools.ready { display: flex; }
.tree-tools input[type=search] {
  font: inherit;
  font-size: .875rem;
  padding: .375rem .625rem;
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--ink);
  min-width: 15rem;
}
.tree-tools button {
  background: var(--surface);
  border: 1px solid var(--rule-strong);
  color: var(--quiet);
  font-size: .8125rem;
}
.tree-tools button:hover { color: var(--ink); filter: none; }
.tree-tools .count { font-size: .8125rem; color: var(--quiet); }

/* The subscriber search. Same furniture as the tree filter, minus the
   .ready gate -- this one is a real form that submits, so it works without
   the script and must never be hidden waiting for one. */
form.filter { display: flex; gap: .625rem; align-items: center; flex-wrap: wrap; margin: 0 0 1rem; }
form.filter label { font-size: .8125rem; color: var(--quiet); }
form.filter input[type=search] {
  flex: 1 1 18rem;
  max-width: 24rem;
  padding: .375rem .625rem;
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: .8125rem;
}
[hidden] { display: none !important; }

/* ------------------------------------------------------- api key issue -- */

/* The issued key is shown once, so it has to be selectable and unmistakable
   rather than styled like the rest of the table. */
code.token {
  display: inline-block;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .9375rem;
  overflow-wrap: anywhere;
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-sm);
  padding: .625rem .75rem;
  background: var(--surface);
}

/* ------------------------------------------------ accessibility basics -- */

/* Content that exists for a screen reader and nothing else. Never
   display:none, which removes it from the accessibility tree as well. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The skip link is the first thing in the tab order on every page. A status
   page puts the component tree ahead of the incident someone came to read,
   and a keyboard user should not have to tab through ninety bars to reach
   it. */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--bg);
  color: var(--ink);
  border: 2px solid var(--accent);
  border-radius: 0 0 var(--r-sm) 0;
  padding: .5rem .75rem;
  z-index: 100;
}
.skip-link:focus { left: 0; }

/* A visible focus ring everywhere, including on the elements whose default
   ring the resets above would have removed. Two pixels and an offset so it
   survives against both the light and the dark background. */
:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: .125rem;
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* ------------------------------------------------------- theme switch -- */

/* Three states, not two. A two-state toggle has to assume a starting point,
   and whichever it assumes is wrong for every reader whose system already
   says otherwise -- so "System" is a real option and it is the default.
   Rendered hidden and revealed by the script, because a theme control that
   cannot store a choice is a button that does nothing. */
.theme-switch {
  display: inline-flex;
  align-items: center;
  gap: .125rem;
  padding: .1875rem;
  background: var(--surface-sunk);
  border: 1px solid var(--rule);
  border-radius: 999px;
}
.theme-switch button {
  background: none;
  border: 0;
  border-radius: 999px;
  padding: .1875rem .5625rem;
  font-size: .75rem;
  font-weight: 600;
  color: var(--quiet);
  cursor: pointer;
}
.theme-switch button:hover { color: var(--ink); background: var(--bg); filter: none; }
.theme-switch button[aria-pressed="true"] {
  background: var(--bg);
  color: var(--ink);
  box-shadow: var(--shadow-sm);
}

/* On the public page the switch sits at the end of the section navigation,
   pushed to the trailing edge; on the console it sits in the brand bar. */
/* The masthead: the page's name, and how it is presented.
   
   The preference controls live here rather than in the section navigation,
   which is five links wide at a fixed measure and only had room for them in
   the language it was laid out in. This row holds one heading, so it has room
   in every language -- and on a phone, where it does not, the controls wrap
   under the title rather than into the middle of the navigation. */
.masthead {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: .5rem 1rem;
}
.masthead h1 { margin: 0; }
.prefs { display: flex; align-items: center; gap: .5rem; }
.brandbar .theme-switch { margin-left: auto; }

/* ---------------------------------------------------------- utilities -- */

/* A short list, kept short on purpose. These exist because a Content-Security
   Policy worth having cannot allow style-src 'unsafe-inline', and a style
   attribute is indistinguishable to the browser from one an injection wrote.
   Eight shortcuts in the console templates were the whole reason the policy
   would have needed that hole. */
.flush-top { margin-top: 0; }
.tight-below { margin-bottom: .75rem; }
.full-width { max-width: none; }
form.inline { display: inline; margin: 0; }

/* ------------------------------------------------- client identities -- */

.certificate {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-sm);
  padding: 1rem 1.125rem;
  margin-bottom: .875rem;
}
/* An identity with no certificate yet is not a problem, but it is unfinished
   work, and it cannot be given to a monitor. It reads as outstanding rather
   than as broken. */
.certificate.is-pending { border-left: 3px solid var(--degraded); }
.certificate h3 { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }

/* The three steps of issuance. Numbered because the order is the whole point:
   the request has to exist before a PKI can issue against it. */
ol.steps { margin: .75rem 0 0; padding-left: 1.25rem; font-size: .875rem; }
ol.steps li { margin: .375rem 0; }

/* A signing request, shown so it can be selected and copied. It is public
   material -- there is deliberately nothing here that needs hiding. */
pre.pem {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .75rem;
  line-height: 1.45;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  background: var(--surface-sunk);
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  padding: .75rem .875rem;
  margin: .5rem 0 0;
}

/* Where a control used to be.
   The console kept every operational surface and lost the configuration ones,
   so a person who has used this before will look for a form that is no longer
   there. Saying what owns it now, in the place the form stood, is the whole
   point -- an absence explains nothing. */
.managed-note {
  margin: 0;
  padding: .75rem .875rem;
  border: 1px dashed var(--rule-strong);
  border-radius: var(--r-md);
  background: var(--surface-sunk);
  color: var(--quiet);
  font-size: .8125rem;
  line-height: 1.5;
}
.managed-note code {
  color: var(--ink);
}

/* ------------------------------------------------- per-hub latency -- */

/* One chart per hub, side by side. Never one chart with three lines: three
   hubs measure three different journeys, and overlaying them invites exactly
   the comparison the design refuses to make. */
.latency-grid { display: grid; gap: .875rem; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); }
.latency-panel h3 { margin: 0 0 .25rem; font-size: .875rem; }
.latency-panel svg { width: 100%; height: 72px; display: block;
                     border-bottom: 1px solid var(--rule); }
.latency-panel .scale { display: flex; justify-content: space-between;
                        color: var(--quiet); font-size: .75rem; margin: .375rem 0 0; }
polyline.latency-line.p50 { stroke: var(--accent); stroke-width: 1.5; }

/* ------------------------------------------------------- subscribers -- */

/* A mask is rendered in the monospace face so that the asterisks line up
   down the column and the eye reads it as redaction rather than as an odd
   address. The revealed form is deliberately different, because the one
   thing an operator must never do is mistake a real address for a mask and
   paste it somewhere. */
.masked { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--quiet); }
.revealed {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  background: color-mix(in srgb, var(--degraded) 14%, transparent);
  border-bottom: 1px solid var(--degraded);
  padding: .0625rem .25rem;
  border-radius: var(--r-sm);
}
.reveal { font-size: .75rem; margin-left: .5rem; }

.badge.sub-confirmed    { color: var(--operational-text); border-color: var(--operational); background: color-mix(in srgb, var(--operational) 10%, transparent); }
.badge.sub-unverified   { color: var(--degraded-text);    border-color: var(--degraded);    background: color-mix(in srgb, var(--degraded) 10%, transparent); }
.badge.sub-suppressed   { color: var(--major-text);       border-color: var(--major);       background: color-mix(in srgb, var(--major) 10%, transparent); }
.badge.sub-unsubscribed { color: var(--quiet);            border-color: var(--rule-strong); }

/* The language switch. Sits beside the theme control in the navigation, the
   same shape and weight: both are preferences about how the page is presented
   rather than places to go, and one dressed louder than the other would read
   as more important than it is. */
.lang-switch { display: inline-flex; gap: .125rem; align-items: center; }
.lang-switch > button,
.lang-switch > span {
  font: inherit;
  font-size: .75rem;
  /* Two letters, read as a pair of codes rather than as words. The tracking
     stops EN and PT collapsing into something that looks like one token. */
  letter-spacing: .03em;
  padding: .1875rem .4375rem;
  border-radius: .25rem;
  border: 1px solid transparent;
  background: none;
  color: var(--quiet);
  cursor: pointer;
}
.lang-switch span[aria-current] {
  color: var(--ink);
  background: var(--surface);
  border-color: var(--rule);
  cursor: default;
}
.lang-switch > button:hover { color: var(--ink); }
