/*!************************************************************************************************************************************************************************************************************************************************************************************!*\
  !*** css ../../node_modules/next/dist/build/webpack/loaders/css-loader/src/index.js??ruleSet[1].rules[14].oneOf[12].use[2]!../../node_modules/next/dist/build/webpack/loaders/postcss-loader/src/index.js??ruleSet[1].rules[14].oneOf[12].use[3]!../../packages/ui/src/tokens.css ***!
  \************************************************************************************************************************************************************************************************************************************************************************************/
/*
 * The workstation design system — every token, in one place.
 *
 * Extracted verbatim from the working reference files, which are the binding contract:
 * `platform-overview-hero.html` and `topology-radial.html`. Where those files and the v4 spec in
 * `repo/claude/prompts/design.md` disagree, the files win — the spec describes them, it does not
 * override them. Nothing here is invented; every value below appears in one of the two files, and
 * the few that come from the written brief instead (bar/rail/status-bar heights, the motion
 * durations) are marked as such.
 *
 * ## Why this is scoped to `.workstation` and not to `:root`
 *
 * Three token names collide with the design system already shipping — `--ok`, `--warn` and
 * `--critical` — and they collide at *different values*: the current product's `--ok` is #3FB950,
 * the reference's is #8BE85C. Putting these on `:root` would silently recolour the status of every
 * one of the forty-odd pages that have not been migrated yet, from a change whose scope is two
 * pages. So the whole palette hangs off one class, the migrated surfaces opt in, and the two
 * systems coexist until the last page moves.
 *
 * It buys a second thing for free. The current stylesheet has a light theme; the references are
 * dark-only and the brief forbids adding one. Scoped like this, a workstation surface cannot be
 * inverted by a `data-theme` a parent set, because these values are not conditional on it.
 *
 * When the migration finishes, this block moves to `:root`, the old palette is deleted, and the
 * `.workstation` class disappears. That is the only intended end state — a permanent two-palette
 * stylesheet would be worse than either one alone.
 *
 * ## The rule that makes it look like the references
 *
 * **Chrome has no accent colour.** There is no `--accent` here and there must not be one. Links,
 * active navigation and primary buttons are built from weight and a `--raised` fill, never a hue.
 * Colour appears only on data (`--ch-*`) and on status (`--ok`/`--warn`/`--crit`), and those two
 * are different palettes with different meanings even where they share a hex — see below.
 */

.workstation {
  /* ── chrome ─────────────────────────────────────────────────────────────
     Eight greys, darkest to lightest, and nothing else. Every surface, every
     border and every piece of text in the frame comes from these. */
  --void: #07090C;      /* the page ground, behind everything */
  --panel: #0D1117;     /* a pane's own fill */
  --raised: #141A22;    /* hover, fills, the "pressed" state that replaces an accent */
  --line: #1C242E;      /* the ordinary 1px separation */
  --line-hot: #2A3542;  /* a border asking to be noticed: focus, active, a plate's edge */
  --ink: #E8EDF3;       /* primary text */
  --ink-2: #8494A6;     /* secondary text, table cells */
  --ink-3: #4E5A69;     /* labels, captions, axis text */

  /* ── data ───────────────────────────────────────────────────────────────
     Series colours. These carry a *quantity* or a *series identity* — a link's
     path quality, a bar's category, a gauge's fill. They never mean "healthy"
     or "broken"; that is what the status triple below is for. */
  --ch-cyan: #4DE1E8;
  --ch-lime: #8BE85C;
  --ch-amber: #FFB03A;
  --ch-rose: #FF5C6E;
  --ch-violet: #B388FF;

  /* ── status ─────────────────────────────────────────────────────────────
     Three values that carry a *verdict*.

     Note that they are the same three hexes as --ch-lime / --ch-amber / --ch-rose,
     and that this is the reference files' own choice: `const C = {ok:'#8BE85C',
     warn:'#FFB03A',crit:'#FF5C6E'}` in platform-overview-hero.html. The written
     brief says status and data "are different palettes ... never mix them", which
     read as a value rule would contradict the files. It is a *reference* rule: the
     token you name must match the meaning you intend, so that the day the status
     ramp is retuned for contrast, no chart moves with it. A bar series reaching for
     --ok, or a health dot reaching for --ch-lime, is the defect — not the hex. */
  --ok: #8BE85C;
  --warn: #FFB03A;
  --crit: #FF5C6E;

  /* ── type ───────────────────────────────────────────────────────────────
     Three families, self-hosted (see the @font-face block in styles.css).
     Archivo Expanded for panel and section titles, Inter for prose, and
     JetBrains Mono for every number, label, identifier and timestamp. */
  --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  --sans: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  --display: "Archivo Expanded", "Inter", system-ui, sans-serif;

  /* Sizes, named for the job each does in the references rather than by a step
     number, because the scale is deliberately dense and half-stepped — 8.5 and
     9.5 are real values in those files, not rounding. A 1440px screen showing
     fewer than ~40 meaningful data points is under-built, and this is the scale
     that makes that possible. */
  --fs-hub: 8px;        /* the smallest caption in a graph */
  --fs-node: 8.5px;     /* node and leaf labels */
  --fs-label: 9px;      /* SECTION HEADERS, table th, legends */
  --fs-num-sm: 9.5px;   /* small right-aligned numerics */
  --fs-meta: 10px;      /* status bar, bar-list names, hud subtitles */
  --fs-meta-lg: 10.5px; /* rail values, connection rows */
  --fs-body: 11px;      /* table cells, breadcrumb, inputs */
  --fs-title: 12px;     /* display titles, empty-state prose */
  --fs-ui: 13px;        /* body default, rail headings */
  --fs-stat: 19px;      /* the number in a stat pair */

  --ls-label: 0.13em;   /* uppercase section headers */
  --ls-title: 0.05em;   /* display titles */
  --ls-wide: 0.22em;    /* signage set on a lit ground */

  /* ── geometry ───────────────────────────────────────────────────────────
     The three frame heights come from the written brief, not the reference
     files; everything else is measured out of them. */
  --h-cmdbar: 38px;
  --w-iconrail: 46px;
  --h-statusbar: 26px;

  --r-hair: 2px;        /* bar tracks and fills, graph leaf markers */
  --r-sm: 4px;          /* the brand mark, scrollbar thumb, branch pills */
  --r-md: 5px;          /* connection rows */
  --r-lg: 6px;          /* plates */
  /* 14px on a 28px control is a pill, not a corner — the brief's "no radius
     above 8px" is about corners, and the files show .search and .btn at 14. */
  --r-pill: 14px;

  /* ── motion ─────────────────────────────────────────────────────────────
     Durations from the brief; the easing is the reference files' own. Nothing
     loops except the critical pulse and the live beacon. */
  --m-hover: 120ms;
  --m-panel: 160ms;
  --m-slide: 200ms;
  --ease: cubic-bezier(.2, 0, 0, 1);
}

/*
 * The frame itself. `overflow: hidden` on both is what makes panes scroll and the
 * page never scroll — bypassing it is the failure mode the brief's self-check #4
 * asks about, and it is only visible once a pane overflows.
 */
.workstation {
  background: var(--void);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--fs-ui);
  /* Tabular figures everywhere, so a column of numbers lines up and a changing
     value does not reflow the row it sits in. */
  font-feature-settings: "tnum" 1;
  -webkit-font-smoothing: antialiased;
}

/*
 * Reduced motion, honoured centrally.
 *
 * The reference files check `matchMedia('(prefers-reduced-motion: reduce)')` in JS and settle the
 * force simulation synchronously instead of animating it — that part has to stay in the components,
 * because it is a different algorithm, not a different duration. This handles everything that is
 * merely a transition or a loop.
 */
@media (prefers-reduced-motion: reduce) {
  .workstation *,
  .workstation *::before,
  .workstation *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
  }
}

/* Focus is the one place a hue is allowed into the chrome, because an outline that
   is only a weight change is not a focus indicator. --ch-cyan, per the brief. */
.workstation :focus-visible {
  outline: 2px solid var(--ch-cyan);
  outline-offset: 2px;
  border-radius: var(--r-hair);
}

/*!*****************************************************************************************************************************************************************************************************************************************************************************************!*\
  !*** css ../../node_modules/next/dist/build/webpack/loaders/css-loader/src/index.js??ruleSet[1].rules[14].oneOf[12].use[2]!../../node_modules/next/dist/build/webpack/loaders/postcss-loader/src/index.js??ruleSet[1].rules[14].oneOf[12].use[3]!../../packages/ui/src/workstation.css ***!
  \*****************************************************************************************************************************************************************************************************************************************************************************************/
/*
 * Workstation primitives — the component layer over `tokens.css`.
 *
 * Every rule here is transcribed from the two reference files. Where a value looks arbitrary it is
 * arbitrary *in the reference* (74px bar labels, 13px 14px section padding, 55ms stagger), and it
 * is kept rather than rounded, because "side-by-side with its reference at 1440px" is the brief's
 * own acceptance test and rounding is how a rebuild drifts one pixel at a time.
 *
 * All of it is nested under `.workstation` — see the note in tokens.css for why the migration is
 * scoped rather than global.
 */

/* ── Pane: header + a body that scrolls inside itself ─────────────────────
   From `.feed` in platform-overview-hero.html. The `min-height: 0` is the part
   that matters and the part that gets dropped: without it a grid child refuses
   to shrink below its content, the body never becomes scrollable, and the page
   scrolls instead — which is self-check #4 in the brief. */
.workstation .ws-pane {
  display: grid;
  grid-template-rows: auto 1fr;
  min-height: 0;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  overflow: hidden;
}

.workstation .ws-pane-h {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 14px;
  border-bottom: 1px solid var(--line);
}

.workstation .ws-pane-title {
  font-family: var(--display);
  font-size: var(--fs-body);
  letter-spacing: var(--ls-title);
  color: var(--ink);
}

.workstation .ws-pane-meta {
  font-family: var(--mono);
  font-size: var(--fs-meta);
  color: var(--ink-3);
}

.workstation .ws-pane-push { flex: 1 }

.workstation .ws-pane-b {
  overflow: auto;
  overscroll-behavior: contain;
  min-height: 0;
}

/* ── Section: the quieter rail block ──────────────────────────────────────
   From `.sect`. The references contain two distinct pane shapes and the brief
   names one; this is the other. No scrolling body, a mono uppercase header,
   and a hairline underneath rather than a border all round. */
.workstation .ws-section {
  border-bottom: 1px solid var(--line);
  padding: 13px 14px;
}
.workstation .ws-section:last-child { border-bottom: 0 }

.workstation .ws-section-h {
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 11px;
}

/* ── StatPair ─────────────────────────────────────────────────────────── */
.workstation .ws-pairs {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 13px;
}

.workstation .ws-pair-l {
  font-family: var(--mono);
  font-size: var(--fs-num-sm);
  color: var(--ink-3);
  margin-bottom: 4px;
}

.workstation .ws-pair-v {
  font-family: var(--mono);
  font-size: var(--fs-stat);
  font-weight: 500;
  letter-spacing: -.02em;
  color: var(--ink);
}

.workstation .ws-pair-v small {
  font-size: var(--fs-meta);
  color: var(--ink-3);
  font-weight: 400;
}

.workstation .ws-pair-d {
  font-family: var(--mono);
  font-size: var(--fs-label);
  color: var(--ink-3);
  margin-top: 3px;
}

/* ── RadialGauge ──────────────────────────────────────────────────────── */
.workstation .ws-gauges {
  display: flex;
  justify-content: space-between;
  gap: 8px;
}
.workstation .ws-gauge { text-align: center; flex: 1 }
.workstation .ws-gauge svg { display: block; margin: 0 auto }
.workstation .ws-gauge-lbl {
  font-family: var(--mono);
  font-size: var(--fs-label);
  color: var(--ink-3);
  margin-top: 5px;
}
.workstation .ws-gauge-sub {
  font-family: var(--mono);
  font-size: var(--fs-label);
  color: var(--ink-2);
  margin-top: 1px;
}

/* ── BarList ──────────────────────────────────────────────────────────── */
.workstation .ws-bars { display: flex; flex-direction: column; gap: 7px }

.workstation .ws-bar {
  display: grid;
  grid-template-columns: 74px 1fr 40px;
  align-items: center;
  gap: 9px;
}

.workstation .ws-bar-n {
  font-family: var(--mono);
  font-size: var(--fs-meta);
  color: var(--ink-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.workstation .ws-bar-t {
  height: 4px;
  background: var(--raised);
  border-radius: var(--r-hair);
  overflow: hidden;
}

.workstation .ws-bar-f {
  /*
   * DEVIATION from platform-overview-hero.html, and the only one in this file.
   *
   * The reference renders the fill as a `<span>` inside another `<span>`, which is an inline box —
   * and `width`, `height` and `transform` all have no effect on one. Measured against the reference
   * markup verbatim: the track lays out at 168x4 and the fill at 0x0, so the bars in that file draw
   * as empty grey lines while its own script carefully computes a percentage and a colour that
   * nothing ever displays.
   *
   * The CSS says plainly what it means to do — a fill of a given width, grown from zero with a
   * transform — so this is a defect in the reference rather than a design decision to copy. One
   * declaration makes the rest of the rule work as written. `workstation-tokens.test.ts` pins it.
   */
  display: block;
  height: 100%;
  border-radius: var(--r-hair);
  transform-origin: left;
  transform: scaleX(0);
  animation: ws-grow .6s var(--ease) forwards;
}

@keyframes ws-grow { to { transform: scaleX(1) } }

.workstation .ws-bar-v {
  font-family: var(--mono);
  font-size: var(--fs-num-sm);
  color: var(--ink-3);
  text-align: right;
}

/* ── StatusDot ────────────────────────────────────────────────────────────
   Dot AND word, always. The brief's accessibility rule, and the reason this is
   a component rather than a span: a bare coloured dot is unreadable to anyone
   who cannot separate the three hues, and it is the easiest thing to hand-roll
   wrongly. The glow is on crit only — restraint is what the references have. */
.workstation .ws-st {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--mono);
}
.workstation .ws-st i {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  flex: none;
}
.workstation .ws-st-ok { color: var(--ok) }
.workstation .ws-st-ok i { background: var(--ok) }
.workstation .ws-st-warn { color: var(--warn) }
.workstation .ws-st-warn i { background: var(--warn) }
.workstation .ws-st-crit { color: var(--crit) }
.workstation .ws-st-crit i { background: var(--crit); box-shadow: 0 0 6px var(--crit) }
.workstation .ws-st-idle { color: var(--ink-3) }
.workstation .ws-st-idle i { background: var(--ink-3) }

/* ── DataTable ────────────────────────────────────────────────────────── */
.workstation .ws-table { width: 100%; border-collapse: collapse }

.workstation .ws-table th {
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: .11em;
  text-transform: uppercase;
  color: var(--ink-3);
  font-weight: 400;
  text-align: left;
  padding: 7px 14px;
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  background: var(--panel);
  z-index: 1;
}

.workstation .ws-table td {
  padding: 6px 14px;
  border-bottom: 1px solid var(--line);
  font-family: var(--mono);
  font-size: var(--fs-body);
  color: var(--ink-2);
  white-space: nowrap;
}

.workstation .ws-table tbody tr:hover { background: var(--raised) }
/* Keyboard cursor for J/K. --raised again, never a hue: chrome takes no accent,
   so "where am I" is carried by fill and a left edge, not by colour. */
.workstation .ws-table tbody tr.is-cursor { background: var(--raised) }
.workstation .ws-table tbody tr.is-cursor td:first-child {
  box-shadow: inset 2px 0 0 var(--ink-3);
}
.workstation .ws-table td.ws-num { text-align: right; color: var(--ink) }
.workstation .ws-table td.ws-strong { color: var(--ink) }
.workstation .ws-table td.ws-dim { color: var(--ink-3) }

/* ── Skeleton ─────────────────────────────────────────────────────────────
   NOT extracted — console-workstation.html was not available, and the brief
   names it as a primitive to take from there. This is the brief's written
   description only ("shimmer on --raised") and should be replaced when the
   file arrives. Flagged in the component's doc comment too. */
.workstation .ws-skel {
  background: linear-gradient(90deg, var(--raised) 0%, var(--line-hot) 50%, var(--raised) 100%);
  background-size: 200% 100%;
  animation: ws-shimmer 1.4s ease-in-out infinite;
  border-radius: var(--r-hair);
}
@keyframes ws-shimmer { to { background-position: -200% 0 } }

/* ── EmptyState ───────────────────────────────────────────────────────────
   From `.empty` in topology-radial.html, plus the brief's rule: one sentence
   saying WHY it is empty, and one action. Never a zeroed chart or an empty
   progress bar — those read as broken, which was a real bug in the current UI. */
.workstation .ws-empty {
  padding: 24px 16px;
  color: var(--ink-3);
  font-size: var(--fs-title);
  line-height: 1.5;
  max-width: 52ch;
}
.workstation .ws-empty-act { margin-top: 12px }

/* ── Button: chrome, so no hue ────────────────────────────────────────── */
.workstation .ws-btn {
  height: 28px;
  padding: 0 14px;
  border-radius: var(--r-pill);
  border: 1px solid var(--line);
  background: rgba(13, 17, 23, .7);
  color: var(--ink-2);
  font-family: var(--mono);
  font-size: var(--fs-meta-lg);
  cursor: pointer;
  transition: border-color var(--m-hover) var(--ease), color var(--m-hover) var(--ease);
}
.workstation .ws-btn:hover { border-color: var(--line-hot); color: var(--ink) }

/* Scrollbars, from the reference. */
.workstation ::-webkit-scrollbar { width: 8px; height: 8px }
.workstation ::-webkit-scrollbar-thumb { background: var(--line-hot); border-radius: var(--r-sm) }

/*!***************************************************************************************************************************************************************************************************************************************************************************************!*\
  !*** css ../../node_modules/next/dist/build/webpack/loaders/css-loader/src/index.js??ruleSet[1].rules[14].oneOf[12].use[2]!../../node_modules/next/dist/build/webpack/loaders/postcss-loader/src/index.js??ruleSet[1].rules[14].oneOf[12].use[3]!../../packages/ui/src/marketing.css ***!
  \***************************************************************************************************************************************************************************************************************************************************************************************/
/*
 * The marketing surface: `/welcome`, and the announcement pill `/login` shares with it.
 *
 * ## Why this file exists
 *
 * `marketing.tsx` shipped complete — eighteen exported components, a hero, a comparison matrix, a
 * perspective console stack — writing `mk-*` class names that **nothing anywhere defined**. Not in
 * `styles.css`, not in `tokens.css`, not in any built CSS, and not in git history at any point. The
 * front door therefore served as a column of unstyled text: it typechecked, its tests passed, it
 * returned 200 with the right copy in the markup, and it had never been looked at. That is the exact
 * failure this repository already documents as trap 21 — a green suite is not evidence that a page
 * renders — and this file is the missing half.
 *
 * ## Why it is a separate file rather than more of `styles.css`
 *
 * Two reasons, and neither is tidiness. `styles.css` is around twenty thousand lines and is the file
 * this repository has the most scar tissue about: concurrent sessions edit it, a mutation check once
 * silently rewrote the wrong rule because an anchor matched three shared lines earlier in the file,
 * and a `git checkout --` of it once destroyed 769 uncommitted lines of someone else's work. Adding
 * eight hundred lines to it makes every one of those worse. And the marketing surface is genuinely
 * separate: two routes use it, no product page does, and nothing in here may leak onto a console
 * screen.
 *
 * It is pulled in by the `@import` block at the top of `styles.css`, beside `tokens.css` and
 * `workstation.css`, for the reason stated there — a consumer that imports the stylesheet and
 * forgets a second entry point gets a frame with no palette, which is the kind of half-applied change
 * nothing would catch. CSS allows `@import` only before other rules, so it stays at the top.
 *
 * ## The rules this surface obeys
 *
 * Every colour is a token. There are no hex values below, so the page follows the theme toggle and
 * the palette cannot drift from the product. The one deliberate departure from the console's own
 * design language is the hero's accent glow and the tilted stack: the console bans decorative
 * gradients and in-page shadows because they are noise on a dense data screen, and a front door is
 * not a data screen. Both are confined to `.mk-band-glow` and `.mk-stage`, and neither selector can
 * match anything outside this surface.
 */

/* ---------------------------------------------------------------- band, the structural unit */

/*
 * Seven full-bleed bands alternating ground. The alternation is what gives the page rhythm without
 * a single divider line; `--mk-measure` is the reading width every band centres its content in.
 */
.mk-band {
  --mk-measure: 1120px;
  --mk-gutter: clamp(20px, 5vw, 48px);
  position: relative;
  width: 100%;
  padding: clamp(56px, 8vw, 104px) var(--mk-gutter);
  overflow: hidden;
  /*
   * The nav is fixed, so an anchor jump would otherwise put the band's own heading underneath it —
   * every link in that nav points at one of these ids, so without this the page's own navigation
   * lands on a heading the reader cannot see.
   */
  scroll-margin-top: 84px;
}

.mk-band-page {
  background: var(--bg-page);
  color: var(--text-primary);
}
.mk-band-surface {
  background: var(--bg-surface);
  color: var(--text-primary);
}
/*
 * The accent ground. `--accent-soft` rather than `--accent`: the accent in this palette is a
 * near-white ink, so a band filled with it would be a white page with white text on it.
 */
.mk-band-accent {
  background: var(--accent-soft);
  color: var(--text-primary);
  border-top: 1px solid var(--color-border);
  border-bottom: 1px solid var(--color-border);
}

.mk-measure {
  max-width: var(--mk-measure);
  margin: 0 auto;
}
/* The logo row's dividers have to reach the band edge, so it opts out of the measure. */
.mk-bleed {
  max-width: none;
  margin: 0;
}

/*
 * The hero's two orbs.
 *
 * A flag on the band rather than `:first-of-type`, per the component's own note — that selector
 * matches on element type and would silently depend on no other section preceding it.
 */
.mk-band-glow::before,
.mk-band-glow::after {
  content: "";
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
  z-index: 0;
  filter: blur(80px);
  /*
   * 0.8, up from 0.55.
   *
   * At the old value the pair was doing nothing a reader would notice, which is a worse outcome
   * than not having it: the cost is paid in a blurred layer either way. The ceiling is set by the
   * headline — the wash must stay under the type rather than tint it, and past ~0.85 the left orb
   * starts lifting the first word off the ground colour.
   */
  opacity: 0.8;
}
.mk-band-glow::before {
  width: 46vw;
  height: 46vw;
  max-width: 620px;
  max-height: 620px;
  top: -18%;
  left: -12%;
  background: color-mix(in srgb, var(--mk-signal) 55%, transparent);
}
.mk-band-glow::after {
  width: 38vw;
  height: 38vw;
  max-width: 520px;
  max-height: 520px;
  top: 8%;
  right: -14%;
  /*
   * The violet half of the wash.
   *
   * It asked for `--accent-soft`, which is near-black in the dark theme — so this orb had never
   * been visible on any build. Same regression as the floor under the console stack and the hero
   * bar figure: the chrome accent went greyscale by design, and the decorative uses of it went
   * silently with it.
   */
  background: color-mix(in srgb, var(--mk-violet) 50%, transparent);
}
/* Content sits above the orbs; without this the blur paints over the headline. */
.mk-band > .mk-measure,
.mk-band > .mk-bleed {
  position: relative;
  z-index: 1;
}

/* ------------------------------------------------------------------------ shared type scale */

.mk-eyebrow {
  margin: 0 0 var(--space-3);
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.mk-heading {
  margin: 0 0 var(--space-5);
  font-size: clamp(26px, 3.4vw, 40px);
  line-height: 1.15;
  letter-spacing: -0.02em;
  font-weight: 600;
  text-wrap: balance;
  color: var(--text-primary);
}

.mk-lead {
  margin: 0 0 var(--space-6);
  max-width: 62ch;
  font-size: clamp(15px, 1.5vw, 18px);
  line-height: 1.6;
  color: var(--text-secondary);
}

/* ------------------------------------------------------------------------------ announcement */

/* The full-width bar above the nav. */
.mk-announce-bar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  padding: 9px var(--space-4);
  background: var(--bg-sunken);
  border-bottom: 1px solid var(--color-border);
  font-size: var(--text-sm);
  text-align: center;
}
.mk-announce-bar-text {
  color: var(--text-secondary);
}
.mk-announce-bar-action,
.mk-announce-action {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--text-primary);
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
}
.mk-announce-bar-action:hover,
.mk-announce-action:hover {
  text-decoration: none;
}

/* The pill form, shared with the sign-in pages. */
.mk-announce {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  justify-content: center;
  margin: 0 0 var(--space-5);
  padding: 6px 14px;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--bg-sunken);
  font-size: var(--text-sm);
}
.mk-announce-text {
  color: var(--text-secondary);
}

/* -------------------------------------------------------------------------------------- nav */

/*
 * A floating pill, not a bar across the top.
 *
 * The bar was a full-width translucent strip with a hairline under it — correct, unremarkable, and
 * the shape every admin console in this product already uses. A front page is not a console: the
 * reference sites all float the navigation as an object over the page rather than as a frame around
 * it, and the difference is the first thing a visitor sees.
 *
 * So the header itself paints nothing. The pill carries the ground, which is what lets the hero's
 * own starfield run underneath it and what makes the open mega menu read as a sheet in front of the
 * page rather than as part of the chrome.
 */
.mk-nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: var(--z-sticky);
  background: none;
  border-bottom: 1px solid transparent;
  transition:
    top 160ms var(--ease-out),
    background-color 160ms var(--ease-out),
    border-color 160ms var(--ease-out);
}
/* An announcement bar sits above, so the nav starts below it rather than on top of it. */
.mk-nav-offset {
  top: 38px;
}

/*
 * Once the page has scrolled, the header becomes a full-width bar.
 *
 * At the top of the page the bar paints nothing and the hero's sky runs under it — that is the
 * design. Scrolled, the same transparency put "Docs" and "Talk to us" directly over body copy with
 * no ground behind them (measured at 1440px), and left a 38px strip above the pill where the page
 * showed through, because the announcement bar it was offset for had scrolled away. `is-scrolled`
 * is set by `MarketingNav` the moment `scrollY` leaves zero.
 *
 * The header is already the containing block for the mega-menu panels (it is `position: fixed`), so
 * giving it a backdrop filter changes nothing about where they sit.
 */
.mk-nav.is-scrolled {
  background: color-mix(in srgb, var(--bg-page) 84%, transparent);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-bottom-color: var(--color-border);
}
.mk-nav-offset.is-scrolled {
  top: 0;
}

/*
 * Below 640px the announcement bar wraps to two lines, and the nav — offset by the one-line height —
 * covered the second, leaving "Which ones" half under the pill. The bar is a pointer to a band, not
 * content a phone reader loses anything by not seeing, so it goes, and the nav sits at the top.
 */
@media (max-width: 640px) {
  .mk-announce-bar {
    display: none;
  }
  .mk-nav-offset {
    top: 0;
  }
}

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

.mk-nav-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  max-width: 1360px;
  margin: 0 auto;
  padding: 14px clamp(20px, 5vw, 48px);
}

.mk-nav-pill {
  display: flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
  padding: 8px 10px 8px 14px;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  /*
   * Nearly opaque, and deliberately **no `backdrop-filter`** — nor `position`, `transform`, `filter`
   * or anything else that makes an element a containing block.
   *
   * The mega-menu panels are rendered inside this pill, straight after their triggers, so that Tab
   * reaches a panel's links next (see `MarketingNav`). They are absolutely positioned against the
   * header and span the page. A backdrop filter here makes the pill the containing block for every
   * absolutely positioned descendant, and the panel would collapse to the pill's width. The blur is
   * not lost: once the page scrolls, the header behind the pill carries it.
   */
  background: color-mix(in srgb, var(--bg-sunken) 94%, transparent);
}

/* The logo and the name, one lockup: the mark sits beside the word, centred on its line. */
.mk-wordmark {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-weight: 650;
  font-size: 20px;
  letter-spacing: -0.01em;
  color: var(--text-primary);
  white-space: nowrap;
  text-decoration: none;
  margin-right: var(--space-3);
}

.mk-nav-links {
  display: flex;
  align-items: center;
  gap: 2px;
  min-width: 0;
}

/*
 * A trigger and a plain link have to be the same object to the eye.
 *
 * One is a `<button>` that opens a panel and the other is an `<a>` that navigates, which is right
 * for the accessibility tree and irrelevant to a reader scanning a row of words. Anything that made
 * them look different — a button's default font, a link's underline — would say there are two kinds
 * of thing up there, and there are not.
 */
.mk-nav-trigger,
.mk-nav-links .mk-navlink {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 44px;
  padding: 0 18px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  font: inherit;
  font-size: 16px;
  color: var(--text-secondary);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}
.mk-nav-trigger:hover,
.mk-nav-links .mk-navlink:hover {
  color: var(--text-primary);
  background: var(--bg-hover);
}
.mk-nav-trigger.is-on {
  color: var(--text-primary);
  background: var(--bg-hover);
}
.mk-nav-trigger:focus-visible,
.mk-nav-links .mk-navlink:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.mk-nav-chev {
  display: inline-flex;
  transition: transform 160ms var(--ease-out);
}
.mk-nav-trigger.is-on .mk-nav-chev {
  transform: rotate(180deg);
}

/* The flat links outside the pill keep the plain form they have always had. */
.mk-navlink {
  font-size: var(--text-md);
  color: var(--text-secondary);
  text-decoration: none;
}
.mk-navlink:hover {
  color: var(--text-primary);
}

.mk-nav-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/*
 * Round, and only here.
 *
 * The page's calls to action keep `--radius-md`, because a band's primary button sits in a column of
 * cards that are all cornered the same way. In the nav the buttons sit beside a pill and a rounded
 * rectangle next to a lozenge reads as a mistake rather than as a choice.
 */
.mk-nav-actions .mk-cta {
  border-radius: 999px;
  height: 46px;
  padding-inline: 22px;
  font-size: 15px;
}

/*
 * The hamburger, hidden until the links no longer fit.
 *
 * `display: none` rather than absence: the button carries `aria-expanded` and `aria-controls`, and
 * rendering it conditionally in JS would make the menu state depend on viewport width at mount.
 */
.mk-nav-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: var(--size-control);
  height: var(--size-control);
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--bg-sunken);
  color: var(--text-primary);
}

.mk-nav-menu {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin: 6px clamp(20px, 5vw, 48px) 0;
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--bg-surface);
  max-height: calc(100dvh - 140px);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.mk-nav-menu-actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border);
}

/* A mega menu, flattened: the label becomes a heading over its own destinations. */
.mk-nav-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--color-border);
}
.mk-nav-group-heading {
  margin: 0;
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-muted);
}

@media (max-width: 1180px) {
  .mk-nav-links,
  .mk-nav-actions {
    display: none;
  }
  .mk-nav-toggle {
    display: inline-flex;
  }
  .mk-nav-pill {
    padding: 6px 20px;
  }
}
@media (min-width: 1181px) {
  /* Above the breakpoint the menu cannot be open in a way the user can reach, so it never shows. */
  .mk-nav-menu {
    display: none;
  }
}

/* ------------------------------------------------------------------------------- the mega menu */

/*
 * The open panel: a sheet of paper in front of the page.
 *
 * It is deliberately not a darker version of the page. Every reference site does the same thing for
 * the same reason — the menu is the one surface a visitor reads as a *document*, a list of what the
 * product contains, and paper is what a document looks like. Keeping it light in both themes also
 * removes a whole class of bug: the panel has one set of colours rather than two, so there is no
 * arrangement in which its ink and its ground come from different themes.
 */
.mk-menu {
  --mk-menu-ground: #faf7ee;
  --mk-menu-ink: #14171c;
  --mk-menu-dim: #5c626c;
  --mk-menu-rule: rgba(20, 23, 28, 0.13);
  --mk-menu-sunken: rgba(20, 23, 28, 0.055);

  position: absolute;
  top: calc(100% - 4px);
  left: 0;
  right: 0;
  padding: 0 clamp(20px, 5vw, 48px);
  display: flex;
  justify-content: center;
  /* The wrapper is only a positioner; the sheet inside it is what the pointer meets. */
  pointer-events: none;
}

.mk-menu-inner {
  pointer-events: auto;
  display: grid;
  grid-template-columns: 300px minmax(0, 1fr);
  width: 100%;
  max-width: 1320px;
  /* Five pixels of the sheet showing all the way round the illustrated card, which is what makes
     the card read as mounted on the page rather than as the page's own left edge. */
  padding: 5px;
  border-radius: 26px;
  background: var(--mk-menu-ground);
  box-shadow:
    0 2px 6px rgba(0, 0, 0, 0.18),
    0 34px 70px -28px rgba(0, 0, 0, 0.55);
  animation: mk-menu-in 150ms var(--ease-out) both;
}

@keyframes mk-menu-in {
  from {
    opacity: 0;
    transform: translateY(-8px);
  }
}

/* -------------------------------------------------------- the illustrated card, and its scenes */

.mk-menu-feature {
  position: relative;
  isolation: isolate;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--space-6);
  /*
   * A floor, not a height. It was 430 — taller than four menu entries need, so a short menu drew a
   * band of empty paper across the bottom right and the sheet looked like it had failed to load its
   * other half. The grid stretches this to match the columns whenever they are taller.
   */
  min-height: 318px;
  padding: 26px 24px 24px;
  border-radius: 21px;
  overflow: hidden;
  color: var(--mk-scene-ink);
}

.mk-menu-scene {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
}

/*
 * The scrim under the words.
 *
 * A headline over a landscape is legible where the landscape happens to be dark and unreadable
 * where it is not, and which of those it is changes with the scene. The gradient makes the answer
 * the same for all four: the lower half is dark, so the eyebrow, the headline and the button sit on
 * a ground that was chosen rather than inherited from a drawing.
 */
.mk-menu-feature::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  /*
   * Dark at both ends, clear through the middle.
   *
   * The bottom is where the eyebrow, the headline and the button sit. The top is where the title
   * sits, and without the first stop a mountain peak crossing behind it decides the contrast of
   * half a word — which changes with the scene and with the length of the title. Two stops means
   * the answer is the same on all four cards, and the landscape still shows between them.
   */
  background: linear-gradient(
    180deg,
    var(--mk-scene-scrim) 0%,
    transparent 30%,
    transparent 46%,
    var(--mk-scene-scrim) 86%
  );
}

.mk-menu-feature-dusk {
  --mk-scene-sky: linear-gradient(180deg, #0a1636 0%, #16214d 48%, #2b2557 100%);
  --mk-scene-far: #4453bb;
  --mk-scene-near: #8b4ac0;
  --mk-scene-spark: rgba(255, 255, 255, 0.75);
  --mk-scene-scrim: rgba(7, 11, 30, 0.84);
  --mk-scene-ink: #f2f4ff;
  background: var(--mk-scene-sky);
}

.mk-menu-feature-city {
  --mk-scene-sky: linear-gradient(180deg, #2c3f7d 0%, #6f5b97 54%, #e08a63 100%);
  --mk-scene-far: #1d2650;
  --mk-scene-near: #121935;
  --mk-scene-spark: rgba(255, 255, 255, 0.8);
  --mk-scene-scrim: rgba(10, 14, 34, 0.84);
  --mk-scene-ink: #f4f2ff;
  background: var(--mk-scene-sky);
}

.mk-menu-feature-canyon {
  --mk-scene-sky: linear-gradient(180deg, #4a1f52 0%, #a83a52 52%, #e8764a 100%);
  --mk-scene-far: #7d2340;
  --mk-scene-near: #43132c;
  --mk-scene-spark: rgba(255, 255, 255, 0.7);
  --mk-scene-scrim: rgba(26, 8, 22, 0.84);
  --mk-scene-ink: #fff1ea;
  background: var(--mk-scene-sky);
}

.mk-menu-feature-ridge {
  --mk-scene-sky: linear-gradient(180deg, #0f3a46 0%, #1d6b62 50%, #d98f4a 100%);
  --mk-scene-far: #17544f;
  --mk-scene-near: #0c2b33;
  --mk-scene-spark: rgba(255, 236, 200, 0.85);
  --mk-scene-scrim: rgba(5, 21, 26, 0.84);
  --mk-scene-ink: #eefaf4;
  background: var(--mk-scene-sky);
}

.mk-menu-title {
  position: relative;
  margin: 0;
  font-size: clamp(26px, 2.4vw, 34px);
  font-weight: 600;
  line-height: 1.12;
  letter-spacing: -0.028em;
  color: var(--mk-scene-ink);
}

.mk-menu-feature-body {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.mk-menu-eyebrow {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  opacity: 0.72;
}

.mk-menu-headline {
  margin: 0 0 6px;
  font-size: var(--text-lg);
  font-weight: 600;
  line-height: 1.34;
  letter-spacing: -0.012em;
}

.mk-menu-feature-cta {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 44px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: var(--text-md);
  font-weight: 550;
  text-decoration: none;
}
.mk-menu-feature-cta:hover {
  background: var(--accent-hover);
}

/* ------------------------------------------------------------------- the columns and the rail */

.mk-menu-body {
  display: flex;
  gap: clamp(20px, 2.6vw, 40px);
  min-width: 0;
  padding: 24px 28px 28px;
}

.mk-menu-rail {
  flex: 0 0 212px;
  min-width: 0;
}

.mk-menu-cols {
  flex: 1 1 auto;
  display: grid;
  gap: 4px clamp(20px, 2.6vw, 44px);
  align-content: start;
  min-width: 0;
}
.mk-menu-cols[data-cols="2"] {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.mk-menu-cols[data-cols="3"] {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.mk-menu-col {
  min-width: 0;
}

.mk-menu-heading {
  margin: 0 0 12px;
  padding-bottom: 11px;
  border-bottom: 1px solid var(--mk-menu-rule);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--mk-menu-dim);
}

.mk-menu-lead {
  margin: 0 0 16px;
  font-size: var(--text-xl);
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.02em;
  color: var(--mk-menu-ink);
}

/*
 * `auto-fill` rather than a column count.
 *
 * The same column markup has to hold six product entries in a 700px area and four plan features in
 * a 230px one. A declared count would be right for one of them; a minimum track width is right for
 * both, and it is the rule that produced the reference's own two-up product lists without anybody
 * having to say "two".
 */
.mk-menu-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 2px clamp(16px, 2vw, 34px);
}

.mk-menu-li {
  min-width: 0;
}

.mk-menu-item {
  display: block;
  padding: 8px 0;
  text-decoration: none;
}
.mk-menu-item-label {
  display: block;
  font-size: var(--text-md);
  font-weight: 500;
  color: var(--mk-menu-ink);
}
.mk-menu-item-note {
  display: block;
  margin-top: 2px;
  font-size: var(--text-sm);
  line-height: 1.45;
  color: var(--mk-menu-dim);
}
.mk-menu-item:hover .mk-menu-item-label {
  text-decoration: none;
}
.mk-menu-item:focus-visible {
  outline: 2px solid var(--mk-menu-ink);
  outline-offset: 3px;
  border-radius: 4px;
}

/* A line of what a plan includes. Not a link, and styled so nobody tries to press it. */
.mk-menu-row {
  display: block;
  padding: 12px 0;
  border-bottom: 1px solid var(--mk-menu-rule);
  font-size: var(--text-md);
  color: var(--mk-menu-ink);
}

.mk-menu-action {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 42px;
  margin-top: 18px;
  border-radius: 10px;
  background: var(--mk-menu-sunken);
  color: var(--mk-menu-ink);
  font-size: var(--text-md);
  font-weight: 550;
  text-decoration: none;
}
.mk-menu-action:hover {
  background: var(--mk-menu-rule);
}

.mk-menu-rail-item {
  display: block;
  width: 100%;
  padding: 9px 12px;
  border: 0;
  border-radius: 9px;
  background: none;
  font: inherit;
  font-size: var(--text-md);
  text-align: left;
  color: var(--mk-menu-ink);
  cursor: pointer;
}
.mk-menu-rail-item:hover {
  background: var(--mk-menu-sunken);
}
.mk-menu-rail-item.is-on {
  background: var(--mk-menu-sunken);
  font-weight: 550;
}
.mk-menu-rail .mk-menu-list {
  grid-template-columns: minmax(0, 1fr);
}

@media (max-width: 1180px) {
  /* The hamburger carries every destination at this width; the panel would open off the screen. */
  .mk-menu {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .mk-menu-inner {
    animation: none;
  }
  .mk-nav-chev {
    transition: none;
  }
}

/* ------------------------------------------------------------------------------------- ctas */

.mk-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: var(--size-control);
  padding: 0 var(--space-4);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  font-size: var(--text-md);
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
}
.mk-cta-lg {
  height: 46px;
  padding: 0 var(--space-5);
  font-size: var(--text-lg);
}

/*
 * The filled one. At most one per band, per the component's own rule: two equally weighted buttons
 * make the reader choose, which is the choice this page exists to avoid asking.
 */
/*
 * The ink here is `--on-accent` and must not be `--accent-solid`.
 *
 * Those two read as interchangeable from the token names and are not. On this palette `--accent` and
 * `--accent-solid` hold the *same* value in both themes (`#FAFAFA` dark, `#09090B` light), so a
 * button painted `--accent` with `--accent-solid` ink is white on white — which is exactly how this
 * rule first shipped, rendering the page's one primary call to action as a blank box. `--on-accent`
 * is the token that exists to be legible on the accent, and it inverts with the theme: near-black on
 * the dark theme's near-white accent, white on the light theme's near-black one.
 */
.mk-cta-primary {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
}
.mk-cta-primary:hover {
  background: var(--accent-hover);
}

.mk-cta-secondary {
  background: var(--bg-sunken);
  color: var(--text-primary);
  border-color: var(--color-border-strong);
}
.mk-cta-secondary:hover {
  border-color: var(--border-strong);
  background: var(--bg-hover);
}

.mk-cta-ghost {
  background: transparent;
  color: var(--text-secondary);
  padding-left: var(--space-2);
  padding-right: var(--space-2);
}
.mk-cta-ghost:hover {
  color: var(--text-primary);
}

/* On the accent band, where the ordinary primary would have too little contrast with the ground. */
.mk-cta-onAccent {
  background: var(--text-primary);
  color: var(--bg-page);
  border-color: var(--text-primary);
}
.mk-cta-onAccent:hover {
  background: var(--accent-hover);
}

/*
 * Every anchor on this surface restates its hover ink, and none of them underlines.
 *
 * `styles.css` carries `a:hover { color: var(--hover-accent-text); text-decoration: underline }` for
 * the console. That selector is (0,1,1); a tone class is (0,1,0), so on hover the global rule won.
 * Measured on the dark theme: the primary call to action went from near-black on near-white to
 * near-white ink on a white fill — the label vanished and an underline appeared under nothing — and
 * the tour's call to action did the same thing on the light theme. A `:hover` rule per tone is
 * (0,2,0), which outranks the global one without `!important`, and states the ink the tone already
 * had, so hovering changes the ground and never the legibility.
 */
.mk-cta:hover {
  text-decoration: none;
}
.mk-cta-primary:hover {
  color: var(--on-accent);
}
.mk-cta-secondary:hover {
  color: var(--text-primary);
}
.mk-cta-onAccent:hover {
  color: var(--bg-page);
}
.mk-menu-feature-cta:hover {
  color: var(--on-accent);
  text-decoration: none;
}
.mk-menu-action:hover {
  color: var(--mk-menu-ink);
  text-decoration: none;
}
/* The label underlines (its own rule, below); the whole row, note included, does not. */
.mk-menu-item:hover {
  text-decoration: none;
}
.mk-nav-links .mk-navlink:hover {
  text-decoration: none;
}

/* ------------------------------------------------------------------------------------- hero */

.mk-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

.mk-headline {
  margin: 0 0 var(--space-5);
  font-size: clamp(34px, 5.6vw, 62px);
  line-height: 1.06;
  letter-spacing: -0.035em;
  font-weight: 600;
  text-wrap: balance;
  color: var(--text-primary);
}

.mk-hero .mk-lead {
  margin-bottom: var(--space-6);
  text-align: center;
}

.mk-hero-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-bottom: var(--space-7, 48px);
}

/*
 * Below 640px the caller's own line breaks in the headline are suppressed and `text-wrap: balance`
 * takes over, which is what the component's doc comment promises. Without this the two-line balance
 * chosen for a desktop width breaks a phone headline in the wrong place.
 */
@media (max-width: 640px) {
  .mk-headline br {
    display: none;
  }
}

/* ------------------------------------------------------------- the console stack (hero figure) */

.mk-stage {
  position: relative;
  width: 100%;
  max-width: 900px;
  margin: 0 auto;
  /* The perspective origin every panel shares, which is what makes them read as one scene. */
  perspective: 1400px;
  perspective-origin: 50% 0%;
}

/* A soft ground under the stack so the panels sit on something rather than float. */
.mk-floor {
  position: absolute;
  left: 50%;
  bottom: -6%;
  width: 78%;
  height: 120px;
  transform: translateX(-50%);
  background: radial-gradient(ellipse at center, var(--accent-soft), transparent 70%);
  opacity: 0.7;
  pointer-events: none;
}

.mk-stack {
  position: relative;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-4);
  transform-style: preserve-3d;
  transform: rotateX(14deg);
}

.mk-panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-height: 150px;
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--bg-surface);
  /* A float above the page, not separation between adjacent regions — the one place this surface
     uses elevation, and it is the overlay token rather than a new value. */
  box-shadow: var(--elev-overlay);
}
/* Rotation stays under 20°, past which the labels stop being legible. */
.mk-panel-1 {
  transform: rotateY(9deg) translateZ(-20px);
}
.mk-panel-2 {
  transform: translateZ(10px);
}
.mk-panel-3 {
  transform: rotateY(-9deg) translateZ(-20px);
}

.mk-p-label {
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* The bar figure. Heights come from the component as inline styles; this is only the frame. */
.mk-p-bars {
  display: flex;
  align-items: flex-end;
  gap: 4px;
  height: 72px;
}
.mk-p-bars > i {
  flex: 1;
  border-radius: 2px 2px 0 0;
  background: var(--accent);
  opacity: 0.55;
}

.mk-p-rows,
.mk-pane-rows {
  display: flex;
  flex-direction: column;
  gap: 7px;
}
.mk-p-row,
.mk-pane-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

.mk-p-dot {
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--text-muted);
}
.mk-p-dot-ok {
  background: var(--ok);
}
.mk-p-dot-warn {
  background: var(--warn);
}
.mk-p-dot-accent {
  background: var(--info);
}

.mk-p-spark {
  width: 100%;
  height: 56px;
  stroke: var(--info);
  fill: var(--info);
}

/* The single upright panel, for a band that needs a figure but not the hero's drama. */
.mk-pane {
  display: flex;
  justify-content: center;
}
.mk-pane-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  width: 100%;
  max-width: 320px;
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--bg-surface);
  transform: rotateY(-5deg);
}

@media (max-width: 720px) {
  /* The tilt is illegible at phone width, and three panels in a row are 90px each. */
  .mk-stack {
    grid-template-columns: 1fr;
    transform: none;
  }
  .mk-panel-1,
  .mk-panel-2,
  .mk-panel-3,
  .mk-pane-card {
    transform: none;
  }
}

/* ------------------------------------------------------------------------------------ trust */

.mk-trust {
  text-align: center;
}
.mk-trust-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  border-top: 1px solid var(--color-border);
}
.mk-trust-cell {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: var(--space-4) var(--space-3);
  border-bottom: 1px solid var(--color-border);
}
/* Hairline dividers between columns, not around the row. */
.mk-trust-cell + .mk-trust-cell {
  border-left: 1px solid var(--color-border);
}
.mk-trust-cell b {
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--text-primary);
}
.mk-trust-cell span {
  font-size: var(--text-sm);
  color: var(--text-muted);
}

/* ------------------------------------------------------------------------------------- rail */

.mk-rail-list {
  border-top: 1px solid var(--color-border);
}
.mk-rail-item {
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--color-border);
}
.mk-rail-title {
  margin: 0 0 6px;
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--text-primary);
}
.mk-rail-body {
  margin: 0;
  max-width: 68ch;
  line-height: 1.6;
  color: var(--text-secondary);
}

/* With an aside the rail runs in two columns; without one it runs full width. */
.mk-rail--split {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  gap: clamp(24px, 4vw, 56px);
  align-items: start;
}
.mk-rail-aside {
  position: sticky;
  top: 96px;
}
@media (max-width: 860px) {
  .mk-rail--split {
    grid-template-columns: 1fr;
  }
  .mk-rail-aside {
    position: static;
  }
}

/* --------------------------------------------------------------------------------- features */

.mk-features-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--space-4);
}
.mk-feature {
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--bg-page);
}
/* On the page ground the card would be invisible, so it takes the other one. */
.mk-band-page .mk-feature {
  background: var(--bg-surface);
}
.mk-feature-title {
  margin: 0 0 6px;
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--text-primary);
}
.mk-feature-body {
  margin: 0;
  line-height: 1.6;
  color: var(--text-secondary);
}

/* ------------------------------------------------------------------------------- highlights */

.mk-hl-heading {
  margin-bottom: var(--space-3);
}
.mk-hl-lead {
  margin: 0 0 var(--space-6);
  max-width: 62ch;
  line-height: 1.6;
  color: var(--text-secondary);
}

.mk-hl-list {
  margin: 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--color-border);
}
.mk-hl-item {
  display: grid;
  grid-template-columns: auto minmax(0, 1.5fr) minmax(0, 1fr);
  gap: var(--space-4);
  align-items: start;
  padding: var(--space-5) 0;
  border-bottom: 1px solid var(--color-border);
}
/* The numbering is real ordering, not decoration, so it is set in the measurement voice. */
.mk-hl-index {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
  padding-top: 3px;
}
.mk-hl-title {
  margin: 0 0 6px;
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--text-primary);
}
.mk-hl-body {
  margin: 0;
  line-height: 1.6;
  color: var(--text-secondary);
}
/*
 * The contrast column, set back deliberately. It names a category and never a vendor, and it must
 * read as context rather than as an accusation — so it is quieter than the claim it sits beside.
 */
.mk-hl-contrast {
  margin: 0;
  padding-left: var(--space-4);
  border-left: 1px solid var(--color-border);
  font-size: var(--text-sm);
  line-height: 1.55;
  color: var(--text-muted);
}
.mk-hl-contrast-label {
  display: block;
  margin-bottom: 4px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-secondary);
}
@media (max-width: 860px) {
  .mk-hl-item {
    grid-template-columns: auto minmax(0, 1fr);
  }
  .mk-hl-contrast {
    grid-column: 2;
    padding-left: 0;
    padding-top: var(--space-3);
    border-left: 0;
    border-top: 1px solid var(--color-border);
  }
}

/* ---------------------------------------------------------------------------------- compare */

/*
 * Its own scroll container. A table that widens the document body is the one layout bug that
 * follows you onto every page of the site, which is why the component wraps it and why this rule
 * is the reason that wrapper exists.
 */
.mk-cmp-scroll {
  overflow-x: auto;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--bg-surface);
}
.mk-band-surface .mk-cmp-scroll {
  background: var(--bg-page);
}

.mk-cmp-table {
  width: 100%;
  min-width: 640px;
  border-collapse: collapse;
  font-size: var(--text-md);
}
.mk-cmp-table th,
.mk-cmp-table td {
  padding: var(--space-3) var(--space-4);
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--color-border);
  line-height: 1.55;
}
.mk-cmp-table tbody tr:last-child th,
.mk-cmp-table tbody tr:last-child td {
  border-bottom: 0;
}

.mk-cmp-aspect {
  width: 26%;
  font-weight: 600;
  color: var(--text-primary);
}

/*
 * Our column is promoted with a ground and a heading weight so the eye lands there — without the
 * other column being drawn as a failure, which is the whole argument for prose cells over ticks.
 */
.mk-cmp-ours {
  background: var(--accent-soft);
  color: var(--text-primary);
  font-weight: 500;
}
.mk-cmp-table thead .mk-cmp-ours {
  font-weight: 600;
}
.mk-cmp-theirs {
  color: var(--text-secondary);
}

/* The header cell over the aspect column has a label for screen readers and none on screen. */
.mk-cmp-vh {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.mk-cmp-note {
  margin: var(--space-3) 0 0;
  font-size: var(--text-sm);
  color: var(--text-muted);
}

/* ------------------------------------------------------------------------------------ close */

.mk-close {
  text-align: center;
}
.mk-close-heading {
  margin: 0 0 var(--space-3);
  font-size: clamp(26px, 3.4vw, 40px);
  line-height: 1.15;
  letter-spacing: -0.02em;
  font-weight: 600;
  text-wrap: balance;
  color: var(--text-primary);
}
.mk-close-lead {
  margin: 0 auto var(--space-5);
  max-width: 58ch;
  line-height: 1.6;
  color: var(--text-secondary);
}
.mk-close-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}

/* ------------------------------------------------------------------------------ narrow measure */

/* For a band whose content is a form rather than a layout — the enquiry section. */
.mk-measure-narrow {
  max-width: 680px;
  margin: 0 auto;
}

/* ------------------------------------------------------------------------------ reduced motion */

/*
 * The tilt is a static transform rather than an animation, so there is nothing here to stop. It is
 * removed anyway: a 3D transform is exactly the kind of depth cue the preference is asked for, and
 * the panels read perfectly well flat.
 */
@media (prefers-reduced-motion: reduce) {
  .mk-stack,
  .mk-panel-1,
  .mk-panel-2,
  .mk-panel-3,
  .mk-pane-card {
    transform: none;
  }
  .mk-band-glow::before,
  .mk-band-glow::after {
    display: none;
  }
}

/* ==========================================================================
   The craft pass: what makes this read as a product rather than a page
   ==========================================================================

   Two of the changes below are repairs, not taste, and they are the reason the surface looked flat
   enough to be called "very normal".

   **The figure lost its colour when the chrome did.** `--accent` is `#FAFAFA` in the dark theme —
   v4 took the hue out of the chrome deliberately, so links and buttons are weight and fill rather
   than a brand blue. The hero's bar figure was written before that and still asks for `--accent` at
   55% opacity, which now resolves to grey-on-near-black: a chart that reads as one that failed to
   load. **And the floor glow under the stack asks for `--accent-soft`, which is `#18181B`** — a
   near-black radial gradient on a near-black page, i.e. nothing at all, which is why the panels
   float against a void instead of sitting on a surface.

   The fix is not to put the hue back into `--accent`. That token is greyscale on purpose and every
   console page depends on it. Colour on *this* surface comes from `--info`, which is already the
   one hue the design lets through the chrome rule, for focus rings, on the stated grounds that
   accessibility outranks the no-hue rule. A figure is data, and data is allowed colour — that is
   the same distinction `tokens.css` draws between `--ch-*` and the status triple.

   Everything else here is the detail layer that separates a competent dark page from an expensive
   one, and it is deliberately four things rather than fourteen:

     1. A **top edge highlight** on every raised panel. One hairline of white at 7%, along the top
        border only. It is what makes a dark panel read as a lit surface rather than a hole, and it
        is the single most copied detail in the reference set for a reason: it is how real material
        behaves under a light above it.
     2. **Ambient light behind the figure**, in `--info`, replacing the invisible floor.
     3. **The display face on the headline.** `Archivo Expanded` is already self-hosted here and is
        what the product's own workstation surfaces set their titles in, so the front door speaks the
        same typographic language as the console behind it instead of a generic geometric sans. Used
        once, on one element, which is the whole of the restraint.
     4. **Mono eyebrows.** Section labels are set in `JetBrains Mono` — the measurement voice this
        product already uses for every number, identifier and timestamp. On an infrastructure
        product that is vernacular rather than decoration.

   Scoped to `.mk-*` throughout. Nothing in this file may reach a console screen.
   ========================================================================== */

.mk-band,
.mk-hero {
  /* Declared here rather than on :root so the console cannot inherit a marketing colour. */
  --mk-signal: var(--info);
  --mk-edge: rgba(255, 255, 255, 0.07);
}

/* In the light theme a white highlight on a white panel is nothing, and the lit edge has to come
   from the other direction: a faint shadow line under the top border instead. */
:root[data-theme="light"] .mk-band,
:root[data-theme="light"] .mk-hero {
  --mk-edge: rgba(10, 12, 16, 0.06);
}

/* ----------------------------------------------------------------- the five unstyled wrappers */

/*
 * These render on the live page and had no rule at all — the scan that should have caught it was
 * reading the wrong file. They are containers, so the symptom was not a visible break but the
 * absence of the constraint each one exists to impose: a grid cell that cannot shrink below its
 * longest word pushes the column beside it off the measure.
 */
.mk-features,
.mk-hl,
.mk-cmp {
  display: flow-root;
}
.mk-rail-main,
.mk-hl-main {
  min-width: 0;
}

/* ------------------------------------------------------------------------------ the headline */

.mk-headline {
  font-family: var(--font-display, "Archivo Expanded", Geist, system-ui, sans-serif);
  /* Expanded at display size wants to be tighter than a normal-width face, not looser. */
  letter-spacing: -0.045em;
  font-weight: 600;
}

/* The eyebrow is a label on a measurement, and it is set like one. */
.mk-eyebrow {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.16em;
}

/* --------------------------------------------------------------------------- the lit surface */

/*
 * One highlight along the top border of every raised panel.
 *
 * `inset 0 1px 0` rather than a border, so it does not change the box's metrics and cannot fight
 * the 1px hairline that is already the product's only separation device. Added to the existing
 * overlay shadow rather than replacing it: the shadow says "this floats", the highlight says "this
 * is lit", and a raised panel needs both to read as material.
 */
.mk-panel,
.mk-pane-card {
  box-shadow: var(--elev-overlay), inset 0 1px 0 var(--mk-edge);
}

/* ------------------------------------------------------------------------------- the figures */

/*
 * The bars are data, so they carry the one hue this surface allows, and they carry it as a ramp
 * rather than a flat fill — a bar that is brighter at its cap reads as a measurement and a bar of
 * uniform grey reads as a placeholder. No value is implied by the ramp; the heights are the
 * component's and mean nothing either, which is the standing rule for this figure.
 */
.mk-p-bars > i {
  background: linear-gradient(
    to top,
    color-mix(in srgb, var(--mk-signal) 28%, transparent),
    color-mix(in srgb, var(--mk-signal) 78%, transparent)
  );
  opacity: 1;
  border-radius: 2px 2px 0 0;
}

/*
 * The light the stack sits in, replacing a gradient that resolved to near-black on near-black.
 *
 * Wider and lower than the panels so the falloff is off-screen at the sides, which is what keeps it
 * reading as light rather than as a drawn ellipse. Low alpha on purpose: at anything stronger this
 * becomes the glowing blob every AI-generated dark landing page has, which is the thing worth
 * avoiding even when the technique is right.
 */
.mk-floor {
  width: 96%;
  height: 190px;
  bottom: -12%;
  background: radial-gradient(
    ellipse at center,
    color-mix(in srgb, var(--mk-signal) 16%, transparent),
    transparent 68%
  );
  opacity: 1;
  filter: blur(6px);
}

/* The spark and the status dots already read as data. The spark gets the weight it was missing:
   a 1px line across 900px of stage is a hairline, not a series. */
.mk-p-spark {
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* ---------------------------------------------------------------------------- the trust band */

/*
 * Five equal cells of small text is the genre's most forgettable band. The content is honest —
 * sectors rather than invented logos, which is stated where the data is defined — so the job here
 * is to make an honest claim look deliberate rather than unfinished.
 *
 * The label leads in the product's own measurement voice and the sector sits under it as prose.
 * Separated by the hairline that is already the only divider this design uses.
 */
.mk-trust-cell b {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  font-weight: 500;
  letter-spacing: 0.02em;
}
.mk-trust-cell span {
  color: var(--text-muted);
}

/*
 * The float shadow, re-weighted for the light theme.
 *
 * `--elev-overlay` is one value for both themes — `0 8px 32px rgba(0,0,0,0.6)` — and it was tuned
 * for a modal floating over a near-black page. Under a white panel on a white page that same 60%
 * black reads as dirt rather than depth: the panel looks smudged instead of lifted.
 *
 * Light needs less spread and far less opacity, because a light ground carries a shadow much further
 * than a dark one does. This overrides only on this surface and only in light, so the token the rest
 * of the product depends on is untouched.
 */
:root[data-theme="light"] .mk-panel,
:root[data-theme="light"] .mk-pane-card {
  box-shadow:
    0 1px 2px rgba(10, 12, 16, 0.06),
    0 12px 28px rgba(10, 12, 16, 0.10),
    inset 0 1px 0 var(--mk-edge);
}

/* ==========================================================================
   The security band
   ==========================================================================

   The card is doing one unusual thing and everything else here supports it: the **mechanism leads**,
   above the claim, rather than sitting under it as a footnote.

   That inversion is the whole design. Read top-down, a card says "a CHECK constraint — an automatic
   high-risk change is unstorable", which is an argument. Read the other way round it is the genre's
   standard reassurance with a citation bolted on, and the citation is what nobody reads. Putting the
   enforcement first also makes a weak row impossible to hide: a card whose mechanism is "our policy"
   looks wrong at a glance, in a way that the same words at the bottom of the card never would.

   The file path is set in the measurement voice and sits at the foot behind a hairline, because it
   is evidence rather than prose. It is deliberately not a link — this repository is not public, and
   a path somebody can grep for is honest where a dead href is not.
   ========================================================================== */

.mk-sec {
  display: flow-root;
}

.mk-sec-heading {
  margin-bottom: var(--space-3);
}

.mk-sec-lead {
  max-width: 68ch;
  margin-bottom: var(--space-6);
}

/*
 * Auto-fit rather than a fixed column count.
 *
 * The rows are eight and will not stay eight. A hardcoded three-column grid leaves a single orphan
 * card on the last row at some counts and a two-wide gap at others, and whoever adds the ninth
 * control should not have to think about the layout to do it.
 */
.mk-sec-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: var(--space-4);
}

.mk-sec-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-5);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--bg-surface);
  /* The same lit top edge the hero panels carry, so the two surfaces read as one material. No
     float shadow: these sit in the page rather than above it, and the product's own rule is that
     separation between adjacent regions is the hairline and never a shadow. */
  box-shadow: inset 0 1px 0 var(--mk-edge);
}

/* ------------------------------------------------------------------- the mechanism, leading */

.mk-sec-mech {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0;
}

.mk-sec-mech-label {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/*
 * The mechanism itself carries the one hue this surface allows.
 *
 * It is the only coloured text on the band, and it is coloured because it is the load-bearing word
 * in the sentence — the thing the reader is being asked to check. Everything else stays in the ink
 * ramp, which is what stops this reading as a page of highlighted keywords.
 */
.mk-sec-mech-name {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--mk-signal);
  /* Wraps inside its own box rather than dropping whole to the next line. A long mechanism that
     wrapped as one flex item pushed its card's claim a line lower than the cards beside it, and a
     row of cards whose headings do not start at the same height reads as four different components. */
  min-width: 0;
  overflow-wrap: anywhere;
}

.mk-sec-claim {
  margin: var(--space-1) 0 0;
  font-size: var(--text-lg);
  line-height: 1.3;
  letter-spacing: -0.01em;
  font-weight: 600;
  color: var(--text-primary);
  text-wrap: balance;
}

.mk-sec-body {
  margin: 0;
  font-size: var(--text-sm);
  line-height: 1.6;
  color: var(--text-secondary);
}

/* ------------------------------------------------------------------------------- the evidence */

.mk-sec-where {
  margin: auto 0 0;
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 1.5;
  color: var(--text-muted);
  /* A path is one token and must not be hyphenated mid-identifier, but it also must not widen the
     card on a narrow column. Breaking on the existing separators is the one place it can give. */
  overflow-wrap: anywhere;
}

/* `margin: auto 0 0` above pins the evidence to the foot of every card whatever the body length, so
   the hairlines line up across a row. Without it the rule sits wherever the prose ends and the row
   reads as four cards of different kinds. */

/* ==========================================================================
   The marketing page scrolls, and the app frame does not
   ==========================================================================

   `styles.css` sets `body { height: 100dvh; overflow: hidden }` and gives a good reason: the console
   is an application frame that fits the window, its content region scrolls instead, and two
   scrollbars mean a wheel event near the edge of a pane chains to the document and drifts the whole
   app by a few pixels. The comment ends "signed-out screens are short and centred, so nothing is
   lost by them not scrolling."

   That was true when it was written. This page is six thousand pixels tall, and it wrapped itself in
   `.mk-page` — a class that **had no rule anywhere** — so the clamp applied to it in full: the
   document was scrollable, the body clipped it, and a wheel over the page did nothing. Everything
   below the hero was unreachable in a browser while being perfectly present in the markup.

   Worth saying why no test caught it. `marketing.test.ts` checks that every `mk-` class has a rule,
   and `.mk-page` is rendered by `apps/portal/app/welcome/page.tsx` rather than by `marketing.tsx`,
   which is the only file that scan reads. Trap 28: a source scan is exactly as wide as its source
   list. The scan now reads the page too.

   ## Why `:has()` on the body rather than a scroll container here

   Making `.mk-page` its own scrolling box would also work and is tempting because it needs no modern
   selector. It quietly changes what "scrolled" means, though: the fixed nav, `scroll-margin` on the
   anchor targets, the browser's own restore-scroll-on-back, and every `#id` link are all written
   against a scrolling *document*. Keeping the document as the scroller means none of that has to be
   re-implemented, and the opt-out stays one rule instead of a set of compensations.

   The fallback below is exact rather than hopeful: where `:has()` is unsupported the page becomes
   its own scroll container, which is worse in the ways listed above and still lets somebody read the
   page, which is the whole bar.
   ========================================================================== */

body:has(.mk-page) {
  height: auto;
  min-height: 100dvh;
  overflow: visible;
}

@supports not selector(:has(*)) {
  .mk-page {
    height: 100dvh;
    overflow-y: auto;
  }
}

/* The page itself claims no height: it is as tall as its bands, which is what makes the document
   the scroller. Declared so the class is not a bare hook with nothing behind it. */
.mk-page {
  display: flow-root;
  min-height: 100dvh;
}

/* ==========================================================================
   The hero, taken toward the reference mockup
   ==========================================================================

   The brief was a mockup: a deep space ground with an aurora behind the type, a two-tone gradient on
   the second half of the headline, glass cards, and a row of short assurances under the buttons.
   Most of that is built here. Two things from it are deliberately **not**, and both are content
   rather than craft:

     - **The numbers.** The mockup's cards carry "12 clusters", "50 Total", "42 Succeeded", "99.9%
       Uptime". Not one of those can be true on a page served to a visitor with no account, and this
       repository bans fabricated data outside seeds and fixtures. `ConsoleStack`'s own comment names
       the exact failure — every reference page in the set invents a figure in its hero — so the
       cards keep labels and states and carry no quantity.
     - **The Red Hat mark** at the centre of the figure. Using another company's trademark as the
       hero of your own product page is a legal question, not a design one, and the same reasoning
       already keeps invented customer logos off the trust row.

   The gradient is spent on exactly one thing: the second half of the headline. A page where the
   eyebrow, the buttons, the card borders and the figure all carry the same two hues is the look the
   mockup is imitating, and it reads as a template. One gradient, one place.
   ========================================================================== */

/*
 * The aurora is the two orbs the `glow` flag already draws, re-coloured — not a third layer.
 *
 * The first draft added a `.mk-band-hero` class with its own `::after` carrying three radial
 * gradients. `MarketingBand` takes no `className`, and widening its API so a call site could paint
 * a band background would undo the thing a band component exists to do. The `glow` flag already
 * means "this is the hero band"; it simply had one invisible half, fixed above.
 */

.mk-band,
.mk-hero {
  /* The second hue, used only in the aurora and in the headline gradient. Violet rather than a
     second blue: two blues read as a rendering error, and this is the product's own chart violet
     rather than a colour invented for the landing page. */
  --mk-violet: #B388FF;
}

/* ------------------------------------------------------------------------------ the eyebrow */

.mk-hero-eyebrow {
  margin: 0 0 var(--space-4);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--mk-signal);
}

/* ------------------------------------------------------------------- the headline's second half */

/*
 * `background-clip: text` with a transparent fill, which is the only way to put a gradient on type.
 *
 * The fallback matters and is one line: `color` is set first, so a browser that does not support the
 * clip renders solid ink instead of an invisible headline. That failure mode is not theoretical —
 * it is what every uncommented version of this snippet does.
 */
.mk-grad {
  color: var(--text-primary);
  background-image: linear-gradient(100deg, var(--mk-signal), var(--mk-violet));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

@supports not (((-webkit-background-clip: text) or (background-clip: text)) or (-webkit-background-clip: text)) {
  .mk-grad {
    background-image: none;
    -webkit-text-fill-color: currentColor;
  }
}

/* ---------------------------------------------------------------------------- the assurances */

.mk-assure {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-5);
  margin: 0 0 var(--space-6);
  padding: 0;
  list-style: none;
}

.mk-assure-item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

/* The marker, drawn rather than written — see the component's note about why there is no icon slot. */
.mk-assure-item::before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: var(--radius-full);
  background: var(--mk-signal);
  flex: none;
}

/* ------------------------------------------------------------------------------ glass panels */

/*
 * The mockup's cards are glass: a translucent fill over the aurora, a lit top edge, and a border
 * that is brighter where the light hits. The fill is `color-mix` against the surface token rather
 * than a fixed rgba, so the panel stays the right colour when the theme changes.
 */
.mk-panel {
  background: color-mix(in srgb, var(--bg-surface) 72%, transparent);
  backdrop-filter: blur(12px);
  border-color: color-mix(in srgb, var(--mk-signal) 16%, var(--color-border));
}

@supports not (backdrop-filter: blur(1px)) {
  /* Without the blur a translucent panel just shows the aurora through it as noise behind text, so
     the fill goes solid. The look degrades; the legibility does not. */
  .mk-panel {
    background: var(--bg-surface);
  }
}

/* ==========================================================================
   The control-plane figure and the dashboard band
   ==========================================================================

   Everything below is prefixed `mk-fig-` or `mk-stat-`, and the prefix is load-bearing rather than
   naming convention: the looping-animation ban that applies to the rest of this surface is relaxed
   for `mk-fig-` alone, and `marketing.test.ts` checks the boundary in both directions. A loop that
   escapes this prefix is a test failure.

   The exemption is deliberate and narrow. The product's rule is that nothing loops except the three
   things that mean "still in flight", and it is a good rule for a console somebody reads during an
   incident. A hero figure of a control plane that does not move reads as a screenshot of one, which
   is the opposite of what a hero is for. Slow enough to sit under reading, and gone entirely under
   `prefers-reduced-motion`.
   ========================================================================== */

/* ------------------------------------------------------------------------------- the figure */

.mk-fig {
  width: 100%;
  max-width: 620px;
  margin: 0 auto;
}

.mk-fig-scene {
  position: relative;
  width: 100%;
  /* A ratio rather than a height, so the cards keep their corners at every width. */
  aspect-ratio: 600 / 420;
  /* The shared vanishing point. Without a perspective on the parent, `translateZ` on the faces is
     a no-op and the cube collapses to a flat cross. */
  perspective: 1100px;
}

.mk-fig-links {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.mk-fig-link {
  stroke-width: 1;
  /* Drawn on entry: the dash is the full path length, so the offset animates the line into being.
     One-shot, `forwards`, so it is a reveal rather than a loop. */
  stroke-dasharray: 260;
  stroke-dashoffset: 260;
  animation: mk-fig-draw 1.6s cubic-bezier(0.32, 0.72, 0, 1) 0.3s forwards;
}

@keyframes mk-fig-draw {
  to { stroke-dashoffset: 0; }
}

/* ----------------------------------------------------------------------------------- the cube */

.mk-fig-cube {
  position: absolute;
  left: 50%;
  top: 34%;
  width: 150px;
  height: 150px;
  margin: -75px 0 0 -75px;
  transform-style: preserve-3d;
  animation: mk-fig-turn 44s linear infinite;
}

@keyframes mk-fig-turn {
  from { transform: rotateX(-14deg) rotateY(0deg); }
  to { transform: rotateX(-14deg) rotateY(360deg); }
}

.mk-fig-face {
  position: absolute;
  inset: 0;
  border: 1px solid color-mix(in srgb, var(--mk-signal) 42%, transparent);
  border-radius: var(--radius-sm, 4px);
  background: linear-gradient(
    150deg,
    color-mix(in srgb, var(--mk-signal) 14%, transparent),
    color-mix(in srgb, var(--mk-violet) 8%, transparent)
  );
  /* Both sides, because a rotating cube shows the inside of its far faces and a hidden backface
     leaves a hole where the glass should be. */
  backface-visibility: visible;
}

.mk-fig-face-front { transform: translateZ(75px); }
.mk-fig-face-back { transform: rotateY(180deg) translateZ(75px); }
.mk-fig-face-left { transform: rotateY(-90deg) translateZ(75px); }
.mk-fig-face-right { transform: rotateY(90deg) translateZ(75px); }
.mk-fig-face-top { transform: rotateX(90deg) translateZ(75px); }
.mk-fig-face-bottom { transform: rotateX(-90deg) translateZ(75px); }

/*
 * The core.
 *
 * An abstract ring, not the OpenShift mark — see the note at the head of `marketing-figure.tsx`.
 * It counter-rotates so it stays face-on while the cube turns around it, which is what makes the
 * cube read as glass with something inside rather than as a decorated box.
 */
.mk-fig-core {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 66px;
  height: 66px;
  margin: -33px 0 0 -33px;
  border-radius: var(--radius-full);
  border: 5px solid transparent;
  background:
    linear-gradient(var(--bg-page), var(--bg-page)) padding-box,
    conic-gradient(from 210deg, var(--mk-violet), var(--mk-signal), transparent 78%) border-box;
  box-shadow: 0 0 28px color-mix(in srgb, var(--mk-signal) 45%, transparent);
  animation: mk-fig-counter 44s linear infinite reverse;
}

@keyframes mk-fig-counter {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

/* ------------------------------------------------------------------- the light and the platform */

.mk-fig-beam {
  position: absolute;
  left: 50%;
  top: 40%;
  width: 190px;
  height: 200px;
  margin-left: -95px;
  pointer-events: none;
  background: linear-gradient(
    to top,
    color-mix(in srgb, var(--mk-signal) 34%, transparent),
    transparent 78%
  );
  /* The taper. A cone is a trapezoid with its top edge pulled in, and a clip path is the only way
     to get one that does not need an image. */
  clip-path: polygon(34% 0%, 66% 0%, 88% 100%, 12% 100%);
  filter: blur(6px);
}

.mk-fig-plate {
  position: absolute;
  left: 50%;
  top: 62%;
  width: 62%;
  aspect-ratio: 3 / 1;
  margin-left: -31%;
  border-radius: var(--radius-full);
  background: radial-gradient(
    ellipse at center,
    color-mix(in srgb, var(--mk-signal) 30%, transparent),
    transparent 68%
  );
  filter: blur(4px);
}

.mk-fig-plate-ring {
  position: absolute;
  left: 50%;
  top: 64%;
  width: 46%;
  aspect-ratio: 3 / 1;
  margin-left: -23%;
  border: 1px solid color-mix(in srgb, var(--mk-signal) 40%, transparent);
  border-radius: var(--radius-full);
}

/* --------------------------------------------------------------------------- the orbit cards */

.mk-fig-card {
  position: absolute;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 128px;
  padding: var(--space-3);
  border: 1px solid color-mix(in srgb, var(--mk-signal) 20%, var(--color-border));
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--bg-surface) 68%, transparent);
  backdrop-filter: blur(14px);
  box-shadow: inset 0 1px 0 var(--mk-edge);
  /* Entrance only. The delay per corner is what makes them arrive as a system rather than at once. */
  opacity: 0;
  animation: mk-fig-arrive 0.7s cubic-bezier(0.32, 0.72, 0, 1) forwards;
}

@keyframes mk-fig-arrive {
  from { opacity: 0; transform: translateY(10px) scale(0.97); }
  to { opacity: 1; transform: none; }
}

.mk-fig-card-tl { left: 0; top: 8%; animation-delay: 0.15s; }
.mk-fig-card-tr { right: 0; top: 8%; animation-delay: 0.3s; }
.mk-fig-card-bl { left: 4%; top: 62%; animation-delay: 0.45s; }
.mk-fig-card-br { right: 4%; top: 62%; animation-delay: 0.6s; }

.mk-fig-card-title {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text-primary);
}

.mk-fig-card-note {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

/* ---------------------------------------------------------------------------------- the flow */

.mk-fig-flow {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
  margin: var(--space-4) 0 0;
  padding: 0;
  list-style: none;
}

.mk-fig-flow-step {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* The separator is drawn, so a wrap cannot strand one at the end of a row. */
.mk-fig-flow-step + .mk-fig-flow-step::before {
  content: "→";
  margin-right: var(--space-3);
  color: color-mix(in srgb, var(--mk-signal) 70%, transparent);
}

@media (prefers-reduced-motion: reduce) {
  /* The exemption ends here. Everything settles into its finished state and nothing turns. */
  .mk-fig-cube,
  .mk-fig-core {
    animation: none;
  }
  .mk-fig-link {
    stroke-dashoffset: 0;
    animation: none;
  }
  .mk-fig-card {
    opacity: 1;
    animation: none;
  }
}

/* ------------------------------------------------------------------------- the dashboard band */

.mk-stats {
  /*
   * Pulled up into the band above.
   *
   * The hero band's bottom padding and this band's top padding are both `clamp(56px, 8vw, 104px)`,
   * which is right between two blocks of prose and is a void under a figure — around two hundred
   * pixels of nothing between the lifecycle legend and the first card. The cards are the hero's
   * continuation rather than the next section, so they close the gap instead of sitting after it.
   */
  margin-top: clamp(-80px, -6vw, -32px);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(290px, 1fr));
  gap: var(--space-4);
  width: 100%;
}

.mk-stat {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5) var(--space-5);
  border: 1px solid color-mix(in srgb, var(--mk-signal) 14%, var(--color-border));
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--bg-surface) 70%, transparent);
  backdrop-filter: blur(16px);
  box-shadow: inset 0 1px 0 var(--mk-edge);
  transition: border-color var(--motion-base) var(--ease-out), transform var(--motion-base) var(--ease-out);
}

/* The hover is a lift and a brighter edge, not a colour change: these are surfaces, and a surface
   that changes hue under the pointer reads as a control that can be pressed. */
.mk-stat:hover {
  border-color: color-mix(in srgb, var(--mk-signal) 34%, var(--color-border));
  transform: translateY(-2px);
}

@media (prefers-reduced-motion: reduce) {
  .mk-stat { transition: none; }
  .mk-stat:hover { transform: none; }
}

@supports not (backdrop-filter: blur(1px)) {
  .mk-stat,
  .mk-fig-card {
    background: var(--bg-surface);
  }
}

.mk-stat-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

/* The square mark. It carries no glyph on purpose — an icon here would have to mean something, and
   three different icons across three cards is three meanings nobody assigned. */
.mk-stat-mark {
  flex: none;
  width: 30px;
  height: 30px;
  border-radius: var(--radius-md);
  border: 1px solid color-mix(in srgb, var(--mk-signal) 30%, transparent);
  background: linear-gradient(
    140deg,
    color-mix(in srgb, var(--mk-signal) 26%, transparent),
    color-mix(in srgb, var(--mk-violet) 20%, transparent)
  );
}

.mk-stat-heads {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.mk-stat-title {
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-primary);
}

.mk-stat-note {
  font-size: var(--text-xs);
  color: var(--text-muted);
}

.mk-stat-badge {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-secondary);
  white-space: nowrap;
}

.mk-stat-badge::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: var(--radius-full);
}

.mk-stat-badge-ok::before { background: var(--ok); }
.mk-stat-badge-info::before { background: var(--mk-signal); }

/* --------------------------------------------------------------------------------- the bars */

.mk-stat-plot {
  display: flex;
  align-items: flex-end;
  gap: 5px;
  height: 104px;
}

.mk-stat-bar {
  flex: 1;
  border-radius: 3px 3px 0 0;
  background: linear-gradient(
    to top,
    color-mix(in srgb, var(--mk-violet) 34%, transparent),
    color-mix(in srgb, var(--mk-signal) 88%, transparent)
  );
  /* Grown from the baseline on entry. `transform-origin` at the foot is what makes it grow rather
     than expand in both directions, and a transform animates without laying the row out again. */
  transform: scaleY(0);
  transform-origin: 50% 100%;
  animation: mk-stat-grow 0.8s cubic-bezier(0.32, 0.72, 0, 1) forwards;
}

.mk-stat-bar:nth-child(1) { animation-delay: 0.02s; }
.mk-stat-bar:nth-child(2) { animation-delay: 0.06s; }
.mk-stat-bar:nth-child(3) { animation-delay: 0.1s; }
.mk-stat-bar:nth-child(4) { animation-delay: 0.14s; }
.mk-stat-bar:nth-child(5) { animation-delay: 0.18s; }
.mk-stat-bar:nth-child(6) { animation-delay: 0.22s; }
.mk-stat-bar:nth-child(7) { animation-delay: 0.26s; }
.mk-stat-bar:nth-child(8) { animation-delay: 0.3s; }
.mk-stat-bar:nth-child(9) { animation-delay: 0.34s; }
.mk-stat-bar:nth-child(10) { animation-delay: 0.38s; }

@keyframes mk-stat-grow {
  to { transform: scaleY(1); }
}

/* --------------------------------------------------------------------------------- the donut */

.mk-stat-queue {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

.mk-stat-legend {
  flex: 1;
  min-width: 0;
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.mk-stat-legend-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

.mk-stat-legend-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.mk-stat-legend-value {
  margin-left: auto;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}

.mk-stat-dot {
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: var(--radius-full);
}
.mk-stat-dot-ok { background: var(--ok); }
.mk-stat-dot-warn { background: var(--warn); }
.mk-stat-dot-info { background: var(--mk-signal); }

.mk-stat-ring {
  position: relative;
  flex: none;
  width: 104px;
  height: 104px;
}

.mk-stat-donut {
  width: 100%;
  height: 100%;
  /* Twelve o'clock, not three. A ring that starts on the right reads as an arbitrary slice. */
  transform: rotate(-90deg);
}

.mk-stat-donut-track,
.mk-stat-donut-arc {
  fill: none;
  stroke-width: 9;
}

.mk-stat-donut-track {
  stroke: color-mix(in srgb, var(--text-muted) 22%, transparent);
}

.mk-stat-donut-arc {
  stroke-linecap: butt;
}

.mk-stat-arc-ok { stroke: var(--ok); }
.mk-stat-arc-info { stroke: var(--mk-signal); }
.mk-stat-arc-warn { stroke: var(--warn); }

.mk-stat-ring-centre {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
}

.mk-stat-ring-value {
  font-size: var(--text-lg);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
  line-height: 1;
}

.mk-stat-ring-label,
.mk-stat-figure-label {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

/* ------------------------------------------------------------------------------ the agent strip */

/*
 * One cell per agent, beside "12 of 12 / Agents reporting".
 *
 * It replaced a rising line under a "99.9% Uptime" figure. The figure was a service-level claim the
 * vendor of self-hosted software cannot make, and a line that climbs reads as growth; a row of lit
 * cells is what the Agents page actually looks like — one heartbeat each — and promises nothing.
 */
.mk-stat-fleet {
  display: flex;
  align-items: flex-end;
  gap: var(--space-4);
}

.mk-stat-cells {
  flex: 1;
  min-width: 0;
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 6px;
  align-self: stretch;
  align-content: end;
}

.mk-stat-cell {
  height: 28px;
  border-radius: var(--radius-sm, 4px);
  border: 1px solid color-mix(in srgb, var(--ok) 45%, transparent);
  background: color-mix(in srgb, var(--ok) 18%, transparent);
}

.mk-stat-figure {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  flex: none;
}

.mk-stat-figure-value {
  font-size: var(--text-xl, 22px);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
  line-height: 1.1;
}

/* "of 12" is the denominator, so it is set as one: quieter than the count it qualifies. */
.mk-stat-figure-of {
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--text-muted);
}

/* ---------------------------------------------------------------------------- the caption */

/*
 * The band is a `<figure>`, which carries a 40px browser margin on every side. The cards are pulled
 * up into the hero by `.mk-stats`'s negative margin, and a default margin here would push them back.
 */
.mk-stats-figure {
  margin: 0;
}

/*
 * "Illustration of the console — not customer data."
 *
 * Small and quiet, but on screen, and not a tooltip. It is what makes the numbers in the cards a
 * picture of an interface rather than a claim, and `marketing.test.ts` fails if it goes.
 */
.mk-stats-caption {
  margin: var(--space-3) 0 0;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.04em;
  color: var(--text-muted);
  text-align: center;
}

@media (prefers-reduced-motion: reduce) {
  .mk-stat-bar {
    transform: scaleY(1);
    animation: none;
  }
}

/*
 * Below the split the figure and the cards stack.
 *
 * At phone width the two lower cards collided: each is as wide as its icon plus its longest line,
 * and the bottom pair sat at 4% from either edge, so at 390px "Provision" and "Operate" overlapped by
 * a third of a card. Each card is now capped below half the scene and pinned to its own edge, and the
 * note wraps inside it rather than widening it — two cards can no longer be wider than the row.
 */
@media (max-width: 720px) {
  .mk-fig-scene {
    aspect-ratio: 1 / 1;
  }
  .mk-fig-card {
    min-width: 0;
    max-width: 47%;
    padding: var(--space-2);
    gap: var(--space-2);
  }
  .mk-fig-card-bl {
    left: 0;
  }
  .mk-fig-card-br {
    right: 0;
  }
  .mk-fig-card-note {
    white-space: normal;
    line-height: 1.3;
  }
  .mk-stat-queue,
  .mk-stat-fleet {
    gap: var(--space-3);
  }
}

/* ==========================================================================
   The split hero: text left, control plane right
   ==========================================================================

   The composition brief, and the measurement behind it: on a 1920x1080 screen the centred
   arrangement put the figure below the fold and the dashboard cards a full screen further down. A
   hero figure the reader has to scroll to reach is not doing the job a hero figure exists for.

   So at the wide breakpoint the hero is two columns — 45/55, text and figure — and the vertical
   rhythm above and below it is tightened so the cards land inside the first viewport. Below the
   breakpoint everything returns to the stack: two columns at tablet width is one column with the
   figure squeezed into a strip, and at phone width it is worse than that.
   ========================================================================== */

.mk-hero-col {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  width: 100%;
}

.mk-hero-figure {
  width: 100%;
}

/*
 * The height of the floating nav: 10px of bar padding either side of a 50px pill. Declared once, so
 * the two places that have to clear it — the hero band at every width, and the scroll margin an
 * anchor lands with — cannot disagree about how tall it is.
 */
.mk-page {
  --mk-nav-clear: 72px;
}
.mk-band-glow {
  padding-top: calc(var(--mk-nav-clear) + clamp(20px, 4vw, 40px));
}

@media (min-width: 1100px) {
  .mk-hero--split {
    display: grid;
    /* 45 / 55, as briefed. `minmax(0, …)` on both tracks because a grid column's default minimum is
       its content, and the headline is one long unbreakable line at this size — without it the text
       column refuses to shrink and pushes the figure off the grid. */
    grid-template-columns: minmax(0, 45fr) minmax(0, 55fr);
    gap: clamp(32px, 4vw, 72px);
    align-items: center;
    text-align: left;
  }

  .mk-hero--split .mk-hero-col {
    align-items: flex-start;
    text-align: left;
  }

  /* The lead is capped at the briefed measure rather than the shared 62ch, which at this size runs
     wider than the column the headline sets. */
  .mk-hero--split .mk-lead {
    max-width: 600px;
    margin-bottom: var(--space-5);
    text-align: left;
  }

  .mk-hero--split .mk-hero-actions,
  .mk-hero--split .mk-assure {
    justify-content: flex-start;
  }

  .mk-hero--split .mk-assure {
    margin-bottom: 0;
    gap: var(--space-2) var(--space-4);
  }

  /*
   * The headline, at the briefed size.
   *
   * `clamp` still, not a fixed 72px: the column is 45% of the measure, so a fixed size that fits at
   * 1920 overflows at 1100 where this layout starts. The ceiling is the brief's number.
   */
  .mk-hero--split .mk-headline {
    font-size: clamp(42px, 3.9vw, 72px);
    line-height: 1.02;
    margin-bottom: var(--space-4);
  }

  .mk-hero--split .mk-fig {
    max-width: none;
  }

  /* The figure fills its column rather than sitting in the middle of it at its old 620px cap. */
  .mk-hero--split .mk-fig-scene {
    aspect-ratio: 5 / 4;
  }

  /*
   * Vertical budget. The brief asks for the hero and the three cards inside one 1080px viewport,
   * and the band's own `clamp(56px, 8vw, 104px)` padding spends 208px of it before any content.
   * These two rules are what buys the room; they apply only where the split does.
   *
   * The top is the fixed nav's height plus a margin, not a bare clamp. It was `clamp(28px, 3vw,
   * 44px)`, which is less than the 70px the floating nav occupies — so at 1440px the hero's eyebrow
   * sat underneath the pill and was never seen. The nav floats over this band by design; the band's
   * padding has to be the thing that makes room for it.
   */
  .mk-band-glow {
    padding-top: calc(var(--mk-nav-clear) + clamp(10px, 1.4vw, 24px));
    padding-bottom: clamp(16px, 2vw, 28px);
  }
}


/*
 * The atmosphere: stars and haze.
 *
 * `Starfield` already exists for the signed-out surfaces and is the product's one sanctioned
 * looping animation, on the stated grounds that login has no data and is the product's marketing
 * face. Reusing it here rather than writing particles again means one implementation that already
 * stops when the tab is hidden and draws a single static frame under `prefers-reduced-motion`.
 *
 * The haze is a horizon rather than mountains. A silhouette needs either a photograph or a path
 * with enough vertices to stop reading as a sawtooth, and neither earns its place under a figure
 * that is itself the subject — so the bottom of the band fades into the page instead.
 */
.mk-hero-sky {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

.mk-hero-sky .auth-scene {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0.55;
}

.mk-hero-sky::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 42%;
  background: linear-gradient(to top, var(--bg-page) 12%, transparent);
}

/*
 * The measure, widened on a wide screen.
 *
 * `--mk-measure` is 1120px everywhere, which is the right reading width for the bands of prose
 * further down and much too narrow for the split hero: at 1920 it leaves 400px of margin on each
 * side, and the 45% text column comes out at 472px. A 72px headline in a 472px column wraps every
 * five or six characters — measured at six lines and 368px tall, which is not a headline, it is a
 * paragraph in a large font.
 *
 * Raised only above 1600px and only to 1440, so the prose bands keep a sane line length on an
 * ordinary laptop and the page still has margins on a very wide monitor rather than running edge to
 * edge.
 */
@media (min-width: 1600px) {
  .mk-band {
    --mk-measure: 1440px;
  }
}

/*
 * The hero column sits above the sky.
 *
 * `.mk-hero-sky` is positioned with `z-index: 0`; `.mk-hero` was static, and a positioned element
 * with a z-index paints above a static sibling whatever the source order. So the starfield canvas
 * was drawn over the buttons and the trust row: the primary call to action rendered under a veil of
 * near-black and the three assurances were invisible entirely, while both measured as present and
 * correctly positioned in the DOM.
 *
 * That is the failure mode worth naming — nothing was missing, nothing was mis-laid, and a test that
 * asserts an element exists and has a box would have passed. It is visible only by looking.
 */
.mk-hero {
  position: relative;
  z-index: 1;
}

/*
 * The wordmark as a link.
 *
 * It keeps the `.mk-wordmark` type styling and adds only what being a link requires: no underline,
 * the inherited ink rather than a link colour, and a focus ring — a keyboard user reaching the first
 * item in the nav has to be able to see where they are.
 */
.mk-wordmark-link {
  text-decoration: none;
  color: inherit;
}

.mk-wordmark-link:hover {
  /* A dim rather than a colour shift: the wordmark is the product's name, and a name that changes
     hue under the pointer reads as a button. */
  opacity: 0.78;
}

.mk-wordmark-link:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 3px;
  border-radius: var(--radius-sm, 4px);
}

/* ==========================================================================
   Figure and telemetry: the detail pass
   ========================================================================== */

/* The orbit card becomes icon + text rather than two stacked lines. */
.mk-fig-card {
  flex-direction: row;
  align-items: center;
  gap: var(--space-3);
  border-radius: 14px;
  transition:
    transform var(--motion-base) var(--ease-out),
    border-color var(--motion-base) var(--ease-out),
    box-shadow var(--motion-base) var(--ease-out);
}

.mk-fig-card-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 30px;
  height: 30px;
  border-radius: 9px;
  border: 1px solid color-mix(in srgb, var(--mk-signal) 26%, transparent);
  background: linear-gradient(
    145deg,
    color-mix(in srgb, var(--mk-signal) 22%, transparent),
    color-mix(in srgb, var(--mk-violet) 14%, transparent)
  );
  color: var(--text-primary);
}

.mk-fig-card-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

/*
 * Hover: a lift and an edge light, no colour change.
 *
 * These are panels in a diagram, not controls — nothing happens when you press one. A hover that
 * brightens the fill would promise an action the card cannot perform, so the response is depth
 * only: it comes toward the reader and its edge catches more light.
 */
.mk-fig-card:hover {
  transform: translateY(-3px);
  border-color: color-mix(in srgb, var(--mk-signal) 45%, transparent);
  box-shadow:
    inset 0 1px 0 var(--mk-edge),
    0 10px 30px color-mix(in srgb, var(--mk-signal) 16%, transparent);
}

/* --------------------------------------------------------------- the machined platform rings */

.mk-fig-plate-ring {
  position: absolute;
  left: 50%;
  border-radius: var(--radius-full);
  border: 1px solid color-mix(in srgb, var(--mk-signal) 34%, transparent);
  aspect-ratio: 3 / 1;
  pointer-events: none;
}

.mk-fig-plate-ring-1 { top: 63.5%; width: 52%; margin-left: -26%; }
.mk-fig-plate-ring-2 {
  top: 65%;
  width: 38%;
  margin-left: -19%;
  border-color: color-mix(in srgb, var(--mk-signal) 52%, transparent);
}
.mk-fig-plate-ring-3 {
  top: 66.5%;
  width: 24%;
  margin-left: -12%;
  border-color: color-mix(in srgb, var(--mk-violet) 48%, transparent);
}

/* ------------------------------------------------------------------ the horizon and the planet */

/*
 * A limb of light on the right edge, not a planet.
 *
 * The brief asks for an Earth with city lights. Drawn as a disc it becomes the second subject of the
 * composition and competes with the figure that is the first — and a convincing one needs a texture,
 * which is a binary this product cannot depend on. What survives is the part that actually carries
 * the atmosphere: a curved horizon glow entering from the right, clipped by the band.
 */
.mk-band-glow .mk-hero-sky::before {
  content: "";
  position: absolute;
  /*
   * Smaller and further out than the first attempt, which read as a blob rather than a horizon.
   *
   * At 46% wide it was a second subject: a soft disc crossing the figure's own cards, competing for
   * the eye with the thing the composition is about. The brief's own word for this element is
   * "subtle" — so what is left is the sliver of a limb entering the frame, most of it outside it.
   */
  right: -30%;
  top: 26%;
  width: 34%;
  aspect-ratio: 1;
  border-radius: var(--radius-full);
  background:
    radial-gradient(
      circle at 28% 58%,
      color-mix(in srgb, var(--mk-signal) 16%, transparent),
      transparent 55%
    );
  /* The rim. A border would be a hard ring; an inset shadow reads as light along a limb. */
  box-shadow: inset 14px -8px 48px color-mix(in srgb, var(--mk-violet) 16%, transparent);
  filter: blur(3px);
  opacity: 0.75;
}

@media (max-width: 1099px) {
  /* Off below the split: on a narrow screen the limb crosses the text column. */
  .mk-band-glow .mk-hero-sky::before { display: none; }
}

/* --------------------------------------------------------------------- telemetry chart polish */

/*
 * A baseline and two gridlines behind the bars, drawn on the plot rather than added as elements.
 *
 * A chart with no reference lines reads as a decorative shape; three faint ones make it read as a
 * measurement without adding anything a reader has to interpret. They are unlabelled on purpose —
 * a labelled axis would be a scale, and a scale on a figure with no units is a claim.
 */
.mk-stat-plot {
  position: relative;
  padding-bottom: 1px;
  background-image: repeating-linear-gradient(
    to top,
    color-mix(in srgb, var(--text-muted) 16%, transparent) 0 1px,
    transparent 1px 34px
  );
}

.mk-stat-bar {
  position: relative;
  transition: filter var(--motion-fast) var(--ease-out);
}

/* Hover the plot and the bars dim except the one under the pointer, which is the cheapest possible
   highlight: no JavaScript, no state, and it cannot desynchronise from what is drawn. */
.mk-stat-plot:hover .mk-stat-bar { filter: brightness(0.62); }
.mk-stat-plot .mk-stat-bar:hover { filter: brightness(1.18); }

@media (prefers-reduced-motion: reduce) {
  .mk-fig-card { transition: none; }
  .mk-fig-card:hover { transform: none; }
  .mk-stat-bar { transition: none; }
}

/* ==========================================================================
   Hero entrance
   ==========================================================================

   A stagger down the left column, reusing `auth-rise` — the keyframe the sign-in surfaces already
   animate with. One definition, two callers, and it is `both`-filled and one-shot, so it settles
   rather than looping and there is nothing here for the loop ban to catch.

   The figure is excluded. It has its own arrival — the links draw and the four cards fade in on
   their own delays — and running a column-level rise over the top of that produces two entrances
   fighting for the same 700ms.
   ========================================================================== */

.mk-hero-col > * {
  animation: auth-rise 0.7s cubic-bezier(0.32, 0.72, 0, 1) both;
}

/* Eyebrow, headline, lead, actions, assurances. The step is 70ms: enough to read as a sequence,
   short enough that the last item is on screen inside half a second. */
.mk-hero-col > *:nth-child(1) { animation-delay: 0.04s; }
.mk-hero-col > *:nth-child(2) { animation-delay: 0.11s; }
.mk-hero-col > *:nth-child(3) { animation-delay: 0.18s; }
.mk-hero-col > *:nth-child(4) { animation-delay: 0.25s; }
.mk-hero-col > *:nth-child(5) { animation-delay: 0.32s; }

@media (prefers-reduced-motion: reduce) {
  .mk-hero-col > * { animation: none; }
}

/* ------------------------------------------------------------------------- the CTA pair */

/*
 * The primary is the page's one bright surface, and it stays a light pill rather than becoming a
 * blue-violet gradient.
 *
 * The brief offers either. A gradient button next to a gradient headline next to a gradient figure
 * is three gradients competing, and the headline is the one that should win — so the button earns
 * its prominence from being the only near-white object on a near-black page, which is a stronger
 * signal than another hue.
 */
.mk-cta-lg {
  transition:
    transform var(--motion-fast) var(--ease-out),
    box-shadow var(--motion-fast) var(--ease-out),
    border-color var(--motion-fast) var(--ease-out);
}

.mk-cta-primary.mk-cta-lg:hover {
  transform: translateY(-1px);
  box-shadow: 0 8px 26px color-mix(in srgb, var(--mk-signal) 26%, transparent);
}

.mk-cta-ghost.mk-cta-lg {
  border: 1px solid color-mix(in srgb, var(--mk-signal) 18%, var(--color-border));
  background: color-mix(in srgb, var(--bg-surface) 55%, transparent);
  border-radius: 12px;
}

.mk-cta-ghost.mk-cta-lg:hover {
  transform: translateY(-1px);
  border-color: color-mix(in srgb, var(--mk-signal) 42%, transparent);
  background: color-mix(in srgb, var(--bg-surface) 78%, transparent);
}

@media (prefers-reduced-motion: reduce) {
  .mk-cta-lg { transition: none; }
  .mk-cta-primary.mk-cta-lg:hover,
  .mk-cta-ghost.mk-cta-lg:hover { transform: none; }
}

/* ---------------------------------------------------------------------------------- the footer */

/*
 * The foot of the page, and the reason the page needed one.
 *
 * It ended on a call to action and then stopped. That is right for a landing page with one door,
 * and this one has several — two consoles, an enquiry form, four sections of argument — so a reader
 * who scrolled past the one they wanted had the scroll bar and nothing else.
 *
 * Sunken rather than another band: the page alternates page/surface grounds all the way down, and a
 * third, darker ground is what says the argument is over. The columns are a site map, not a second
 * pitch, so nothing here is set larger than body text.
 */
.mk-footer {
  /*
   * The page's own ground, which is darker than every band above it.
   *
   * `--bg-sunken` was the first choice and landed within a shade of the accent band directly above,
   * so the foot read as one more band with a rule across it rather than as the end of the page. The
   * alternation stops here, and the ground saying so is what a footer is.
   */
  background: var(--bg-page);
  border-top: 1px solid var(--color-border);
  padding: clamp(48px, 6vw, 76px) 0 var(--space-6);
}

.mk-footer-inner {
  max-width: 1320px;
  margin: 0 auto;
  padding: 0 clamp(20px, 5vw, 48px);
}

.mk-footer-cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: clamp(28px, 4vw, 56px);
}

.mk-footer-col {
  min-width: 0;
}

.mk-footer-heading {
  margin: 0 0 var(--space-4);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.mk-footer-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 13px;
}

.mk-footer-link {
  font-size: var(--text-md);
  color: var(--text-secondary);
  text-decoration: none;
}
.mk-footer-link:hover {
  color: var(--text-primary);
  text-decoration: none;
}

.mk-footer-base {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-5);
  margin-top: clamp(40px, 5vw, 64px);
  padding-top: var(--space-5);
  border-top: 1px solid var(--color-border);
}

.mk-footer-id {
  min-width: 0;
}

.mk-footer-wordmark {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin: 0;
  font-size: var(--text-lg);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--text-primary);
}

.mk-footer-note {
  margin: 6px 0 0;
  max-width: 52ch;
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--text-muted);
}

.mk-footer-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.mk-footer-actions .mk-cta {
  border-radius: 999px;
}

/*
 * A capability card is a menu destination now, so it needs the same clearance the bands have.
 *
 * Without it an anchor jump puts the card's own heading under the fixed nav — which is the failure
 * `.mk-band` already carries a `scroll-margin-top` for, and the menu made it reachable one level
 * further down.
 */
.mk-feature[id] {
  scroll-margin-top: 96px;
}

/* ================================================================================== the tour band
 *
 * A band that plays through the consoles, one surface at a time. The reference plays a recording;
 * this draws the screens from the product's own tokens and steps them, for the reasons the
 * component's header sets out — a video is a binary on a surface that fetches nothing, and a
 * recording is a picture of a build nobody notices has gone stale.
 *
 * Nothing here loops. Every moving part is a `transition` between states a counter in the component
 * steps through, so the motion stops the moment the tour is paused or the reader asks for less of
 * it, and the surface keeps the no-looping-animation rule `marketing.test.ts` enforces.
 * ============================================================================================== */

.mk-tour {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.mk-tour-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-5);
  flex-wrap: wrap;
}

.mk-tour-eyebrow {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 var(--space-3);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-muted);
}
/* The square that marks the eyebrow, in the accent — the same mark the bands above already use. */
.mk-tour-eyebrow::before {
  content: "";
  width: 7px;
  height: 7px;
  background: var(--mk-signal, var(--accent));
}

.mk-tour-heading {
  margin: 0;
  max-width: 18ch;
  font-size: clamp(28px, 3.4vw, 44px);
  line-height: 1.1;
  letter-spacing: -0.03em;
  font-weight: 600;
  color: var(--text-primary);
}

.mk-tour-action {
  display: flex;
  gap: var(--space-3);
}
.mk-tour-action .mk-cta {
  border-radius: 999px;
}

/* ------------------------------------------------------------------------------- the tab strip */

/*
 * Tabs that sit *on* the stage rather than above it.
 *
 * The active one is lighter and square-shouldered at the bottom, so it reads as the front of a
 * stack rather than as a selected pill. That is the whole reason the strip has its own ground: a
 * raised tab needs something to be raised out of.
 */
.mk-tour-tabs {
  display: flex;
  align-items: stretch;
  gap: 2px;
  overflow-x: auto;
  scrollbar-width: none;
  background: var(--bg-sunken);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}
.mk-tour-tabs::-webkit-scrollbar {
  display: none;
}

.mk-tour-tab {
  flex: 1 1 auto;
  padding: 16px var(--space-4);
  border: 0;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  background: transparent;
  font: inherit;
  font-size: var(--text-md);
  font-weight: 500;
  color: var(--text-muted);
  white-space: nowrap;
  cursor: pointer;
  transition:
    background 160ms var(--ease-out),
    color 160ms var(--ease-out);
}
.mk-tour-tab:hover {
  color: var(--text-primary);
}
.mk-tour-tab.is-on {
  background: var(--bg-page);
  color: var(--text-primary);
}
.mk-tour-tab:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -3px;
}

/* ----------------------------------------------------------------------------------- the stage */

.mk-tour-stage {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}

.mk-tour-card {
  display: grid;
  grid-template-columns: minmax(0, 1.75fr) minmax(280px, 1fr);
  width: 100%;
  border-radius: var(--radius-lg);
  overflow: hidden;
}

/*
 * The ground the window floats on.
 *
 * Four tones, cycled by slide, and they encode nothing — they are there so that six screens of the
 * same console are distinguishable at a glance before any of the words are read. Same argument as
 * the menu scenes, and named after colours rather than after products for the same reason.
 */
.mk-tour-screen {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 380px;
  padding: clamp(20px, 3vw, 44px);
}
.mk-tour-screen-magenta {
  background: linear-gradient(135deg, #e0417c 0%, #7b2a8e 42%, #221a49 78%, #14b8a6 100%);
}
.mk-tour-screen-violet {
  background: linear-gradient(135deg, #6d4bd8 0%, #3a2a78 44%, #191a3e 80%, #2fb6c4 100%);
}
.mk-tour-screen-teal {
  background: linear-gradient(135deg, #17a3a3 0%, #1d5f8a 40%, #241c4c 78%, #d8547e 100%);
}
.mk-tour-screen-amber {
  background: linear-gradient(135deg, #e0763f 0%, #a8356a 44%, #2a1c4e 80%, #3fb8d8 100%);
}

/* ------------------------------------------------------------------------ the drawn console */

/*
 * The window is committed to dark in both themes, like `.depmap-canvas` and for the same reason:
 * it is a picture of the product, and the product is a dark console. A light-theme reader looking
 * at a screenshot expects the screenshot to look like the thing it is of.
 */
.mk-tour-win {
  --tw-ground: #0b0e14;
  --tw-panel: rgba(255, 255, 255, 0.05);
  --tw-edge: rgba(255, 255, 255, 0.09);
  --tw-ink: #e7ecf6;
  --tw-dim: #8a94ac;
  --tw-live: #4ad6a0;
  --tw-warn: #f2b955;

  width: 100%;
  max-width: 660px;
  border: 1px solid var(--tw-edge);
  border-radius: 10px;
  background: var(--tw-ground);
  box-shadow: 0 30px 60px -24px rgba(0, 0, 0, 0.7);
  overflow: hidden;
}

.mk-tour-win-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 9px 12px;
  border-bottom: 1px solid var(--tw-edge);
  background: var(--tw-panel);
}
.mk-tour-dot {
  width: 7px;
  height: 7px;
  border-radius: 999px;
  background: var(--tw-edge);
}
.mk-tour-crumb {
  margin-left: 10px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.04em;
  color: var(--tw-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.mk-tour-win-body {
  display: grid;
  grid-template-columns: 122px minmax(0, 1fr);
  min-height: 226px;
}

.mk-tour-nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 10px 8px;
  border-right: 1px solid var(--tw-edge);
}
.mk-tour-nav-row {
  padding: 5px 8px;
  border-radius: 5px;
  font-size: 10.5px;
  color: var(--tw-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition:
    background 300ms var(--ease-out),
    color 300ms var(--ease-out);
}
.mk-tour-nav-row.is-on {
  background: var(--tw-panel);
  color: var(--tw-ink);
}

.mk-tour-main {
  display: flex;
  min-width: 0;
  padding: 14px;
}

/* ---- the map body */

.mk-tour-map {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
}
.mk-tour-map-canvas {
  width: 100%;
  height: auto;
}
.mk-tour-link {
  fill: none;
  stroke: var(--tw-edge);
  stroke-width: 1;
  transition:
    stroke 420ms var(--ease-out),
    stroke-width 420ms var(--ease-out);
}
.mk-tour-link.is-on {
  stroke: var(--tw-live);
  stroke-width: 1.4;
}
.mk-tour-node {
  fill: var(--tw-ground);
  stroke: var(--tw-edge);
  stroke-width: 1.2;
  transition:
    stroke 420ms var(--ease-out),
    fill 420ms var(--ease-out);
}
.mk-tour-node.is-on {
  stroke: var(--tw-live);
  fill: color-mix(in srgb, var(--tw-live) 22%, transparent);
}
/*
 * Opaque, so the links stop at it rather than crossing it.
 *
 * The wash it had was translucent and every edge drew straight through the middle of the focus —
 * which is the one thing the real map's geometry is careful about: an edge starts at the orb's rim,
 * never at its centre, or the picture says the centre is a junction rather than a subject.
 */
.mk-tour-core {
  fill: var(--tw-ground);
  stroke: var(--tw-ink);
  stroke-width: 1.4;
}
.mk-tour-core-wash {
  fill: color-mix(in srgb, var(--tw-ink) 12%, transparent);
  stroke: none;
}

/* ---- the chart body */

.mk-tour-chart {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}
.mk-tour-tiles {
  display: flex;
  gap: 8px;
}
.mk-tour-tile {
  flex: 1 1 0;
  padding: 9px 10px;
  border: 1px solid var(--tw-edge);
  border-radius: 6px;
  font-size: 10.5px;
  color: var(--tw-dim);
  opacity: 0.4;
  transition:
    opacity 360ms var(--ease-out),
    color 360ms var(--ease-out),
    border-color 360ms var(--ease-out);
}
.mk-tour-tile.is-on {
  opacity: 1;
  color: var(--tw-ink);
  border-color: color-mix(in srgb, var(--tw-live) 46%, transparent);
}
.mk-tour-bars {
  display: flex;
  align-items: flex-end;
  gap: 5px;
  flex: 1 1 auto;
  min-height: 86px;
  padding-top: 4px;
}
.mk-tour-bar {
  flex: 1 1 0;
  border-radius: 2px 2px 0 0;
  background: linear-gradient(180deg, var(--tw-live), color-mix(in srgb, var(--tw-live) 18%, transparent));
  transition: height 520ms var(--ease-out);
}
.mk-tour-legend {
  display: flex;
  gap: 14px;
}
.mk-tour-key {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--tw-dim);
}

/* ---- the queue body */

.mk-tour-rows {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
}
.mk-tour-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 7px 10px;
  border: 1px solid var(--tw-edge);
  border-radius: 6px;
  opacity: 0.28;
  transform: translateY(4px);
  transition:
    opacity 380ms var(--ease-out),
    transform 380ms var(--ease-out);
}
.mk-tour-row.is-on {
  opacity: 1;
  transform: none;
}
.mk-tour-row-name {
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--tw-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.mk-tour-pill {
  flex: none;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--tw-panel);
  font-size: 9.5px;
  letter-spacing: 0.03em;
  color: var(--tw-dim);
  white-space: nowrap;
}

/* ---- the logs body */

.mk-tour-logs {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}
.mk-tour-log {
  padding: 5px 9px;
  border-radius: 5px;
  border-left: 2px solid transparent;
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--tw-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition:
    background 320ms var(--ease-out),
    color 320ms var(--ease-out),
    border-color 320ms var(--ease-out);
}
.mk-tour-log.is-on {
  background: var(--tw-panel);
  border-left-color: var(--tw-live);
  color: var(--tw-ink);
}

/* ---- the chat body */

.mk-tour-chat {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
}
.mk-tour-bubble {
  align-self: flex-end;
  max-width: 84%;
  padding: 8px 12px;
  border-radius: 12px 12px 3px 12px;
  background: var(--tw-panel);
  font-size: 11px;
  color: var(--tw-ink);
}
.mk-tour-evidence {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.mk-tour-step {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 5px 9px;
  border: 1px solid var(--tw-edge);
  border-radius: 6px;
  font-family: var(--font-mono);
  font-size: 9.5px;
  color: var(--tw-dim);
  opacity: 0.3;
  transition: opacity 340ms var(--ease-out);
}
.mk-tour-step.is-on {
  opacity: 1;
}
/* A tick that fills when the tool has run. Each evidence row is a thing the platform measured. */
.mk-tour-step::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 999px;
  background: var(--tw-live);
}
.mk-tour-answer {
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding-top: 4px;
  opacity: 0;
  transition: opacity 420ms var(--ease-out);
}
.mk-tour-answer.is-on {
  opacity: 1;
}
.mk-tour-answer-line {
  height: 5px;
  border-radius: 999px;
  background: var(--tw-panel);
}
.mk-tour-answer-line.is-short {
  width: 56%;
}

/* ------------------------------------------------------------------------- the panel beside it */

/*
 * Paper, the same stock as the mega menu, and for the same reason: this is the part of the band a
 * reader reads rather than looks at. It keeps one set of colours in both themes, so there is no
 * arrangement in which its ink and its ground come from different ones.
 */
.mk-tour-info {
  --mk-paper: #faf7ee;
  --mk-paper-ink: #14171c;
  --mk-paper-dim: #5c626c;
  --mk-paper-rule: rgba(20, 23, 28, 0.15);

  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: clamp(24px, 2.4vw, 34px);
  background: var(--mk-paper);
  color: var(--mk-paper-ink);
}

.mk-tour-info-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
}

.mk-tour-title {
  margin: 0;
  font-size: clamp(24px, 2.2vw, 32px);
  line-height: 1.12;
  letter-spacing: -0.028em;
  font-weight: 600;
}

.mk-tour-ring {
  flex: none;
  width: 26px;
  height: 26px;
  /* Twelve o'clock, and clockwise: the direction every timer a reader has ever seen runs in. */
  transform: rotate(-90deg);
}
.mk-tour-ring-track {
  fill: none;
  stroke: var(--mk-paper-rule);
  stroke-width: 2;
}
.mk-tour-ring-head {
  fill: none;
  stroke: var(--mk-paper-ink);
  stroke-width: 2;
  stroke-linecap: round;
  /*
   * The tween is short on purpose. The offset is recomputed every frame from the clock, so this is
   * only smoothing the step between frames — a long transition here would make the ring lag the
   * thing it is measuring, which is the one thing a progress indicator may not do.
   */
  transition: stroke-dashoffset 120ms linear;
}

.mk-tour-chips {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  margin: 0;
  padding: 0;
}
.mk-tour-chip {
  padding: 5px 10px;
  border: 1px solid var(--mk-paper-rule);
  border-radius: 4px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--mk-paper-ink);
}

.mk-tour-lead {
  margin: auto 0 0;
  padding-top: var(--space-4);
  font-size: var(--text-md);
  line-height: 1.52;
  color: var(--mk-paper-dim);
}
.mk-tour-lead-strong {
  font-weight: 650;
  color: var(--mk-paper-ink);
}

.mk-tour-cta {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 52px;
  border-radius: 999px;
  background: var(--mk-paper-ink);
  color: var(--mk-paper);
  font-size: var(--text-md);
  font-weight: 550;
  text-decoration: none;
}
.mk-tour-cta:hover {
  background: color-mix(in srgb, var(--mk-paper-ink) 84%, transparent);
  /* The paper's own ink, restated: see "Every anchor on this surface restates its hover ink". */
  color: var(--mk-paper);
  text-decoration: none;
}

/* --------------------------------------------------------------------------------- the arrows */

.mk-tour-arrow {
  position: absolute;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  border: 1px solid var(--mk-paper-rule, var(--color-border));
  border-radius: 999px;
  background: #faf7ee;
  color: #14171c;
  cursor: pointer;
  transition: transform 160ms var(--ease-out);
}
.mk-tour-arrow svg {
  width: 19px;
  height: 19px;
}
.mk-tour-arrow:hover {
  transform: scale(1.06);
}
.mk-tour-arrow.is-prev {
  left: -26px;
}
.mk-tour-arrow.is-next {
  right: -26px;
}

@media (max-width: 900px) {
  .mk-tour-card {
    grid-template-columns: minmax(0, 1fr);
  }
  .mk-tour-screen {
    min-height: 260px;
  }
  /*
   * Under the card at this width, as a pair.
   *
   * Outside the card they hang off the page; inside it, vertically centred on a stacked card, they
   * landed on the seam between the screen and the panel and covered the first letters of the slide's
   * title (measured at 390px: the "D" of "Dependency map", the "AI" of "AI and SRE"). A row of two
   * under the card covers nothing and sits where a thumb already is.
   */
  .mk-tour-stage {
    padding-bottom: 58px;
  }
  .mk-tour-arrow {
    top: auto;
    bottom: 0;
    width: 44px;
    height: 44px;
  }
  .mk-tour-arrow.is-prev {
    left: calc(50% - 52px);
  }
  .mk-tour-arrow.is-next {
    right: calc(50% - 52px);
  }
  .mk-tour-tab {
    flex: 0 0 auto;
    padding: 14px var(--space-3);
    font-size: var(--text-sm);
  }
}

@media (prefers-reduced-motion: reduce) {
  /*
   * Every transition on this band goes. The component already stops stepping and renders the
   * finished frame, so there is nothing left to tween — and a 500ms fade on a state that never
   * changes again is motion charged to a reader who asked for none.
   */
  .mk-tour-tab,
  .mk-tour-nav-row,
  .mk-tour-link,
  .mk-tour-node,
  .mk-tour-tile,
  .mk-tour-bar,
  .mk-tour-row,
  .mk-tour-log,
  .mk-tour-step,
  .mk-tour-answer,
  .mk-tour-arrow,
  .mk-tour-ring-head {
    transition: none;
  }
}

/* ==========================================================================
   The docs page — `/welcome/docs`
   ==========================================================================

   Reading material for a visitor with no account, so the rules here serve one thing: a long page a
   person can find their place in. A contents list that stays in view, a measure short enough to
   read, and every colour from a token so the page follows the theme the reader chose.
   ========================================================================== */

/* The nav floats over the first band and is taller than that band's padding at phone width. */
.mk-docs-head {
  padding-top: 48px;
}

/*
 * `overflow: clip`, not the band's `hidden`.
 *
 * `hidden` makes the band a scroll container, and a sticky element sticks to its nearest scroll
 * container — one that never scrolls — so the contents list would scroll away with the page. `clip`
 * hides the same overflow without becoming a scroller.
 */
.mk-docs-band {
  overflow: clip;
}

.mk-docs {
  display: grid;
  grid-template-columns: 220px minmax(0, 1fr);
  gap: clamp(32px, 5vw, 72px);
  align-items: start;
}

.mk-docs-toc {
  position: sticky;
  top: 96px;
}
.mk-docs-toc-title {
  margin: 0 0 var(--space-3);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.mk-docs-toc-list {
  margin: 0;
  padding: 0;
  list-style: none;
  border-left: 1px solid var(--color-border);
}
.mk-docs-toc-link {
  display: block;
  padding: 6px 0 6px var(--space-3);
  margin-left: -1px;
  border-left: 1px solid transparent;
  font-size: var(--text-md);
  line-height: 1.4;
  color: var(--text-secondary);
  text-decoration: none;
  transition: color 120ms var(--ease-out), border-color 120ms var(--ease-out);
}
.mk-docs-toc-link:hover,
.mk-docs-toc-link:focus-visible {
  color: var(--text-primary);
  border-left-color: var(--text-primary);
}

/* Reading size. The app's base text is set for dense consoles, and this page is prose. */
.mk-docs-body {
  min-width: 0;
  max-width: 760px;
  font-size: 15px;
}

/* ------------------------------------------------------------------------------- one section */

.mk-doc {
  padding: 0 0 clamp(40px, 5vw, 64px);
  /* Clears the floating nav when a contents link jumps here. */
  scroll-margin-top: 96px;
}
.mk-doc + .mk-doc {
  padding-top: clamp(40px, 5vw, 64px);
  border-top: 1px solid var(--color-border);
}
.mk-doc-title {
  margin: 0 0 var(--space-3);
  font-size: clamp(22px, 2.4vw, 30px);
  line-height: 1.2;
  letter-spacing: -0.015em;
  font-weight: 600;
  color: var(--text-primary);
}
.mk-doc-sub {
  margin: var(--space-8) 0 var(--space-3);
  font-size: var(--text-xl);
  font-weight: 600;
  color: var(--text-primary);
}
.mk-doc-lead {
  margin: 0 0 var(--space-5);
  font-size: clamp(15px, 1.4vw, 17px);
  line-height: 1.6;
  color: var(--text-secondary);
}
.mk-doc-text {
  margin: 0 0 var(--space-4);
  line-height: 1.65;
  color: var(--text-secondary);
}
.mk-doc-note {
  margin: var(--space-5) 0 0;
  padding: var(--space-3) var(--space-4);
  border-left: 2px solid var(--color-border-strong);
  font-size: var(--text-md);
  line-height: 1.6;
  color: var(--text-secondary);
}
.mk-doc-link {
  color: var(--text-primary);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.mk-doc-list,
.mk-doc-steps {
  margin: 0 0 var(--space-4);
  padding-left: 1.25em;
  line-height: 1.65;
  color: var(--text-secondary);
}
.mk-doc-list li,
.mk-doc-steps li {
  margin-bottom: var(--space-2);
}
.mk-doc-steps li::marker {
  font-family: var(--font-mono);
  color: var(--text-muted);
}

/* -------------------------------------------------------------------- titled rows (dl) */

.mk-doc-items {
  margin: 0;
  border-top: 1px solid var(--color-border);
}
.mk-doc-item {
  display: grid;
  grid-template-columns: minmax(0, 15em) minmax(0, 1fr);
  gap: var(--space-2) var(--space-5);
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--color-border);
}
.mk-doc-item-title {
  font-weight: 600;
  line-height: 1.45;
  color: var(--text-primary);
}
.mk-doc-item-body {
  margin: 0;
  line-height: 1.6;
  color: var(--text-secondary);
}
/* A sentence after a row list starts clear of the list's last rule. */
.mk-doc-items + .mk-doc-text {
  margin-top: var(--space-5);
}

/* ----------------------------------------------------------------- the data-path figure */

.mk-doc-flow {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 28px;
  margin: 0 0 var(--space-5);
  padding: 0;
  list-style: none;
}
.mk-doc-flow-node {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--bg-page);
}
/* The arrow between two boxes, drawn in the gap so it needs no element of its own. */
.mk-doc-flow-node + .mk-doc-flow-node::before {
  content: "\2192";
  position: absolute;
  left: -22px;
  top: 50%;
  transform: translateY(-50%);
  font-size: var(--text-md);
  color: var(--text-muted);
}
.mk-doc-flow-where {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.mk-doc-flow-title {
  font-weight: 600;
  color: var(--text-primary);
}
.mk-doc-flow-body {
  font-size: var(--text-md);
  line-height: 1.55;
  color: var(--text-secondary);
}

/* ------------------------------------------------------------------------- the topology table */

/* The table scrolls inside its own box at phone width; the page never scrolls sideways. */
.mk-doc-table-wrap {
  overflow-x: auto;
  margin: 0 0 var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
}
.mk-doc-table {
  width: 100%;
  min-width: 560px;
  border-collapse: collapse;
  font-size: var(--text-md);
  line-height: 1.5;
}
.mk-doc-table th,
.mk-doc-table td {
  padding: var(--space-3) var(--space-4);
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--color-border);
}
.mk-doc-table tbody tr:last-child th,
.mk-doc-table tbody tr:last-child td {
  border-bottom: 0;
}
.mk-doc-table thead th {
  font-weight: 600;
  color: var(--text-primary);
  background: var(--bg-page);
}
.mk-doc-table tbody th {
  font-weight: 600;
  color: var(--text-primary);
}
.mk-doc-table td {
  color: var(--text-secondary);
}
/* The corner header names the row labels for a screen reader and draws nothing. */
.mk-doc-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* ------------------------------------------------------------------------------ platform chips */

.mk-doc-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0 0 var(--space-5);
  padding: 0;
  list-style: none;
}
.mk-doc-chip {
  padding: 6px 14px;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  font-size: var(--text-md);
  color: var(--text-primary);
  background: var(--bg-page);
}

/* ---------------------------------------------------------------------------- the engagement */

.mk-doc-journey {
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: mk-journey;
}
.mk-doc-journey-step {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 0 0 var(--space-6) 52px;
  counter-increment: mk-journey;
}
/* The number in a ring, and a rule down to the next one — a sequence, read top to bottom. */
.mk-doc-journey-step::before {
  content: counter(mk-journey);
  position: absolute;
  left: 0;
  top: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: 1px solid var(--color-border-strong);
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--text-primary);
  background: var(--bg-page);
}
.mk-doc-journey-step:not(:last-child)::after {
  content: "";
  position: absolute;
  left: 16px;
  top: 36px;
  bottom: 4px;
  border-left: 1px solid var(--color-border);
}
.mk-doc-journey-step:last-child {
  padding-bottom: 0;
}
.mk-doc-journey-title {
  padding-top: 5px;
  font-weight: 600;
  color: var(--text-primary);
}
.mk-doc-journey-body {
  line-height: 1.6;
  color: var(--text-secondary);
}

/* --------------------------------------------------------------------------------- narrow widths */

@media (max-width: 1040px) {
  .mk-doc-flow {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  /* In two columns the third box starts a new row, so an arrow beside it would point at nothing. */
  .mk-doc-flow-node:nth-child(odd)::before {
    content: none;
  }
}

@media (max-width: 900px) {
  .mk-docs {
    grid-template-columns: minmax(0, 1fr);
  }
  /* Above the content at this width, as a row of links that wraps — not a column that pushes the
     first section a screen down. */
  .mk-docs-toc {
    position: static;
  }
  .mk-docs-toc-list {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    border-left: 0;
  }
  .mk-docs-toc-link {
    padding: 6px 12px;
    margin-left: 0;
    border: 1px solid var(--color-border);
    border-radius: 999px;
  }
  .mk-docs-toc-link:hover,
  .mk-docs-toc-link:focus-visible {
    border-color: var(--text-primary);
  }
}

@media (max-width: 640px) {
  .mk-doc-flow {
    grid-template-columns: minmax(0, 1fr);
    gap: 26px;
  }
  /* Stacked, the arrow points down between boxes. */
  .mk-doc-flow-node + .mk-doc-flow-node::before {
    content: "\2193";
    left: 50%;
    top: -22px;
    transform: translateX(-50%);
  }
  .mk-doc-item {
    grid-template-columns: minmax(0, 1fr);
  }
}

@media (prefers-reduced-motion: reduce) {
  .mk-docs-toc-link {
    transition: none;
  }
}

/* ==========================================================================
   Anchors, the tour's pause control, the phone comparison, plans and questions
   ==========================================================================

   Added with the front-door pass that made every claim on `/welcome` checkable. The plans and the
   questions are drawn by `apps/portal/app/welcome/bands.tsx` in the same vocabulary as the security
   band — hairlines, a mono label leading, the evidence at the foot — so they read as the same
   material rather than as a pricing widget dropped onto the page.
   ========================================================================== */

/* ------------------------------------------------------------------------------- anchors */

/*
 * An in-page link lands with its target clear of the fixed nav.
 *
 * The bands already had this; the rows inside them did not, because until now nothing linked to a
 * row that was actually there. With ids on the rail rows, the differentiators, the loop steps, the
 * plans and the questions, every one of them is a destination and each would otherwise arrive with
 * its heading under the bar.
 */
.mk-rail-item,
.mk-hl-item,
.mk-feature,
.mk-plan,
.mk-faq-item {
  scroll-margin-top: calc(var(--mk-nav-clear, 72px) + 20px);
}

/* ----------------------------------------------------------------------- the tour's pause */

/*
 * The clock and the button that stops it, side by side.
 *
 * WCAG 2.2.2 asks for a way to pause anything that moves on its own for more than five seconds.
 * Hover and focus already held the tour, and neither exists on a phone. The button is a word as well
 * as a glyph, on the paper's ink, at the paper's own scale — part of the card rather than part of
 * the drawing of a console beside it.
 */
.mk-tour-clock {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  flex: none;
}

.mk-tour-pause {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 30px;
  padding: 0 12px 0 10px;
  border: 1px solid var(--mk-paper-rule);
  border-radius: 999px;
  background: transparent;
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 550;
  color: var(--mk-paper-ink);
  cursor: pointer;
}
.mk-tour-pause svg {
  width: 13px;
  height: 13px;
}
.mk-tour-pause:hover {
  background: var(--mk-paper-rule);
}
.mk-tour-pause:focus-visible {
  outline: 2px solid var(--mk-paper-ink);
  outline-offset: 2px;
}

/* ------------------------------------------------------------- the comparison, on a phone */

/*
 * One card per row below 640px.
 *
 * The table holds a 640px minimum so its prose cells stay readable, and at 390px that meant a
 * sideways scroll inside a box with nothing to say it scrolled — the right-hand column, half the
 * argument, was simply off the edge. Stacked, each row is its question with both answers under it,
 * and each answer prints its column's heading from `data-label` because the header row is hidden.
 */
@media (max-width: 640px) {
  .mk-cmp-scroll {
    overflow-x: visible;
    border: 0;
    background: none;
  }
  .mk-band-surface .mk-cmp-scroll {
    background: none;
  }
  .mk-cmp-table {
    min-width: 0;
  }
  .mk-cmp-table,
  .mk-cmp-table tbody,
  .mk-cmp-table tr,
  .mk-cmp-table th,
  .mk-cmp-table td {
    display: block;
    width: auto;
  }
  /* Hidden but still read: the column headings stay in the accessibility tree. */
  .mk-cmp-table thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }
  .mk-cmp-table tbody tr {
    margin-bottom: var(--space-3);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    overflow: hidden;
    background: var(--bg-surface);
  }
  .mk-cmp-table tbody tr:last-child {
    margin-bottom: 0;
  }
  .mk-cmp-table th,
  .mk-cmp-table td,
  .mk-cmp-table tbody tr:last-child th,
  .mk-cmp-table tbody tr:last-child td {
    border-bottom: 1px solid var(--color-border);
  }
  .mk-cmp-table tbody tr td:last-child {
    border-bottom: 0;
  }
  .mk-cmp-table td::before {
    content: attr(data-label);
    display: block;
    margin-bottom: 2px;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    font-weight: 500;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-muted);
  }
}

/* ------------------------------------------------------------------------------ plans */

.mk-plans {
  display: flow-root;
}

.mk-plans-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--space-4);
}

/* The security card's material: one hairline, the lit top edge, no float shadow. */
.mk-plan {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-5);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--bg-surface);
  box-shadow: inset 0 1px 0 var(--mk-edge);
}
.mk-band-surface .mk-plan {
  background: var(--bg-page);
}

.mk-plan-label {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.mk-plan-name {
  margin: 0;
  font-size: clamp(22px, 2vw, 26px);
  line-height: 1.15;
  letter-spacing: -0.02em;
  font-weight: 600;
  color: var(--text-primary);
}

.mk-plan-audience {
  margin: 0 0 var(--space-2);
  font-size: var(--text-md);
  color: var(--text-secondary);
}

/* The limits: label left, value right, one hairline each — a spec sheet, not a feature list. */
.mk-plan-limits {
  margin: 0 0 var(--space-3);
  border-top: 1px solid var(--color-border);
}
.mk-plan-limit {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  padding: 8px 0;
  border-bottom: 1px solid var(--color-border);
}
.mk-plan-limit dt {
  font-size: var(--text-sm);
  color: var(--text-muted);
}
.mk-plan-limit dd {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}

.mk-plan-includes-lead {
  margin: 0;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text-primary);
}

.mk-plan-includes {
  margin: 0 0 var(--space-3);
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.mk-plan-includes li {
  position: relative;
  padding-left: 16px;
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--text-secondary);
}
/* The same drawn dot the hero assurances use, so a list marker is never a glyph read aloud. */
.mk-plan-includes li::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 0.62em;
  width: 5px;
  height: 5px;
  border-radius: var(--radius-full);
  background: var(--mk-signal);
}

/* The price line sits at the foot of every card, behind a hairline, wherever the list ends. */
.mk-plan-price {
  margin: auto 0 0;
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border);
  font-size: var(--text-sm);
  color: var(--text-muted);
}
.mk-plan-ask {
  color: var(--text-primary);
  font-weight: 550;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.mk-plan-ask:hover {
  color: var(--text-primary);
}

.mk-plans-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-5);
  margin-top: var(--space-5);
}
.mk-plans-note {
  margin: 0;
  max-width: 62ch;
  font-size: var(--text-sm);
  color: var(--text-muted);
}

/* -------------------------------------------------------------------------- questions */

.mk-faq {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
  gap: var(--space-6) clamp(32px, 5vw, 72px);
  align-items: start;
}

.mk-faq-head .mk-heading {
  margin-bottom: var(--space-4);
}

.mk-faq-list {
  border-top: 1px solid var(--color-border);
}

.mk-faq-item {
  border-bottom: 1px solid var(--color-border);
}

/*
 * The summary is the whole row, and it is the control.
 *
 * The browser's own triangle is removed (`list-style: none` for the standard marker, the WebKit
 * pseudo-element for Safari) and replaced with a chevron at the end of the row, which turns when the
 * item opens. Focus is drawn on the row, because the row is what a keyboard reader is on.
 */
.mk-faq-q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) 0;
  list-style: none;
  cursor: pointer;
  font-size: var(--text-lg);
  font-weight: 600;
  line-height: 1.35;
  color: var(--text-primary);
}
.mk-faq-q::-webkit-details-marker {
  display: none;
}
/* The question wraps inside its own box, so a long one cannot push the chevron off the row. */
.mk-faq-q-text {
  min-width: 0;
}
.mk-faq-q:hover .mk-faq-q-text {
  text-decoration: none;
  text-underline-offset: 4px;
}
.mk-faq-q:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
  border-radius: var(--radius-sm, 4px);
}

.mk-faq-chev {
  display: inline-flex;
  flex: none;
  color: var(--text-muted);
  transition: transform 160ms var(--ease-out);
}
.mk-faq-item[open] .mk-faq-chev {
  transform: rotate(180deg);
}

.mk-faq-a {
  max-width: 68ch;
  padding: 0 0 var(--space-5);
  font-size: var(--text-md);
  line-height: 1.65;
  color: var(--text-secondary);
}

@media (max-width: 860px) {
  .mk-faq {
    grid-template-columns: minmax(0, 1fr);
  }
}

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

/* ==========================================================================
   The front door, as a console and a canvas (2026-10-20).

   Declared last so it wins over the layers above without editing them.

   - **The labels.** Every eyebrow, column heading and caption on the surface was tracked-out
     capitals, most in the monospace face. They are sentence case in the body face now; monospace
     stays where the text is code — a file path, a terminal.
   - **The stage.** The hero and the card canvas sit on `void`: a charcoal grid with grain, dark in
     both themes, lit by a spotlight that follows the pointer. On it, a terminal that answers
     commands, a node graph of the loop that tilts toward the pointer, cards that drag and throw,
     and buttons that lean toward the cursor. Emerald is the product's colour on the stage; purple,
     cyan and coral are the cards' and nothing else's.
   ========================================================================== */

/* ------------------------------------------------------------------------------- the labels */

.mk-eyebrow,
.mk-hero-eyebrow,
.mk-nav-group-heading,
.mk-menu-eyebrow,
.mk-menu-heading,
.mk-p-label,
.mk-hl-contrast-label,
.mk-sec-mech-label,
.mk-stat-title,
.mk-footer-heading,
.mk-tour-eyebrow,
.mk-tour-key,
.mk-tour-chip,
.mk-docs-toc-title,
.mk-doc-flow-where,
.mk-cmp-table td::before {
  font-family: var(--font-sans);
  letter-spacing: 0;
  text-transform: none;
}

/* The eyebrow over each band: a quiet line in the section's signal colour, not a stamped label. */
.mk-eyebrow {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--mk-signal);
}

/* The sectors are names, not codes. */
.mk-trust-cell b {
  font-family: var(--font-sans);
  font-weight: 600;
  letter-spacing: 0;
}

/* ------------------------------------------------------------------ the loop, in the hero's colours */

/*
 * The six steps of "What happens when something breaks" are the line in the hero, told in full, so
 * each number wears the colour that step has there: the forecast's blue while the platform and the
 * agent work it out, ink where a person and the cluster act, green for the check afterwards.
 */
#operate .mk-hl-index {
  --step: var(--text-primary);
  display: inline-grid;
  place-items: center;
  width: 28px;
  height: 28px;
  margin-top: -2px;
  padding: 0;
  border-radius: var(--radius-full);
  border: 2px solid var(--step);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--step);
}
#operate-warned .mk-hl-index,
#operate-investigate .mk-hl-index,
#operate-fix .mk-hl-index { --step: var(--mk-signal); }
#operate-verify .mk-hl-index { --step: var(--severity-ok-fill); }

/* --------------------------------------------------------------------------- the controls grid */

/* Eight controls, two to a row: four even rows instead of three rows with a hole in the last, and
   a line length the long bodies can be read at. */
@media (min-width: 900px) {
  .mk-sec-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ------------------------------------------------------------------------------ the trust row */

/* A single row of five names does not need a band's full height of air above and below it. */
.mk-band:has(> .mk-bleed > .mk-trust) {
  padding-block: clamp(32px, 4vw, 48px);
}

/* ------------------------------------------------------------------------------- the stage */

.mk-band-void {
  --v-ground: #0b0d10;
  --v-panel: rgba(17, 21, 25, 0.72);
  --v-edge: rgba(255, 255, 255, 0.09);
  --v-ink: #eef3f0;
  --v-dim: #a2aca7;
  --v-faint: #66706c;
  --v-emerald: #00e59b;
  --v-purple: #a463ff;
  --v-cyan: #2bd9ff;
  --v-coral: #ff6b57;
  /* The theme's own names, re-pointed, so the components on the stage need no dark variant. */
  --text-primary: var(--v-ink);
  --text-secondary: var(--v-dim);
  --text-muted: var(--v-faint);
  --color-border: var(--v-edge);
  --mk-signal: var(--v-emerald);
  position: relative;
  isolation: isolate;
  overflow: hidden;
  color: var(--v-ink);
  background-color: var(--v-ground);
  /* The infinite grid: two hairline families, drawn, so it costs no asset. */
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.045) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.045) 1px, transparent 1px),
    linear-gradient(rgba(255, 255, 255, 0.02) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.02) 1px, transparent 1px);
  background-size: 96px 96px, 96px 96px, 24px 24px, 24px 24px;
  background-position: -1px -1px;
}

/* Grain, generated in place: fractal noise in an inline SVG, so nothing is fetched. */
.mk-band-void::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: 0.09;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
}

/* The spotlight: follows the pointer (`--mx`, `--my`, set by the console), resting top right. */
.mk-band-void::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(
    520px circle at var(--mx, 72%) var(--my, 30%),
    rgba(0, 229, 155, 0.13),
    rgba(43, 217, 255, 0.04) 45%,
    transparent 70%
  );
}

/* The hero band clears the floating nav itself, since it no longer carries `glow`. */
.mk-band-void:has(.mk-hero) {
  padding-top: calc(var(--mk-nav-clear, 72px) + clamp(16px, 3vw, 36px));
  padding-bottom: clamp(48px, 6vw, 80px);
  --mk-measure: 1280px;
}

/* --------------------------------------------------------------------- the hero on the stage */

.mk-band-void .mk-hero--horizon {
  display: block;
  text-align: left;
}

.mk-band-void .mk-hero--horizon .mk-hero-col {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  grid-template-areas:
    "eyebrow eyebrow"
    "head head"
    "lead actions"
    "lead assure";
  -moz-column-gap: clamp(32px, 5vw, 80px);
       column-gap: clamp(32px, 5vw, 80px);
  align-items: start;
  text-align: left;
}

.mk-band-void .mk-hero-eyebrow {
  grid-area: eyebrow;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0 0 var(--space-4);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0;
  text-transform: none;
  color: var(--v-emerald);
}
.mk-band-void .mk-hero-eyebrow::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: var(--radius-full);
  background: var(--v-emerald);
  box-shadow: 0 0 12px var(--v-emerald);
}

.mk-band-void .mk-headline {
  grid-area: head;
  margin: 0 0 clamp(20px, 2.6vw, 32px);
  font-size: clamp(38px, 5.7vw, 88px);
  line-height: 0.94;
  letter-spacing: -0.055em;
  font-weight: 800;
  text-wrap: initial;
  color: var(--v-ink);
}

/*
 * The headline turning over (apps/portal/app/welcome/rotating-headline.tsx). Every pair sits in one
 * grid cell so the box keeps the tallest pair's height and nothing under it moves.
 */
.mk-rotor { display: grid; }
.mk-rotor-item {
  grid-area: 1 / 1;
  transition: opacity 520ms ease, transform 520ms cubic-bezier(0.2, 0.7, 0.2, 1), filter 520ms ease;
}
/* `visibility` flips after the fade, so a faded pair is out of find-in-page and copied text too. */
.mk-rotor-item[data-on="no"] {
  opacity: 0; transform: translateY(0.18em); filter: blur(6px); visibility: hidden;
  transition-property: opacity, transform, filter, visibility; transition-delay: 0s, 0s, 0s, 520ms;
}
.mk-rotor-item[data-on="yes"] { opacity: 1; transform: none; filter: none; visibility: visible; }
@media (prefers-reduced-motion: reduce) {
  .mk-rotor-item { transition: none; }
}

/* The forecast half, drawn in outline: what has not happened yet, set as a line rather than a mass. */
.mk-outline {
  color: transparent;
  -webkit-text-stroke: 1.5px var(--v-emerald, var(--mk-signal));
  text-shadow: 0 0 40px rgba(0, 229, 155, 0.25);
}

.mk-band-void .mk-lead {
  grid-area: lead;
  max-width: 58ch;
  margin: 0;
  font-size: clamp(15px, 1.25vw, 18px);
  line-height: 1.6;
  text-align: left;
}

.mk-band-void .mk-hero-actions {
  grid-area: actions;
  justify-content: flex-start;
  margin: 0 0 var(--space-4);
}

.mk-band-void .mk-assure {
  grid-area: assure;
  justify-content: flex-start;
  gap: var(--space-2);
  margin: 0;
}
.mk-band-void .mk-assure-item {
  padding: 5px 10px;
  border: 1px solid var(--v-edge);
  border-radius: var(--radius-full);
  background: rgba(255, 255, 255, 0.03);
  font-size: var(--text-xs);
  color: var(--v-dim);
}
.mk-band-void .mk-assure-item::before {
  background: var(--v-emerald);
  box-shadow: 0 0 8px var(--v-emerald);
}

.mk-band-void .mk-hero-figure {
  margin-top: clamp(20px, 2.6vw, 36px);
}

/* The calls to action, on the stage: emerald, and a ghost that is glass. */
.mk-band-void .mk-cta-primary,
.mk-band-void .mk-cta-primary.mk-cta-lg {
  background: var(--v-emerald);
  border-color: var(--v-emerald);
  color: #03130c;
  box-shadow: 0 0 0 1px rgba(0, 229, 155, 0.4), 0 10px 40px -10px rgba(0, 229, 155, 0.7);
}
.mk-band-void .mk-cta-primary:hover,
.mk-band-void .mk-cta-primary.mk-cta-lg:hover {
  background: #3dffbf;
  border-color: #3dffbf;
}
.mk-band-void .mk-cta-ghost,
.mk-band-void .mk-cta-ghost.mk-cta-lg {
  background: rgba(255, 255, 255, 0.04);
  border-color: var(--v-edge);
  color: var(--v-ink);
  backdrop-filter: blur(10px);
}
.mk-band-void .mk-cta-ghost:hover,
.mk-band-void .mk-cta-ghost.mk-cta-lg:hover {
  border-color: rgba(0, 229, 155, 0.6);
  color: var(--v-ink);
}

@media (max-width: 900px) {
  .mk-band-void .mk-hero--horizon .mk-hero-col {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "eyebrow" "head" "lead" "actions" "assure";
  }
  .mk-band-void .mk-lead { margin-bottom: var(--space-5); }
}

/* --------------------------------------------------------------------------- the console */

.mk-con {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: clamp(20px, 3vw, 40px);
  align-items: stretch;
}

.mk-term {
  display: flex;
  flex-direction: column;
  min-width: 0;
  border: 1px solid var(--v-edge);
  border-radius: 16px;
  background: var(--v-panel);
  backdrop-filter: blur(16px) saturate(140%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.06),
    0 40px 90px -40px rgba(0, 229, 155, 0.35),
    0 20px 50px -20px rgba(0, 0, 0, 0.6);
  overflow: hidden;
  cursor: text;
}

.mk-term-bar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 10px 14px;
  border-bottom: 1px solid var(--v-edge);
  background: rgba(255, 255, 255, 0.02);
}
.mk-term-dots { display: inline-flex; gap: 6px; }
.mk-term-dots i {
  width: 10px;
  height: 10px;
  border-radius: var(--radius-full);
  background: var(--v-coral);
}
.mk-term-dots i:nth-child(2) { background: #ffc24b; }
.mk-term-dots i:nth-child(3) { background: var(--v-emerald); }
.mk-term-title {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--v-faint);
}

.mk-term-screen {
  flex: 1 1 auto;
  min-height: 220px;
  max-height: 260px;
  overflow-y: auto;
  padding: 14px 16px 6px;
  font-family: var(--font-mono);
  font-size: 13px;
  line-height: 1.6;
}

.mk-term-line { margin: 0; overflow-wrap: anywhere; }
.mk-term-cmd { margin-top: 10px; color: var(--v-ink); }
.mk-term-cmd:first-child { margin-top: 0; }
.mk-term-out { color: var(--v-dim); }
.mk-term-who { color: var(--v-cyan); opacity: 0.85; }
.mk-term-err { color: var(--v-coral); }
.mk-term-prompt { color: var(--v-emerald); font-weight: 700; }
.mk-term-link {
  color: var(--v-emerald);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.mk-term-input {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
}
.mk-term-input input {
  flex: 1 1 auto;
  min-width: 0;
  border: 0;
  outline: none;
  background: transparent;
  color: var(--v-ink);
  font: inherit;
  caret-color: var(--v-emerald);
}
.mk-term-input input::-moz-placeholder { color: var(--v-faint); }
.mk-term-input input::placeholder { color: var(--v-faint); }
.mk-term-input:focus-within .mk-term-prompt { text-shadow: 0 0 10px var(--v-emerald); }

.mk-term-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 12px 14px 14px;
  border-top: 1px solid var(--v-edge);
}
.mk-term-chip {
  padding: 5px 10px;
  border: 1px solid var(--v-edge);
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.03);
  color: var(--v-dim);
  font-family: var(--font-mono);
  font-size: 12px;
  cursor: pointer;
  transition: border-color 160ms, color 160ms, box-shadow 160ms, background 160ms;
}
.mk-term-chip:hover,
.mk-term-chip:focus-visible {
  border-color: rgba(0, 229, 155, 0.7);
  color: var(--v-ink);
  box-shadow: 0 0 18px -4px rgba(0, 229, 155, 0.6);
  outline: none;
}
.mk-term-chip.is-on {
  border-color: var(--v-emerald);
  background: rgba(0, 229, 155, 0.14);
  color: var(--v-emerald);
}

/* ----------------------------------------------------------------------------- the graph */

/* The stage is as tall as the terminal beside it; the graph scales into that, never the other way. */
.mk-fig-g-stage {
  position: relative;
  min-width: 0;
  min-height: 360px;
  perspective: 1100px;
}

.mk-fig-g-tilt {
  position: absolute;
  inset: 0;
  transform: rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
  transform-style: preserve-3d;
  transition: transform 300ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

.mk-fig-g-graph {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.mk-fig-g-ring { fill: none; stroke: rgba(255, 255, 255, 0.12); stroke-width: 1.2; stroke-dasharray: 2 7; }
.mk-fig-g-pulse {
  fill: none;
  stroke: var(--v-emerald);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-dasharray: 46 954;
  filter: drop-shadow(0 0 6px rgba(0, 229, 155, 0.9));
  animation: mk-fig-g-run 9s linear infinite;
}
.mk-fig-g-wire { stroke: rgba(255, 255, 255, 0.06); stroke-width: 1; transition: stroke 200ms; }
.mk-fig-g-wire.is-lit { stroke: rgba(0, 229, 155, 0.35); }
.mk-fig-g-sat { fill: rgba(255, 255, 255, 0.22); transition: fill 200ms; }
.mk-fig-g-sat.is-lit { fill: var(--v-emerald); }
.mk-fig-g-spoke { stroke: rgba(255, 255, 255, 0.05); stroke-width: 1; }
.mk-fig-g-spoke.is-hot { stroke: rgba(0, 229, 155, 0.55); stroke-dasharray: 4 6; animation: mk-fig-g-flow 0.9s linear infinite; }
.mk-fig-g-edge { stroke: rgba(255, 255, 255, 0.16); stroke-width: 1.5; transition: stroke 200ms; }
.mk-fig-g-edge.is-hot {
  stroke: var(--v-emerald);
  stroke-width: 2.5;
  stroke-dasharray: 8 7;
  filter: drop-shadow(0 0 5px rgba(0, 229, 155, 0.8));
  animation: mk-fig-g-flow 0.7s linear infinite;
}

.mk-fig-g-core { pointer-events: none; }
.mk-fig-g-core-halo {
  fill: rgba(0, 229, 155, 0.08);
  stroke: rgba(0, 229, 155, 0.25);
  transform-box: fill-box;
  transform-origin: center;
  animation: mk-fig-g-breathe 4s ease-in-out infinite alternate;
}
.mk-fig-g-core-disc { fill: #0f1417; stroke: var(--v-emerald); stroke-width: 1.5; }
.mk-fig-g-core-text {
  fill: var(--v-ink);
  font-family: var(--font-display, "Archivo Expanded", Geist, sans-serif);
  font-size: 13px;
  font-weight: 800;
  letter-spacing: -0.02em;
}

.mk-fig-g-node { cursor: pointer; }
.mk-fig-g-node-glow {
  fill: var(--v-emerald);
  opacity: 0;
  filter: blur(10px);
  transition: opacity 220ms;
}
.mk-fig-g-node-disc {
  fill: #12171b;
  stroke: rgba(255, 255, 255, 0.32);
  stroke-width: 1.5;
  transition: stroke 200ms, fill 200ms;
}
.mk-fig-g-node-label {
  fill: var(--v-dim);
  font-family: var(--font-sans);
  font-size: 20px;
  font-weight: 600;
  transition: fill 200ms;
}
.mk-fig-g-node-cmd {
  fill: var(--v-faint);
  font-family: var(--font-mono);
  font-size: 15px;
  font-weight: 700;
}
.mk-fig-g-node:hover .mk-fig-g-node-disc,
.mk-fig-g-node.is-hot .mk-fig-g-node-disc { stroke: var(--v-emerald); fill: rgba(0, 229, 155, 0.16); }
.mk-fig-g-node.is-hot .mk-fig-g-node-glow { opacity: 0.45; }
.mk-fig-g-node:hover .mk-fig-g-node-label,
.mk-fig-g-node.is-hot .mk-fig-g-node-label { fill: var(--v-ink); }
.mk-fig-g-node.is-hot .mk-fig-g-node-cmd { fill: var(--v-emerald); }

/* The platforms, floating at the edges of the stage. */
.mk-fig-g-tags {
  position: absolute;
  inset: 0;
  margin: 0;
  padding: 0;
  list-style: none;
  pointer-events: none;
}
.mk-fig-g-tag {
  position: absolute;
  padding: 4px 10px;
  border: 1px solid var(--v-edge);
  border-radius: var(--radius-full);
  background: rgba(17, 21, 25, 0.8);
  backdrop-filter: blur(8px);
  color: var(--v-dim);
  font-family: var(--font-mono);
  font-size: 11px;
  white-space: nowrap;
  pointer-events: auto;
  animation: mk-fig-g-float 6s ease-in-out infinite alternate;
  animation-delay: calc(var(--i) * -0.9s);
  transition: color 160ms, border-color 160ms, box-shadow 160ms;
}
.mk-fig-g-tag:hover {
  color: var(--v-cyan);
  border-color: rgba(43, 217, 255, 0.7);
  box-shadow: 0 0 20px -2px rgba(43, 217, 255, 0.55);
}
.mk-fig-g-tag:nth-child(1) { left: 2%; top: 4%; }
.mk-fig-g-tag:nth-child(2) { right: 4%; top: 2%; }
.mk-fig-g-tag:nth-child(3) { left: 0; top: 30%; }
.mk-fig-g-tag:nth-child(4) { right: 0; top: 26%; }
.mk-fig-g-tag:nth-child(5) { left: 3%; top: 58%; }
.mk-fig-g-tag:nth-child(6) { right: 2%; top: 56%; }
.mk-fig-g-tag:nth-child(7) { left: 8%; bottom: 6%; }
.mk-fig-g-tag:nth-child(8) { right: 10%; bottom: 4%; }
.mk-fig-g-tag:nth-child(9) { left: 34%; bottom: -2%; }
.mk-fig-g-tag:nth-child(10) { left: 40%; top: -2%; }

@keyframes mk-fig-g-run { from { stroke-dashoffset: 1000; } to { stroke-dashoffset: 0; } }
@keyframes mk-fig-g-flow { from { stroke-dashoffset: 15; } to { stroke-dashoffset: 0; } }
@keyframes mk-fig-g-breathe { from { transform: scale(0.94); opacity: 0.7; } to { transform: scale(1.08); opacity: 1; } }
@keyframes mk-fig-g-float { from { transform: translateY(-5px); } to { transform: translateY(6px); } }

@media (max-width: 980px) {
  .mk-con { grid-template-columns: minmax(0, 1fr); }
  .mk-fig-g-stage { min-height: 360px; }
  /* Narrow: the tags leave the graph's edges, where they landed on its nodes, for a row beneath it. */
  .mk-fig-g-stage { display: flex; flex-direction: column; }
  .mk-fig-g-tilt { position: relative; inset: auto; height: 340px; }
  .mk-fig-g-tags {
    position: static;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 6px;
    margin-top: var(--space-4);
  }
  .mk-fig-g-tag { position: static; animation: none; }
}

/* -------------------------------------------------------------------------------- the canvas */

.mk-play {
  position: relative;
}

.mk-play-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "heading heading" "lead hint";
  align-items: end;
  gap: var(--space-3) var(--space-6);
  margin-bottom: clamp(24px, 3vw, 40px);
}

/* Oversized and heavy: the band's one statement, set big enough to be part of the canvas. */
.mk-play-heading {
  grid-area: heading;
  margin: 0;
  font-family: var(--font-display, "Archivo Expanded", Geist, sans-serif);
  font-size: clamp(44px, 7.4vw, 120px);
  line-height: 0.9;
  letter-spacing: -0.06em;
  font-weight: 800;
  color: var(--v-ink);
}
.mk-play-lead { grid-area: lead; margin: 0; max-width: 52ch; }
.mk-play-hint {
  grid-area: hint;
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--v-emerald);
}

.mk-play-board {
  position: relative;
  height: clamp(560px, 46vw, 640px);
  border: 1px dashed rgba(255, 255, 255, 0.12);
  border-radius: 4px;
}

.mk-play-card {
  --tone: var(--v-emerald);
  position: absolute;
  left: calc(var(--ax) * 100%);
  top: calc(var(--ay) * 100%);
  display: grid;
  gap: 10px;
  width: clamp(240px, 23vw, 300px);
  padding: 20px 20px 18px;
  border: 2px solid var(--tone);
  border-radius: 6px;
  background: #13171b;
  box-shadow: 8px 8px 0 var(--tone);
  color: var(--v-ink);
  text-decoration: none;
  cursor: grab;
  -webkit-user-select: none;
     -moz-user-select: none;
          user-select: none;
  touch-action: none;
  transform: translate(var(--dx, 0px), var(--dy, 0px)) rotate(var(--tilt, 0deg)) scale(var(--lift, 1));
  transition: box-shadow 160ms, scale 160ms;
}
.mk-play-card:hover { box-shadow: 11px 11px 0 var(--tone); }
.mk-play-card:focus-visible { outline: 3px solid var(--v-ink); outline-offset: 4px; }
.mk-play-card.is-held {
  --tilt: 0deg;
  --lift: 1.04;
  cursor: grabbing;
  box-shadow: 16px 16px 0 var(--tone);
}
.mk-play-purple { --tone: var(--v-purple); }
.mk-play-cyan { --tone: var(--v-cyan); }
.mk-play-coral { --tone: var(--v-coral); }
.mk-play-emerald { --tone: var(--v-emerald); }

.mk-play-card-title {
  font-family: var(--font-display, "Archivo Expanded", Geist, sans-serif);
  font-size: 19px;
  line-height: 1.12;
  font-weight: 800;
  letter-spacing: -0.03em;
}
.mk-play-card-body { font-size: var(--text-sm); line-height: 1.5; color: var(--v-dim); }
.mk-play-card-more {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--tone);
}

@media (max-width: 760px) {
  .mk-play-head { grid-template-columns: minmax(0, 1fr); grid-template-areas: "heading" "lead"; }
  .mk-play-hint { display: none; }
  .mk-play-board { height: auto; display: grid; gap: 22px; border: 0; }
  .mk-play-card { position: relative; left: auto; top: auto; width: auto; transform: none; cursor: default; }
}

/* ------------------------------------------------------------------------- magnetic buttons */

.mk-magnet {
  display: inline-flex;
  transform: translate(var(--mgx, 0px), var(--mgy, 0px));
  transition: transform 280ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

/* ---------------------------------------------------------------------- the floating panel */

.mk-dock {
  position: fixed;
  right: clamp(14px, 2vw, 28px);
  bottom: clamp(14px, 2vw, 28px);
  z-index: 60;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 14px;
  transition: transform 320ms cubic-bezier(0.2, 0.8, 0.2, 1), opacity 240ms;
}
.mk-dock.is-away:not(.is-open) {
  transform: translateY(140%);
  opacity: 0;
  pointer-events: none;
}

.mk-dock-button {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 12px 18px;
  border: 2px solid #03130c;
  border-radius: 10px;
  background: #00e59b;
  color: #03130c;
  font: inherit;
  font-weight: 700;
  font-size: var(--text-sm);
  box-shadow: 5px 5px 0 #a463ff;
  cursor: pointer;
  transition: transform 160ms, box-shadow 160ms;
}
.mk-dock-button:hover { transform: translate(-2px, -2px); box-shadow: 7px 7px 0 #a463ff; }
.mk-dock-button:active { transform: translate(3px, 3px); box-shadow: 2px 2px 0 #a463ff; }
.mk-dock-button:focus-visible { outline: 3px solid #2bd9ff; outline-offset: 3px; }
.mk-dock-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #03130c;
}

.mk-dock-panel {
  display: grid;
  gap: 8px;
  width: min(320px, calc(100vw - 28px));
  padding: 18px;
  border: 2px solid #eef3f0;
  border-radius: 10px;
  background: #111418;
  color: #eef3f0;
  box-shadow: 8px 8px 0 #2bd9ff;
  transform-origin: bottom right;
  transition: transform 380ms cubic-bezier(0.34, 1.56, 0.64, 1), opacity 200ms, visibility 0s linear 380ms;
}
.mk-dock:not(.is-open) .mk-dock-panel {
  opacity: 0;
  visibility: hidden;
  transform: translateY(16px) scale(0.86);
}
.mk-dock.is-open .mk-dock-panel { transition-delay: 0s; }

.mk-dock-title {
  margin: 0;
  font-family: var(--font-display, "Archivo Expanded", Geist, sans-serif);
  font-size: 18px;
  font-weight: 800;
  letter-spacing: -0.03em;
}
.mk-dock-note { margin: 0 0 6px; font-size: var(--text-sm); line-height: 1.5; color: #a2aca7; }
.mk-dock-row {
  display: block;
  padding: 10px 12px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.03);
  color: #eef3f0;
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: border-color 160ms, background 160ms;
}
.mk-dock-row:hover,
.mk-dock-row:focus-visible { border-color: #00e59b; background: rgba(0, 229, 155, 0.08); outline: none; }
.mk-dock-row--primary { background: #00e59b; border-color: #00e59b; color: #03130c; }
.mk-dock-row--primary:hover,
.mk-dock-row--primary:focus-visible { background: #3dffbf; color: #03130c; }

/* ---------------------------------------------------------------------- oversized headings */

/* The band headings, big and heavy in the display face — the canvas's voice, carried down the page. */
.mk-heading {
  font-family: var(--font-display, "Archivo Expanded", Geist, sans-serif);
  font-size: clamp(32px, 4.4vw, 64px);
  line-height: 0.98;
  letter-spacing: -0.05em;
  font-weight: 800;
}

/* ------------------------------------------------------------------------------ reduced motion */

@media (prefers-reduced-motion: reduce) {
  .mk-fig-g-pulse,
  .mk-fig-g-edge.is-hot,
  .mk-fig-g-spoke.is-hot,
  .mk-fig-g-core-halo,
  .mk-fig-g-tag { animation: none; }
  .mk-fig-g-tilt, .mk-magnet, .mk-dock, .mk-dock-panel { transition: none; }
  .mk-band-void::after { background: none; }
}

/* ------------------------------------------------------------------------ the nav, on the stage */

/*
 * The nav and the bar above it are dark glass in both themes. They sit on the stage first, and a
 * pale pill over a charcoal grid read as a different site; over the light bands further down a dark
 * bar is the ordinary sticky header. The theme's names are re-pointed here, as on the stage, so the
 * nav's own rules need no second set.
 */
.mk-nav,
.mk-announce-bar {
  --text-primary: #eef3f0;
  --text-secondary: #a2aca7;
  --text-muted: #66706c;
  --color-border: rgba(255, 255, 255, 0.1);
  --bg-page: #0b0d10;
  --bg-sunken: #14181c;
  --accent: #00e59b;
  --accent-hover: #3dffbf;
  --on-accent: #03130c;
  --bg-hover: rgba(255, 255, 255, 0.08);
  /* The global `a:hover` colour. It is `var(--text-primary)` resolved on :root, so it arrives here as
     the light theme's dark ink whatever `--text-primary` says; it has to be named again to follow. */
  --hover-accent-text: #eef3f0;
}
/* The wordmark link inherits its colour, and what it inherited was the light theme's page ink. */
.mk-nav {
  color: var(--text-primary);
}
/* The global `a:hover` (0,1,1) outranks `.mk-wordmark-link:hover`'s colour and adds an underline;
   on the light roll that turned the name into dark ink on the dark pill — gone under the pointer. */
.mk-nav a.mk-wordmark-link:hover {
  color: var(--text-primary);
  text-decoration: none;
  opacity: 1;
  text-shadow: 0 0 18px rgba(0, 229, 155, 0.55);
}
.mk-announce-bar {
  background: #07090b;
  color: var(--text-secondary);
  border-bottom: 1px solid var(--color-border);
}
/* No backdrop-filter here: it makes the pill the containing block for the absolutely positioned mega
   menu, which then opened at the pill's width instead of the page's. Near-opaque instead. */
.mk-nav-pill {
  background: rgba(20, 24, 28, 0.94);
}

/* ==========================================================================
   The whole page on the stage.

   The hero and the canvas were a dark console and everything after them reverted to pale bands, so
   the page read as two sites stitched at the canvas's lower edge. Every band now stands on the same
   charcoal, and the sections take the stage's vocabulary: an emerald rail for the loop, coral for
   the hosted alternative, glass for the controls, offset shadows for the cards a reader picks
   between. The theme's own names are re-pointed once, on the page, so the components below — the
   enquiry form's fields included — follow without a dark variant of their own.
   ========================================================================== */

.mk-page {
  --text-primary: #eef3f0;
  --text-secondary: #a2aca7;
  --text-muted: #6f7975;
  --color-border: rgba(255, 255, 255, 0.09);
  --color-border-strong: rgba(255, 255, 255, 0.18);
  --bg-page: #0b0d10;
  --bg-surface: #0f1215;
  --color-surface: #13171b;
  --bg-sunken: #0d1013;
  --bg-hover: rgba(255, 255, 255, 0.06);
  /* The global scrollbar thumb's colour, so every scroller inside the page — the terminal, the table,
     the menus — gets a dark one rather than the light theme's grey. */
  --border-strong: rgba(255, 255, 255, 0.14);
  --accent: #00e59b;
  --accent-hover: #3dffbf;
  --accent-soft: rgba(0, 229, 155, 0.08);
  --accent-text: #00e59b;
  --on-accent: #03130c;
  --focus-ring: #2bd9ff;
  /* The global `a:hover` colour; see the note on `.mk-nav` above. */
  --hover-accent-text: #eef3f0;
  /* The form controls' own names: `Input`, `Select`, `Textarea` and `Button` style themselves inline. */
  --color-background: #0d1013;
  --color-text: #eef3f0;
  --color-primary-solid: #00e59b;
  --color-on-primary: #03130c;
  --v-emerald: #00e59b;
  --v-purple: #a463ff;
  --v-cyan: #2bd9ff;
  --v-coral: #ff6b57;
  --v-ink: #eef3f0;
  --v-dim: #a2aca7;
  background: #0b0d10;
  color: var(--text-primary);
  color-scheme: dark;
}
.mk-page .mk-band { --mk-signal: #00e59b; --mk-edge: rgba(255, 255, 255, 0.06); }

/* The grounds: the page's charcoal with the grid faintly through it, and a lifted charcoal between. */
.mk-page .mk-band-page {
  background-color: #0b0d10;
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.025) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.025) 1px, transparent 1px);
  background-size: 96px 96px;
}
.mk-page .mk-band-surface {
  background: #0e1114;
  border-top: 1px solid rgba(255, 255, 255, 0.05);
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}

/* -------------------------------------------------------------------- the loop, as a lit rail */

#operate .mk-hl-list {
  position: relative;
  border-top: 0;
}
#operate .mk-hl-list::before {
  content: "";
  position: absolute;
  left: 13px;
  top: 34px;
  bottom: 34px;
  width: 2px;
  background: linear-gradient(180deg, #2bd9ff, #00e59b 45%, #eef3f0 70%, #00e59b);
  box-shadow: 0 0 14px rgba(0, 229, 155, 0.55);
  opacity: 0.7;
}
#operate .mk-hl-item { border-bottom: 0; padding: var(--space-4) 0; }
#operate .mk-hl-index {
  position: relative;
  z-index: 1;
  background: #0b0d10;
  box-shadow: 0 0 16px -2px var(--step);
}
#operate-warned .mk-hl-index,
#operate-investigate .mk-hl-index,
#operate-fix .mk-hl-index { --step: #2bd9ff; }
#operate-approve .mk-hl-index,
#operate-run .mk-hl-index { --step: #eef3f0; }
#operate-verify .mk-hl-index { --step: #00e59b; }
#operate .mk-hl-contrast {
  padding: 12px 14px;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.025);
  color: var(--text-secondary);
}
#operate .mk-hl-contrast-label { color: #00e59b; }

/* --------------------------------------------------------------- the five differences, in two hues */

#why .mk-hl-index {
  color: #00e59b;
  font-family: var(--font-display, "Archivo Expanded", Geist, sans-serif);
  font-size: 22px;
  font-weight: 800;
}
#why .mk-hl-contrast {
  padding: 12px 14px;
  border-left: 2px solid #ff6b57;
  background: rgba(255, 107, 87, 0.05);
  border-radius: 0 10px 10px 0;
  color: var(--text-secondary);
}
#why .mk-hl-contrast-label { color: #ff6b57; }

/* ------------------------------------------------------------------- the controls, as glass */

.mk-page .mk-sec-card {
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 16px;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0.01));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
  transition: border-color 200ms, transform 200ms, box-shadow 200ms;
}
.mk-page .mk-sec-card:hover {
  border-color: rgba(0, 229, 155, 0.45);
  transform: translateY(-3px);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06), 0 22px 50px -24px rgba(0, 229, 155, 0.45);
}
.mk-page .mk-sec-mech-name { color: #00e59b; }
/* The file that makes it true, as the line you would type to go and look. */
.mk-page .mk-sec-where { color: #00e59b; }
.mk-page .mk-sec-where::before { content: "$ open "; color: #6f7975; }

/* ------------------------------------------------------------------ collection and the pane */

.mk-page .mk-pane-card,
.mk-page .mk-panel {
  background: rgba(17, 21, 25, 0.8);
  border-color: rgba(255, 255, 255, 0.1);
  box-shadow: 0 30px 70px -30px rgba(0, 229, 155, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.05);
}

/* ------------------------------------------------------------------------- the comparison */

.mk-page .mk-cmp-scroll {
  background: #0f1215;
  border-color: rgba(255, 255, 255, 0.1);
}
.mk-page .mk-cmp-ours {
  background: rgba(0, 229, 155, 0.07);
  color: var(--text-primary);
}
.mk-page .mk-cmp-table thead .mk-cmp-ours { color: #00e59b; }
.mk-page .mk-cmp-table thead .mk-cmp-theirs { color: #ff6b57; }
.mk-page .mk-cmp-theirs { color: var(--text-secondary); }

/* ------------------------------------------------------ how it works and the plans, as cards */

.mk-page #how-it-works .mk-feature,
.mk-page .mk-plan {
  --tone: #00e59b;
  border: 2px solid var(--tone);
  border-radius: 8px;
  background: #13171b;
  box-shadow: 8px 8px 0 var(--tone);
  transition: transform 180ms, box-shadow 180ms;
}
.mk-page #how-it-works .mk-feature:hover,
.mk-page .mk-plan:hover {
  transform: translate(-3px, -3px);
  box-shadow: 11px 11px 0 var(--tone);
}
.mk-page #how-it-works .mk-feature:nth-child(1),
.mk-page .mk-plan:nth-child(1) { --tone: #2bd9ff; }
.mk-page #how-it-works .mk-feature:nth-child(2),
.mk-page .mk-plan:nth-child(2) { --tone: #00e59b; }
.mk-page #how-it-works .mk-feature:nth-child(3),
.mk-page .mk-plan:nth-child(3) { --tone: #a463ff; }
.mk-page #how-it-works .mk-feature-title {
  font-family: var(--font-display, "Archivo Expanded", Geist, sans-serif);
  font-size: 18px;
  font-weight: 800;
  letter-spacing: -0.03em;
}
.mk-page .mk-plan-name {
  font-family: var(--font-display, "Archivo Expanded", Geist, sans-serif);
  font-weight: 800;
  letter-spacing: -0.04em;
}
.mk-page .mk-plan-ask { color: var(--tone); }

/* ------------------------------------------------------------------------------ the tour */

.mk-page .mk-tour-info {
  --mk-paper: #13171b;
  --mk-paper-ink: #eef3f0;
  --mk-paper-dim: #a2aca7;
  --mk-paper-rule: rgba(255, 255, 255, 0.1);
}

/* ------------------------------------------------------------------------- the questions */

/* The heading shares a third of the row with nothing else, so it is set to the column, not the page. */
.mk-page .mk-faq-head .mk-heading { font-size: clamp(30px, 3.4vw, 48px); line-height: 1.02; }
.mk-page .mk-faq-item[open] .mk-faq-q { color: #00e59b; }
.mk-page .mk-faq-chev { color: #00e59b; }

/* ------------------------------------------------------------------------------- the form */

.mk-page #talk-to-us .mk-measure-narrow {
  padding: clamp(24px, 4vw, 48px);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 20px;
  background: rgba(17, 21, 25, 0.85);
  box-shadow: 0 40px 100px -40px rgba(0, 229, 155, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

/* ------------------------------------------------------------------------------ the footer */

.mk-page .mk-footer {
  background: #07090b;
  border-top: 1px solid rgba(255, 255, 255, 0.06);
  overflow: hidden;
}
.mk-footer-giant {
  margin: clamp(24px, 4vw, 56px) 0 -0.16em;
  font-family: var(--font-display, "Archivo Expanded", Geist, sans-serif);
  font-size: clamp(88px, 21vw, 360px);
  font-weight: 800;
  line-height: 0.8;
  letter-spacing: -0.07em;
  text-align: center;
  white-space: nowrap;
  color: transparent;
  -webkit-text-stroke: 1px rgba(0, 229, 155, 0.28);
  -webkit-user-select: none;
     -moz-user-select: none;
          user-select: none;
}

/* The trust row, on the stage. */
.mk-page .mk-trust-cell b { color: var(--text-primary); }

/* ---------------------------------------------------------------------- the page's scrollbar */

/*
 * Hidden on the front door, at the user's request: a bar down the right edge cut through the stage,
 * and a dimmed one was still a grey stripe there. The page still scrolls by wheel, trackpad, keyboard
 * and touch — only the bar is gone. The scrollers inside the page keep theirs: the terminal and the
 * comparison table are boxes whose edge a reader needs to see.
 *
 * `<html>` is the scroller, and it sits above `.mk-page`, hence `:has`.
 *
 * The standard property, unconditionally — the one place on the surface it is set outside the
 * `@supports` split `styles.css` keeps. That split exists because `scrollbar-width` moves Blink onto
 * its standard bar, with stepper arrows, and off the styled pseudo-elements; a bar of width `none`
 * has no arrows to draw, so the reason does not apply. And the pseudo-element route does not work
 * here: Chrome 154 ignored `::-webkit-scrollbar { display: none }` on the root, even under a plain
 * class selector, and kept reserving ten pixels for the global rule's bar. Measured, not assumed.
 */
html:has(.mk-page) {
  background: #0b0d10;
  scrollbar-width: none;
}

.mk-page *::-webkit-scrollbar-thumb:hover {
  background-color: rgba(0, 229, 155, 0.7);
}

/*!************************************************************************************************************************************************************************************************************************************************************************************!*\
  !*** css ../../node_modules/next/dist/build/webpack/loaders/css-loader/src/index.js??ruleSet[1].rules[14].oneOf[12].use[2]!../../node_modules/next/dist/build/webpack/loaders/postcss-loader/src/index.js??ruleSet[1].rules[14].oneOf[12].use[3]!../../packages/ui/src/styles.css ***!
  \************************************************************************************************************************************************************************************************************************************************************************************/
/*
 * Global stylesheet.
 *
 * Every value in the token blocks below is generated from `@platform/config/tokens` and asserted by
 * `packages/config/src/tokens.test.ts` — edit one file without the other and the suite fails. Do not
 * hand-tune a hex here; change the token.
 *
 * The direction is `repo/claude/28-design.md` v3: a dark, quiet console. **Dark is the only
 * default** — bare `:root` carries the dark palette, and light exists solely behind an explicit
 * `[data-theme="light"]` choice. There is deliberately no `prefers-color-scheme` block: v3 says
 * "no light theme by default", so a first visit is dark regardless of the OS setting, and the
 * pre-hydration script in each app's layout applies a stored choice before first paint.
 *
 * ## This file was inverted, again
 *
 * The previous direction was light-first with card shadows, and the one before that was dark and
 * hairline-bordered. v3 lands close to the first with the lessons of the second: separation is a
 * single 1px `--border-hairline` and whitespace — **never a shadow between adjacent regions**
 * (blacklist 2) — while overlays (modal, toast, tooltip) keep `--elev-overlay`, because a float is
 * not adjacent-region separation. `--elev-rest`/`--elev-hover` still exist and are `none`, so a
 * component reading them is harmless and the reverse-drift test keeps working.
 *
 * Three rules enforced structurally rather than by discipline:
 *
 *   - **A card is a hairline, not a shadow.** `.card` carries a 1px border and no box-shadow;
 *     `tokens.test.ts` reads the rule and checks both halves.
 *   - **A nested surface gets no lift of its own.** A chart inside a card, a code block inside a
 *     panel, a progress track: `--bg-sunken`, flat.
 *   - **Nothing auto-animates in a loop.** The shimmer, the spinner and the dot on a live pill are
 *     the exceptions, and all three are load-bearing: they are the thing telling you something is
 *     still in flight.
 */

/*
 * The workstation token system, kept in its own file because it is a whole second design system
 * living beside this one during the migration. `@import` rather than a second entry point every
 * app has to remember: a consumer that pulls in styles.css and forgets the tokens gets a frame
 * with no palette, which is the kind of half-applied change nothing would catch.
 *
 * It must stay above every rule below it — CSS allows @import only before other rules.
 */
/*
 * The marketing surface, in its own file for the same reason as the two above and one more of its
 * own: `marketing.tsx` shipped writing `mk-*` class names that nothing defined anywhere, so the
 * public front door served as unstyled text while typechecking, passing its tests and returning 200.
 * Keeping the fix here rather than adding eight hundred lines to this file keeps it out of the
 * twenty-thousand-line stylesheet that concurrent sessions edit — and the surface really is
 * separate: two routes use it and no product page does.
 */

/*
 * Geist, actually loaded, product-wide (sidebar included).
 *
 * Self-hosted variable-weight woff2 (fonts/, OFL — licence sits beside it) covers 100–900.
 * `font-display: swap` because a flash of fallback text beats a flash of nothing.
 */
@font-face {
  font-family: "Geist";
  src: url(/customerportal/_next/static/media/Geist-Variable.0b78ff37.woff2) format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

/*
 * The three faces the design references name, self-hosted for the same reason Geist is.
 *
 * The reference files pull these from `fonts.googleapis.com` with a `<link>`. That works on every
 * developer machine and fails on the one deployment that matters: this product ships into
 * disconnected OpenShift clusters, where an external stylesheet resolves to nothing, no error
 * reaches a log the customer reads, and the page simply renders in the fallback. It is trap 26 in
 * CLAUDE.md — `@monaco-editor/react`'s CDN default left the editor pane blank forever in exactly
 * that environment. A typeface degrades more gently than an editor, but silently and product-wide.
 *
 * Variable woff2 from Google Fonts, latin and latin-ext only. All three are SIL OFL; the licence
 * sits beside them. 436 KB total, which is the price of the design working where it is installed.
 */

@font-face {
  font-family: "Inter";
  src: url(/customerportal/_next/static/media/Inter-Variable-latin.6c596dfc.woff2) format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter";
  src: url(/customerportal/_next/static/media/Inter-Variable-latin-ext.3835a68e.woff2) format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "JetBrains Mono";
  src: url(/customerportal/_next/static/media/JetBrainsMono-Variable-latin.a865edea.woff2) format("woff2");
  font-weight: 100 800;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url(/customerportal/_next/static/media/JetBrainsMono-Variable-latin-ext.9c9751ca.woff2) format("woff2");
  font-weight: 100 800;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/*
 * "Expanded" is a width, not a family — Archivo is one variable font with a `wdth` axis running
 * 62.5 to 125, and the references ask for the top of it.
 *
 * Pinned with `font-stretch: 125%` **as an @font-face descriptor**, which is the part worth
 * knowing: on a face, that declares the width this file is being registered for, so a consumer
 * asking for ordinary `normal` (100%) is clamped to the nearest available and gets 125. The
 * alternative is `font-variation-settings` on every element that uses the family, which every new
 * caller has to remember and none of them will.
 */

@font-face {
  font-family: "Archivo Expanded";
  src: url(/customerportal/_next/static/media/Archivo-Variable-latin.8eca89c0.woff2) format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-stretch: 125%;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Archivo Expanded";
  src: url(/customerportal/_next/static/media/Archivo-Variable-latin-ext.33cc89f0.woff2) format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-stretch: 125%;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-10: 40px;
  --space-12: 48px;
  --space-16: 64px;
  --radius-sm: 10px;
  --radius-md: 12px;
  --radius-lg: 14px;
  --radius-xl: 16px;
  --radius-card: 14px;
  --radius-full: 9999px;
  --text-xs: 11px;
  --text-sm: 12px;
  --text-base: 13px;
  --text-md: 14px;
  --text-lg: 15px;
  --text-xl: 18px;
  --text-2xl: 28px;
  --text-3xl: 32px;
  --size-row: 40px;
  --size-row-compact: 34px;
  --size-control: 34px;
  --size-control-sm: 28px;
  --size-control-coarse: 44px;
  --size-control-sm-coarse: 36px;
  --size-topbar: 52px;
  --size-nav: 220px;
  --size-nav-rail: 56px;
  --size-slideover: 480px;
  --size-toast: 320px;
  --size-card-icon: 26px;
  --size-bar: 6px;
  --leading-hero: 1.15;
  --leading-heading: 1.25;
  --leading-body: 1.5;
  --leading-relaxed: 1.6;
  --font-sans: Geist, 'Geist Sans', Inter, 'Inter var', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --font-mono: 'JetBrains Mono', 'Geist Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
  --motion-fast: 150ms;
  --motion-base: 200ms;
  --motion-slow: 250ms;
  --ease-out: cubic-bezier(0.2, 0, 0, 1);
  --z-raised: 10;
  --z-sticky: 20;
  --z-overlay: 40;
  --z-modal: 100;
  /* The bastion panel a command opens from any page — above a dialog, because it is opened from one. */
  --z-shell: 110;
  --z-toast: 1000;

  /*
   * The dark palette IS the default palette. Not a media-query override, not a variant: v3's
   * "dark is the only default". The light block at the bottom of this region is reachable only
   * through the theme toggle.
   */
  --bg-page: #111315;
  --bg-surface: #22262B;
  --bg-sunken: #1A1D21;
  --bg-hover: #2B3036;
  --border-hairline: #383E45;
  --border-strong: #4B525B;
  --text-primary: #F5F7FA;
  --text-secondary: #AEB5BE;
  --text-muted: #9097A1;
  --accent: #F5F7FA;
  --accent-hover: #FFFFFF;
  --accent-soft: #2B3036;
  --accent-text: #F5F7FA;
  --accent-solid: #F5F7FA;
  --on-accent: #111315;
  --focus-ring: #8E9093;
  --scrim: rgba(10, 11, 13, 0.62);
  --grid: rgba(245, 247, 250, 0.05);
  --chart-fill: rgba(245, 247, 250, 0.10);
  --brand: #EE0000;
  --brand-deep: #A30000;
  --elev-rest: none;
  --elev-hover: none;
  --elev-overlay: 0 8px 32px rgba(0, 0, 0, 0.6);
  --ok: #3FB950;
  --ok-soft: rgba(63, 185, 80, 0.28);
  --ok-text: #62DF70;
  --info: #58A6FF;
  --info-soft: rgba(88, 166, 255, 0.28);
  --info-text: #89D0FF;
  --warn: #D29922;
  --warn-soft: rgba(210, 153, 34, 0.28);
  --warn-text: #F1C14F;
  --critical: #F85149;
  --critical-soft: rgba(248, 81, 73, 0.28);
  --critical-text: #FFA198;
  /* v4: hover is a grayscale response — brighter text, stronger border, raised fill. The old
     values pointed all three at --critical, which washed every hover in the product red: a chrome
     hue (blacklist 1), and one that made a *real* critical state harder to spot, because the eye
     had been taught that red means "the pointer is here". */
  --hover-accent: var(--border-strong);
  --hover-accent-text: var(--text-primary);
  --hover-accent-soft: var(--bg-hover);
  --color-background: var(--bg-page);
  --color-surface: var(--bg-surface);
  --color-surface-raised: var(--bg-surface);
  --color-sunken: var(--bg-sunken);
  --color-hover: var(--bg-hover);
  --color-border: var(--border-hairline);
  --color-border-strong: var(--border-strong);
  --color-text: var(--text-primary);
  --color-text-muted: var(--text-secondary);
  --color-text-subtle: var(--text-muted);
  --color-primary: var(--accent);
  --color-primary-hover: var(--accent-hover);
  --color-primary-soft: var(--accent-soft);
  --color-primary-text: var(--accent-text);
  --color-primary-solid: var(--accent-solid);
  --color-on-primary: var(--on-accent);
  --color-focus-ring: var(--focus-ring);
  --color-scrim: var(--scrim);
  --color-grid: var(--grid);
  --color-chart-fill: var(--chart-fill);
  --color-brand: var(--brand);
  --color-brand-deep: var(--brand-deep);
  --color-accent2: var(--ok);
  --elev-card: var(--elev-rest);
  --elev-dropdown: var(--elev-overlay);
  --elev-modal: var(--elev-overlay);
  --severity-ok-fg: var(--ok-text);
  --severity-ok-bg: var(--ok-soft);
  --severity-ok-on: var(--ok-text);
  --severity-ok-fill: var(--ok);
  --severity-info-fg: var(--info-text);
  --severity-info-bg: var(--info-soft);
  --severity-info-on: var(--info-text);
  --severity-info-fill: var(--info);
  --severity-warning-fg: var(--warn-text);
  --severity-warning-bg: var(--warn-soft);
  --severity-warning-on: var(--warn-text);
  --severity-warning-fill: var(--warn);
  --severity-critical-fg: var(--critical-text);
  --severity-critical-bg: var(--critical-soft);
  --severity-critical-on: var(--critical-text);
  --severity-critical-fill: var(--critical);
  --status-ready-fg: var(--ok-text);
  --status-ready-bg: var(--ok-soft);
  --status-ready-fill: var(--ok);
  --status-succeeded-fg: var(--ok-text);
  --status-succeeded-bg: var(--ok-soft);
  --status-succeeded-fill: var(--ok);
  --status-progressing-fg: var(--info-text);
  --status-progressing-bg: var(--info-soft);
  --status-progressing-fill: var(--info);
  --status-running-fg: var(--info-text);
  --status-running-bg: var(--info-soft);
  --status-running-fill: var(--info);
  --status-pending-fg: var(--info-text);
  --status-pending-bg: var(--info-soft);
  --status-pending-fill: var(--info);
  --status-degraded-fg: var(--warn-text);
  --status-degraded-bg: var(--warn-soft);
  --status-degraded-fill: var(--warn);
  --status-failed-fg: var(--critical-text);
  --status-failed-bg: var(--critical-soft);
  --status-failed-fill: var(--critical);
  --status-unknown-fg: var(--text-muted);
  --status-unknown-bg: var(--bg-sunken);
  --status-unknown-fill: var(--text-muted);
  --chart-1: #58A6FF;
  --chart-2: #3FB950;
  --chart-3: #BC8CFF;
  --chart-4: #39C5CF;
  --chart-5: #D29922;
  --chart-6: #FF7B72;

  /*
   * Two derived sizes that live here rather than in tokens.ts, because each is arithmetic over
   * another token rather than a value anyone chose. The scrollbar is styled to a known width so the
   * advice dialog can be sized to its measure exactly: 38rem of prose plus the dialog's own padding
   * plus the scrollbar that would otherwise eat into it. `measure.test.ts` pins both shapes.
   */
  --size-scrollbar: 10px;
  --size-advice-dialog: calc(38rem + 40px + var(--size-scrollbar) + 2px);

  color-scheme: dark;
}

/*
 * The explicit choice, same values as the default. Kept even though it currently duplicates bare
 * :root, because the toggle writes data-theme="dark" and the meaning of that attribute — "the user
 * chose this, stop consulting anything else" — should not silently depend on the default happening
 * to match.
 *
 * A `.force-dark` opt-in used to share this block, pinning the monitoring board dark in a light
 * portal. Removed 2026-08-18 at the product owner's direction: every surface, the graphical ones
 * included, follows the reader's theme — dark mode is the reference look, light mode adapts.
 */
:root[data-theme="dark"] {
  --bg-page: #111315;
  --bg-surface: #22262B;
  --bg-sunken: #1A1D21;
  --bg-hover: #2B3036;
  --border-hairline: #383E45;
  --border-strong: #4B525B;
  --text-primary: #F5F7FA;
  --text-secondary: #AEB5BE;
  --text-muted: #9097A1;
  --accent: #F5F7FA;
  --accent-hover: #FFFFFF;
  --accent-soft: #2B3036;
  --accent-text: #F5F7FA;
  --accent-solid: #F5F7FA;
  --on-accent: #111315;
  --focus-ring: #8E9093;
  --scrim: rgba(10, 11, 13, 0.62);
  --grid: rgba(245, 247, 250, 0.05);
  --chart-fill: rgba(245, 247, 250, 0.10);
  --brand: #EE0000;
  --brand-deep: #A30000;
  --elev-rest: none;
  --elev-hover: none;
  --elev-overlay: 0 8px 32px rgba(0, 0, 0, 0.6);
  --ok: #3FB950;
  --ok-soft: rgba(63, 185, 80, 0.28);
  --ok-text: #62DF70;
  --info: #58A6FF;
  --info-soft: rgba(88, 166, 255, 0.28);
  --info-text: #89D0FF;
  --warn: #D29922;
  --warn-soft: rgba(210, 153, 34, 0.28);
  --warn-text: #F1C14F;
  --critical: #F85149;
  --critical-soft: rgba(248, 81, 73, 0.28);
  --critical-text: #FFA198;
  /* v4: hover is a grayscale response — brighter text, stronger border, raised fill. The old
     values pointed all three at --critical, which washed every hover in the product red: a chrome
     hue (blacklist 1), and one that made a *real* critical state harder to spot, because the eye
     had been taught that red means "the pointer is here". */
  --hover-accent: var(--border-strong);
  --hover-accent-text: var(--text-primary);
  --hover-accent-soft: var(--bg-hover);
  --color-background: var(--bg-page);
  --color-surface: var(--bg-surface);
  --color-surface-raised: var(--bg-surface);
  --color-sunken: var(--bg-sunken);
  --color-hover: var(--bg-hover);
  --color-border: var(--border-hairline);
  --color-border-strong: var(--border-strong);
  --color-text: var(--text-primary);
  --color-text-muted: var(--text-secondary);
  --color-text-subtle: var(--text-muted);
  --color-primary: var(--accent);
  --color-primary-hover: var(--accent-hover);
  --color-primary-soft: var(--accent-soft);
  --color-primary-text: var(--accent-text);
  --color-primary-solid: var(--accent-solid);
  --color-on-primary: var(--on-accent);
  --color-focus-ring: var(--focus-ring);
  --color-scrim: var(--scrim);
  --color-grid: var(--grid);
  --color-chart-fill: var(--chart-fill);
  --color-brand: var(--brand);
  --color-brand-deep: var(--brand-deep);
  --color-accent2: var(--ok);
  --elev-card: var(--elev-rest);
  --elev-dropdown: var(--elev-overlay);
  --elev-modal: var(--elev-overlay);
  --severity-ok-fg: var(--ok-text);
  --severity-ok-bg: var(--ok-soft);
  --severity-ok-on: var(--ok-text);
  --severity-ok-fill: var(--ok);
  --severity-info-fg: var(--info-text);
  --severity-info-bg: var(--info-soft);
  --severity-info-on: var(--info-text);
  --severity-info-fill: var(--info);
  --severity-warning-fg: var(--warn-text);
  --severity-warning-bg: var(--warn-soft);
  --severity-warning-on: var(--warn-text);
  --severity-warning-fill: var(--warn);
  --severity-critical-fg: var(--critical-text);
  --severity-critical-bg: var(--critical-soft);
  --severity-critical-on: var(--critical-text);
  --severity-critical-fill: var(--critical);
  --status-ready-fg: var(--ok-text);
  --status-ready-bg: var(--ok-soft);
  --status-ready-fill: var(--ok);
  --status-succeeded-fg: var(--ok-text);
  --status-succeeded-bg: var(--ok-soft);
  --status-succeeded-fill: var(--ok);
  --status-progressing-fg: var(--info-text);
  --status-progressing-bg: var(--info-soft);
  --status-progressing-fill: var(--info);
  --status-running-fg: var(--info-text);
  --status-running-bg: var(--info-soft);
  --status-running-fill: var(--info);
  --status-pending-fg: var(--info-text);
  --status-pending-bg: var(--info-soft);
  --status-pending-fill: var(--info);
  --status-degraded-fg: var(--warn-text);
  --status-degraded-bg: var(--warn-soft);
  --status-degraded-fill: var(--warn);
  --status-failed-fg: var(--critical-text);
  --status-failed-bg: var(--critical-soft);
  --status-failed-fill: var(--critical);
  --status-unknown-fg: var(--text-muted);
  --status-unknown-bg: var(--bg-sunken);
  --status-unknown-fill: var(--text-muted);
  --chart-1: #58A6FF;
  --chart-2: #3FB950;
  --chart-3: #BC8CFF;
  --chart-4: #39C5CF;
  --chart-5: #D29922;
  --chart-6: #FF7B72;
}

/*
 * Light, behind an explicit choice only. Derived, not co-designed (v3 blacklist 8) — these are the
 * settled values of the previous light-primary direction, kept because they measure well, with the
 * v3 accent unified across themes so charts keep their hue when the theme flips.
 */
:root[data-theme="light"] {
  /*
   * The ground is cool near-white and the surfaces above it are pure white.
   *
   * These two were inverted: `--bg-page` was #FFFFFF under a `--bg-surface` of #FAFAFA, so every
   * card in the light theme was painted a shade *darker* than the page it sat on. Elevation ran
   * backwards, which is most of why this theme read as a wireframe — a surface that recedes cannot
   * establish hierarchy, so hierarchy fell entirely to borders, and the page became boxes inside
   * boxes. See `light` in packages/config/src/tokens.ts for the value and what it costs.
   */
  --bg-page: #F6F8FA;
  --bg-surface: #FFFFFF;
  --bg-sunken: #F1F4F8;
  --bg-hover: #F1F4F8;
  --border-hairline: #DCE2E8;
  /*
   * #D4D4D8 measured 1.48:1 against a white surface — under the 1.6 floor a border has to clear to
   * read as structure, and barely apart from the hairline's 1.37. This is the border that marks a
   * focused input, an active container and the divider that has to survive a busy card, and in the
   * light theme it was doing none of that: every "strong" edge in the product was effectively the
   * quiet one. #C4C4CC measures 1.73:1 and is still well inside the quiet register.
   */
  --border-strong: #B8C1CB;
  --text-primary: #17202A;
  /*
   * Darkened from #52525B, which measured 7.03:1 at worst against 11.99:1 for the same tier in dark
   * — 1.70x apart, over the 1.6x band `tokens.test.ts` holds the fact-carrying tier to. The band
   * exists because this is exactly how the light theme went unreadable once before while every dark
   * screenshot looked fine. Raised rather than dimming dark: the weaker theme should come up.
   */
  --text-secondary: #56616D;
  --text-muted: #636D79;
  --accent: #2563EB;
  --accent-hover: #1D4ED8;
  --accent-soft: #E7EEFD;
  --accent-text: #2563EB;
  --accent-solid: #2563EB;
  --on-accent: #FFFFFF;
  --focus-ring: #2563EB;
  --scrim: rgba(23, 32, 42, 0.38);
  --grid: rgba(23, 32, 42, 0.06);
  --chart-fill: rgba(37, 99, 235, 0.08);
  --brand: #EE0000;
  --brand-deep: #A30000;
  --elev-rest: none;
  --elev-hover: none;
  --elev-overlay: 0 8px 32px rgba(0, 0, 0, 0.6);
  --ok: #35C48C;
  --ok-soft: #BFEDD8;
  --ok-text: #0A6444;
  --info: #5B8DEF;
  --info-soft: #D3E0FC;
  --info-text: #1F4FC4;
  --warn: #E8A83C;
  --warn-soft: #FFE4A8;
  --warn-text: #7A4F00;
  --critical: #F0555B;
  --critical-soft: #FBCBCC;
  --critical-text: #A81F24;
  /* v4: hover is a grayscale response — brighter text, stronger border, raised fill. The old
     values pointed all three at --critical, which washed every hover in the product red: a chrome
     hue (blacklist 1), and one that made a *real* critical state harder to spot, because the eye
     had been taught that red means "the pointer is here". */
  --hover-accent: var(--border-strong);
  --hover-accent-text: var(--text-primary);
  --hover-accent-soft: var(--bg-hover);
  --color-background: var(--bg-page);
  --color-surface: var(--bg-surface);
  --color-surface-raised: var(--bg-surface);
  --color-sunken: var(--bg-sunken);
  --color-hover: var(--bg-hover);
  --color-border: var(--border-hairline);
  --color-border-strong: var(--border-strong);
  --color-text: var(--text-primary);
  --color-text-muted: var(--text-secondary);
  --color-text-subtle: var(--text-muted);
  --color-primary: var(--accent);
  --color-primary-hover: var(--accent-hover);
  --color-primary-soft: var(--accent-soft);
  --color-primary-text: var(--accent-text);
  --color-primary-solid: var(--accent-solid);
  --color-on-primary: var(--on-accent);
  --color-focus-ring: var(--focus-ring);
  --color-scrim: var(--scrim);
  --color-grid: var(--grid);
  --color-chart-fill: var(--chart-fill);
  --color-brand: var(--brand);
  --color-brand-deep: var(--brand-deep);
  --color-accent2: var(--ok);
  --elev-card: var(--elev-rest);
  --elev-dropdown: var(--elev-overlay);
  --elev-modal: var(--elev-overlay);
  --severity-ok-fg: var(--ok-text);
  --severity-ok-bg: var(--ok-soft);
  --severity-ok-on: var(--ok-text);
  --severity-ok-fill: var(--ok);
  --severity-info-fg: var(--info-text);
  --severity-info-bg: var(--info-soft);
  --severity-info-on: var(--info-text);
  --severity-info-fill: var(--info);
  --severity-warning-fg: var(--warn-text);
  --severity-warning-bg: var(--warn-soft);
  --severity-warning-on: var(--warn-text);
  --severity-warning-fill: var(--warn);
  --severity-critical-fg: var(--critical-text);
  --severity-critical-bg: var(--critical-soft);
  --severity-critical-on: var(--critical-text);
  --severity-critical-fill: var(--critical);
  --status-ready-fg: var(--ok-text);
  --status-ready-bg: var(--ok-soft);
  --status-ready-fill: var(--ok);
  --status-succeeded-fg: var(--ok-text);
  --status-succeeded-bg: var(--ok-soft);
  --status-succeeded-fill: var(--ok);
  --status-progressing-fg: var(--info-text);
  --status-progressing-bg: var(--info-soft);
  --status-progressing-fill: var(--info);
  --status-running-fg: var(--info-text);
  --status-running-bg: var(--info-soft);
  --status-running-fill: var(--info);
  --status-pending-fg: var(--info-text);
  --status-pending-bg: var(--info-soft);
  --status-pending-fill: var(--info);
  --status-degraded-fg: var(--warn-text);
  --status-degraded-bg: var(--warn-soft);
  --status-degraded-fill: var(--warn);
  --status-failed-fg: var(--critical-text);
  --status-failed-bg: var(--critical-soft);
  --status-failed-fill: var(--critical);
  --status-unknown-fg: var(--text-muted);
  --status-unknown-bg: var(--bg-sunken);
  --status-unknown-fill: var(--text-muted);
  --chart-1: #58A6FF;
  --chart-2: #3FB950;
  --chart-3: #BC8CFF;
  --chart-4: #39C5CF;
  --chart-5: #D29922;
  --chart-6: #FF7B72;

  color-scheme: light;
}

/*
 * The seven further themes (product owner, 2026-10-12). Each block is generated from its palette in
 * `PALETTES` (packages/config/src/tokens.ts) and held to it by `tokens.test.ts`; the three `--chrome-*`
 * lines are the frame — header, sidebar and the seam under the header — which every theme names for
 * itself because the frame's step above the ground is part of what makes a palette read as one.
 */

/* Slate / Steel */
:root[data-theme="slate"] {
  --bg-page: #0F1419;
  --bg-surface: #1B232C;
  --bg-sunken: #151C23;
  --bg-hover: #242E39;
  --border-hairline: #303C49;
  --border-strong: #465668;
  --text-primary: #E8EDF2;
  --text-secondary: #A9B4C0;
  --text-muted: #919DAA;
  --accent: #94A3B8;
  --accent-hover: #CBD5E1;
  --accent-soft: #2A3542;
  --accent-text: #CBD5E1;
  --accent-solid: #CBD5E1;
  --on-accent: #0F1419;
  --focus-ring: #94A3B8;
  --scrim: rgba(15, 20, 25, 0.65);
  --grid: rgba(232, 237, 242, 0.05);
  --chart-fill: rgba(148, 163, 184, 0.14);
  --brand: #EE0000;
  --brand-deep: #A30000;
  --elev-rest: none;
  --elev-hover: none;
  --elev-overlay: 0 8px 32px rgba(0, 0, 0, 0.6);
  --ok: #3FB950;
  --ok-soft: rgba(63, 185, 80, 0.28);
  --ok-text: #62DF70;
  --info: #58A6FF;
  --info-soft: rgba(88, 166, 255, 0.28);
  --info-text: #89D0FF;
  --warn: #D29922;
  --warn-soft: rgba(210, 153, 34, 0.28);
  --warn-text: #F1C14F;
  --critical: #F85149;
  --critical-soft: rgba(248, 81, 73, 0.28);
  --critical-text: #FFA198;
  --color-background: var(--bg-page);
  --color-surface: var(--bg-surface);
  --color-surface-raised: var(--bg-surface);
  --color-sunken: var(--bg-sunken);
  --color-hover: var(--bg-hover);
  --color-border: var(--border-hairline);
  --color-border-strong: var(--border-strong);
  --color-text: var(--text-primary);
  --color-text-muted: var(--text-secondary);
  --color-text-subtle: var(--text-muted);
  --color-primary: var(--accent);
  --color-primary-hover: var(--accent-hover);
  --color-primary-soft: var(--accent-soft);
  --color-primary-text: var(--accent-text);
  --color-primary-solid: var(--accent-solid);
  --color-on-primary: var(--on-accent);
  --color-focus-ring: var(--focus-ring);
  --color-scrim: var(--scrim);
  --color-grid: var(--grid);
  --color-chart-fill: var(--chart-fill);
  --color-brand: var(--brand);
  --color-brand-deep: var(--brand-deep);
  --color-accent2: var(--ok);
  --elev-card: var(--elev-rest);
  --elev-dropdown: var(--elev-overlay);
  --elev-modal: var(--elev-overlay);
  --severity-ok-fg: var(--ok-text);
  --severity-ok-bg: var(--ok-soft);
  --severity-ok-on: var(--ok-text);
  --severity-ok-fill: var(--ok);
  --severity-info-fg: var(--info-text);
  --severity-info-bg: var(--info-soft);
  --severity-info-on: var(--info-text);
  --severity-info-fill: var(--info);
  --severity-warning-fg: var(--warn-text);
  --severity-warning-bg: var(--warn-soft);
  --severity-warning-on: var(--warn-text);
  --severity-warning-fill: var(--warn);
  --severity-critical-fg: var(--critical-text);
  --severity-critical-bg: var(--critical-soft);
  --severity-critical-on: var(--critical-text);
  --severity-critical-fill: var(--critical);
  --status-ready-fg: var(--ok-text);
  --status-ready-bg: var(--ok-soft);
  --status-ready-fill: var(--ok);
  --status-succeeded-fg: var(--ok-text);
  --status-succeeded-bg: var(--ok-soft);
  --status-succeeded-fill: var(--ok);
  --status-progressing-fg: var(--info-text);
  --status-progressing-bg: var(--info-soft);
  --status-progressing-fill: var(--info);
  --status-running-fg: var(--info-text);
  --status-running-bg: var(--info-soft);
  --status-running-fill: var(--info);
  --status-pending-fg: var(--info-text);
  --status-pending-bg: var(--info-soft);
  --status-pending-fill: var(--info);
  --status-degraded-fg: var(--warn-text);
  --status-degraded-bg: var(--warn-soft);
  --status-degraded-fill: var(--warn);
  --status-failed-fg: var(--critical-text);
  --status-failed-bg: var(--critical-soft);
  --status-failed-fill: var(--critical);
  --status-unknown-fg: var(--text-muted);
  --status-unknown-bg: var(--bg-sunken);
  --status-unknown-fill: var(--text-muted);
  --chart-1: #58A6FF;
  --chart-2: #3FB950;
  --chart-3: #BC8CFF;
  --chart-4: #39C5CF;
  --chart-5: #D29922;
  --chart-6: #FF7B72;
  --chrome-topbar: #131A21;
  --chrome-sidebar: #161E26;
  --chrome-seam: var(--border-hairline);
  color-scheme: dark;
}

/* White / Black */
:root[data-theme="mono"] {
  --bg-page: #000000;
  --bg-surface: #0A0A0A;
  --bg-sunken: #000000;
  --bg-hover: #1A1A1A;
  --border-hairline: #4D4D4D;
  --border-strong: #A6A6A6;
  --text-primary: #FFFFFF;
  --text-secondary: #E0E0E0;
  --text-muted: #B0B0B0;
  --accent: #FFFFFF;
  --accent-hover: #D9D9D9;
  --accent-soft: #262626;
  --accent-text: #FFFFFF;
  --accent-solid: #FFFFFF;
  --on-accent: #000000;
  --focus-ring: #FFFFFF;
  --scrim: rgba(0, 0, 0, 0.65);
  --grid: rgba(255, 255, 255, 0.06);
  --chart-fill: rgba(255, 255, 255, 0.10);
  --brand: #EE0000;
  --brand-deep: #A30000;
  --elev-rest: none;
  --elev-hover: none;
  --elev-overlay: 0 8px 32px rgba(0, 0, 0, 0.6);
  --ok: #3DFF8A;
  --ok-soft: rgba(61, 255, 138, 0.30);
  --ok-text: #A5FFC9;
  --info: #4FC3FF;
  --info-soft: rgba(79, 195, 255, 0.30);
  --info-text: #BDE8FF;
  --warn: #FFD400;
  --warn-soft: rgba(255, 212, 0, 0.30);
  --warn-text: #FFEF99;
  --critical: #FF5C5C;
  --critical-soft: rgba(255, 92, 92, 0.32);
  --critical-text: #FFCCCC;
  --color-background: var(--bg-page);
  --color-surface: var(--bg-surface);
  --color-surface-raised: var(--bg-surface);
  --color-sunken: var(--bg-sunken);
  --color-hover: var(--bg-hover);
  --color-border: var(--border-hairline);
  --color-border-strong: var(--border-strong);
  --color-text: var(--text-primary);
  --color-text-muted: var(--text-secondary);
  --color-text-subtle: var(--text-muted);
  --color-primary: var(--accent);
  --color-primary-hover: var(--accent-hover);
  --color-primary-soft: var(--accent-soft);
  --color-primary-text: var(--accent-text);
  --color-primary-solid: var(--accent-solid);
  --color-on-primary: var(--on-accent);
  --color-focus-ring: var(--focus-ring);
  --color-scrim: var(--scrim);
  --color-grid: var(--grid);
  --color-chart-fill: var(--chart-fill);
  --color-brand: var(--brand);
  --color-brand-deep: var(--brand-deep);
  --color-accent2: var(--ok);
  --elev-card: var(--elev-rest);
  --elev-dropdown: var(--elev-overlay);
  --elev-modal: var(--elev-overlay);
  --severity-ok-fg: var(--ok-text);
  --severity-ok-bg: var(--ok-soft);
  --severity-ok-on: var(--ok-text);
  --severity-ok-fill: var(--ok);
  --severity-info-fg: var(--info-text);
  --severity-info-bg: var(--info-soft);
  --severity-info-on: var(--info-text);
  --severity-info-fill: var(--info);
  --severity-warning-fg: var(--warn-text);
  --severity-warning-bg: var(--warn-soft);
  --severity-warning-on: var(--warn-text);
  --severity-warning-fill: var(--warn);
  --severity-critical-fg: var(--critical-text);
  --severity-critical-bg: var(--critical-soft);
  --severity-critical-on: var(--critical-text);
  --severity-critical-fill: var(--critical);
  --status-ready-fg: var(--ok-text);
  --status-ready-bg: var(--ok-soft);
  --status-ready-fill: var(--ok);
  --status-succeeded-fg: var(--ok-text);
  --status-succeeded-bg: var(--ok-soft);
  --status-succeeded-fill: var(--ok);
  --status-progressing-fg: var(--info-text);
  --status-progressing-bg: var(--info-soft);
  --status-progressing-fill: var(--info);
  --status-running-fg: var(--info-text);
  --status-running-bg: var(--info-soft);
  --status-running-fill: var(--info);
  --status-pending-fg: var(--info-text);
  --status-pending-bg: var(--info-soft);
  --status-pending-fill: var(--info);
  --status-degraded-fg: var(--warn-text);
  --status-degraded-bg: var(--warn-soft);
  --status-degraded-fill: var(--warn);
  --status-failed-fg: var(--critical-text);
  --status-failed-bg: var(--critical-soft);
  --status-failed-fill: var(--critical);
  --status-unknown-fg: var(--text-muted);
  --status-unknown-bg: var(--bg-sunken);
  --status-unknown-fill: var(--text-muted);
  --chart-1: #58A6FF;
  --chart-2: #3FB950;
  --chart-3: #BC8CFF;
  --chart-4: #39C5CF;
  --chart-5: #D29922;
  --chart-6: #FF7B72;
  color-scheme: dark;
  --chrome-topbar: #000000;
  --chrome-sidebar: #000000;
  --chrome-seam: var(--border-strong);
}

/* High Contrast */
:root[data-theme="contrast"] {
  --bg-page: #000000;
  --bg-surface: #0B0B0B;
  --bg-sunken: #000000;
  --bg-hover: #1F1F1F;
  --border-hairline: #8C8C8C;
  --border-strong: #FFFFFF;
  --text-primary: #FFFFFF;
  --text-secondary: #EBEBEB;
  --text-muted: #C4C4C4;
  --accent: #FFD400;
  --accent-hover: #FFE24D;
  --accent-soft: #2E2600;
  --accent-text: #FFD400;
  --accent-solid: #FFD400;
  --on-accent: #000000;
  --focus-ring: #FFD400;
  --scrim: rgba(0, 0, 0, 0.65);
  --grid: rgba(255, 255, 255, 0.05);
  --chart-fill: rgba(255, 212, 0, 0.14);
  --brand: #EE0000;
  --brand-deep: #A30000;
  --elev-rest: none;
  --elev-hover: none;
  --elev-overlay: 0 8px 32px rgba(0, 0, 0, 0.6);
  --ok: #3DFF8A;
  --ok-soft: rgba(61, 255, 138, 0.30);
  --ok-text: #A5FFC9;
  --info: #4FC3FF;
  --info-soft: rgba(79, 195, 255, 0.30);
  --info-text: #BDE8FF;
  --warn: #FFD400;
  --warn-soft: rgba(255, 212, 0, 0.30);
  --warn-text: #FFEF99;
  --critical: #FF5C5C;
  --critical-soft: rgba(255, 92, 92, 0.32);
  --critical-text: #FFCCCC;
  --color-background: var(--bg-page);
  --color-surface: var(--bg-surface);
  --color-surface-raised: var(--bg-surface);
  --color-sunken: var(--bg-sunken);
  --color-hover: var(--bg-hover);
  --color-border: var(--border-hairline);
  --color-border-strong: var(--border-strong);
  --color-text: var(--text-primary);
  --color-text-muted: var(--text-secondary);
  --color-text-subtle: var(--text-muted);
  --color-primary: var(--accent);
  --color-primary-hover: var(--accent-hover);
  --color-primary-soft: var(--accent-soft);
  --color-primary-text: var(--accent-text);
  --color-primary-solid: var(--accent-solid);
  --color-on-primary: var(--on-accent);
  --color-focus-ring: var(--focus-ring);
  --color-scrim: var(--scrim);
  --color-grid: var(--grid);
  --color-chart-fill: var(--chart-fill);
  --color-brand: var(--brand);
  --color-brand-deep: var(--brand-deep);
  --color-accent2: var(--ok);
  --elev-card: var(--elev-rest);
  --elev-dropdown: var(--elev-overlay);
  --elev-modal: var(--elev-overlay);
  --severity-ok-fg: var(--ok-text);
  --severity-ok-bg: var(--ok-soft);
  --severity-ok-on: var(--ok-text);
  --severity-ok-fill: var(--ok);
  --severity-info-fg: var(--info-text);
  --severity-info-bg: var(--info-soft);
  --severity-info-on: var(--info-text);
  --severity-info-fill: var(--info);
  --severity-warning-fg: var(--warn-text);
  --severity-warning-bg: var(--warn-soft);
  --severity-warning-on: var(--warn-text);
  --severity-warning-fill: var(--warn);
  --severity-critical-fg: var(--critical-text);
  --severity-critical-bg: var(--critical-soft);
  --severity-critical-on: var(--critical-text);
  --severity-critical-fill: var(--critical);
  --status-ready-fg: var(--ok-text);
  --status-ready-bg: var(--ok-soft);
  --status-ready-fill: var(--ok);
  --status-succeeded-fg: var(--ok-text);
  --status-succeeded-bg: var(--ok-soft);
  --status-succeeded-fill: var(--ok);
  --status-progressing-fg: var(--info-text);
  --status-progressing-bg: var(--info-soft);
  --status-progressing-fill: var(--info);
  --status-running-fg: var(--info-text);
  --status-running-bg: var(--info-soft);
  --status-running-fill: var(--info);
  --status-pending-fg: var(--info-text);
  --status-pending-bg: var(--info-soft);
  --status-pending-fill: var(--info);
  --status-degraded-fg: var(--warn-text);
  --status-degraded-bg: var(--warn-soft);
  --status-degraded-fill: var(--warn);
  --status-failed-fg: var(--critical-text);
  --status-failed-bg: var(--critical-soft);
  --status-failed-fill: var(--critical);
  --status-unknown-fg: var(--text-muted);
  --status-unknown-bg: var(--bg-sunken);
  --status-unknown-fill: var(--text-muted);
  --chart-1: #58A6FF;
  --chart-2: #3FB950;
  --chart-3: #BC8CFF;
  --chart-4: #39C5CF;
  --chart-5: #D29922;
  --chart-6: #FF7B72;
  --chrome-topbar: #000000;
  --chrome-sidebar: #000000;
  --chrome-seam: var(--border-strong);
  color-scheme: dark;
}

/* ================================================================== base */

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

* {
  margin: 0;
}

html {
  /* dvh, not vh: on mobile 100vh includes the collapsing browser chrome and overflows. */
  min-height: 100dvh;
  -webkit-text-size-adjust: 100%;
}

/*
 * The document does not scroll — `AppShell` is an application frame that fits the window, and the
 * content region inside it scrolls instead.
 *
 * `overflow: hidden` here rather than only in the frame, because a second scrollbar is not a
 * cosmetic problem: with both, a wheel event near the edge of a full content region chains to the
 * document and moves the whole app by a few pixels, so the header appears to drift. Signed-out
 * screens (login, activation) are short and centred, so nothing is lost by them not scrolling.
 */
body {
  height: 100dvh;
  overflow: hidden;
  background: var(--color-background);
  color: var(--color-text);
  font-family: var(--font-sans);
  /* 13px is the spec's body size. See the iOS note under the compact breakpoint. */
  font-size: var(--text-base);
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
}

/*
 * The measurement voice: mono, tabular, for things that ARE a reading.
 *
 * Membership of this list is the whole rule — `serif is prose, sans is interface, mono is
 * measurement` — and it is a list of **value** classes, deliberately. A figure is a measurement; the
 * caption under it is interface prose that happens to contain a number, and those are not the same
 * thing. `.metric-delta` carries "no heartbeat for 90s" and `.stat-card-delta` carries "2 ready";
 * setting either in mono renders a sentence as though it were an instrument reading.
 *
 * That distinction was got wrong once, by putting the mono family on `.tnum` — which is worn by
 * values and captions alike — and it shipped: the Agents page rendered "none reported" and "no
 * heartbeat for 90s" in monospace under the figures they describe. See `.tnum` below.
 */
.mono,
.num,
td.num,
.metric-value,
.stat-card-value,
.gauge-value,
.chart-tip-value,
.panel-tip-value,
.dash-pin-window {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  letter-spacing: -0.015em;
}

/*
 * A measurement, in the measurement voice. This rule is the direction's signature.
 *
 * `.tnum` used to mean "line these figures up but keep them in the sans face", and it is worn by
 * exactly one kind of thing: `stat-card-value`, `metric-value`, `gauge-value`, `chart-tip-value`,
 * `panel-tip-value`, `dash-updated`, `dash-pin-window` — every one a reading taken from a cluster.
 * None of them is prose. Setting a node count in the same face as the word "Nodes" presents a
 * measurement as if it were a sentence, and this product's entire job is reporting measurements.
 *
 * So the type system now separates three voices rather than two: **serif is prose, sans is
 * interface, mono is measurement.** A reader can tell which one is speaking without being told, and
 * the tell is the same one an engineer already reads terminal output by.
 *
 * `letter-spacing` comes back a touch because mono is wider than the sans it replaces, and a hero
 * figure at 32px is where that shows up first — a four-digit count in a stat card that shares its
 * row with three others.
 */
/*
 * Tabular figures, and NOT the mono family. Both halves are deliberate.
 *
 * `.tnum` is applied to values and to captions alike — `stat-card-value` wears it, and so do
 * `metric-delta` ("no heartbeat for 90s") and `stat-card-delta` ("2 ready"). It is therefore the
 * wrong hook for the measurement voice, which belongs to the value classes listed above.
 *
 * It briefly carried `font-family: var(--font-mono)` and that shipped a visible bug: every caption
 * in the product went monospace, so the Agents page read its cluster figures with "none reported"
 * and "no heartbeat for 90s" set like terminal output underneath. The lesson is narrow and worth
 * keeping — a class that means "line these digits up" is not the same as a class that means "this
 * is a reading", and only the second one is a typeface decision.
 *
 * What it still does matters: tabular figures on a caption stop "2 ready" from re-flowing as the
 * count crosses from 9 to 10.
 */
.tnum {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

a {
  color: var(--color-primary);
  text-decoration: none;
}
a:hover {
  color: var(--hover-accent-text);
  text-decoration: none;
  transition: color var(--motion-fast) var(--ease-out);
}

/* 2px accent ring at 40%. Never removed. */
:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 1px;
  border-radius: var(--radius-sm);
}

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}

button {
  cursor: pointer;
}
button:disabled {
  cursor: not-allowed;
  /* Disabled reads as disabled at 0.38–0.5, and is announced by the attribute, not the opacity. */
  opacity: 0.42;
}

/*
 * Topbar account menu — one compact trigger, one dropdown, replacing what used to be a name/role
 * block, a theme switch and a bare-text sign-out control sitting side by side at all times. See
 * `UserMenu` in index.tsx for why the theme row does not close the panel and Log out does.
 */
.user-menu-trigger {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  height: var(--size-control);
  padding: 0 var(--space-3);
  border: none;
  border-radius: var(--radius-md);
  background: none;
  color: var(--color-text);
  font-size: var(--text-base);
  font-weight: 500;
  font-family: var(--font-sans);
  cursor: pointer;
  transition: background-color var(--motion-fast) var(--ease-out);
}
.user-menu-trigger:hover,
.user-menu-trigger:focus-visible,
.user-menu-trigger[aria-expanded="true"] {
  background-color: var(--color-hover);
}
.user-menu-trigger-name {
  max-width: 140px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.user-menu-trigger-chevron {
  flex: none;
  color: var(--color-text-subtle);
  transition: transform var(--motion-base) var(--ease-out);
}
.user-menu-trigger[aria-expanded="true"] .user-menu-trigger-chevron {
  transform: rotate(180deg);
}

/*
 * Anchored under the trigger rather than the whole header, so it tracks the one control that opens
 * it regardless of what else the topbar carries to its left. No header block: the reference this
 * follows is a plain list of rows and nothing else — the name already reads on the trigger itself,
 * and repeating it in here is exactly the bulk the trigger was built to remove.
 */
.user-menu-panel {
  position: absolute;
  top: calc(100% + var(--space-2));
  right: 0;
  z-index: var(--z-overlay);
  /* 220: the identity block's email and "High Contrast" beside its swatch and check need the room. */
  width: 220px;
  /* No overflow clipping: the nested theme list sits outside this box, and the menu is now short. */
  padding: var(--space-1);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
}
.user-menu-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  height: var(--size-control-sm);
  padding: 0 var(--space-2);
  border-radius: var(--radius-sm);
}
.user-menu-row-label {
  font-size: var(--text-sm);
  color: var(--color-text);
}
.user-menu-item {
  display: flex;
  align-items: center;
  width: 100%;
  height: var(--size-control-sm);
  padding: 0 var(--space-2);
  border: none;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--color-text);
  font-size: var(--text-sm);
  font-family: var(--font-sans);
  text-align: left;
  cursor: pointer;
  transition: background-color var(--motion-fast) var(--ease-out);
}
.user-menu-item:hover,
.user-menu-item:focus-visible {
  background-color: var(--color-hover);
}
@media (prefers-reduced-motion: reduce) {
  .user-menu-trigger,
  .user-menu-trigger-chevron,
  .user-menu-item {
    transition: none;
  }
}

/* Anything wide scrolls inside its own box; the page body never scrolls sideways. */
.scroll-x {
  overflow-x: auto;
  /*
   * Pinned, because leaving it alone does NOT leave it alone.
   *
   * Per CSS Overflow 3, when one axis is not `visible` the other computes from `visible` to `auto` —
   * so `overflow-x: auto` on its own quietly makes the box scrollable *vertically* as well. One
   * subpixel of content overflow then paints a full-height scrollbar. That is what put a vertical
   * scrollbar down the right-hand side of the Agents tab strip, a row of four tabs occupying a third
   * of the width with nothing to scroll in either direction.
   *
   * `hidden`, not `clip`, and that was measured rather than reasoned. `clip` was written here first
   * on the grounds that it avoids making this a scroll container, so `overflow-clip-margin` could
   * let a child's focus ring paint past the edge. It does not work in this pairing: the same spec
   * says `clip` computes to `hidden` when the *other* axis is `auto` or `scroll`. Chrome duly
   * reported `overflow-y: hidden`, the clip margin was inert, and the ring was still cropped by 3px
   * at the top. Two declarations that read as a considered choice and did nothing.
   */
  overflow-y: hidden;
  max-width: 100%;
  /* Without this, reaching the right end of a wide table hands the wheel to the page and the whole
     layout slides sideways — the "why is my dashboard drifting" bug. */
  overscroll-behavior-x: contain;
}

.scroll-x :focus-visible {
  outline-offset: -2px;
}

/*
 * A tab label occupies its bold width in both states, so the strip cannot re-flow on selection.
 *
 * Tabs go from weight 500 to 600 when selected, and a heavier label is wider — so choosing a tab
 * moved every tab after it. Measured on the Agents strip: Install shifted 2.83px left, Permissions
 * and Keys 0.97px right. No single state looks wrong, which is why this survives review; it is only
 * visible as the row twitching under the pointer at the moment of the click.
 *
 * The mechanism is a zero-height ghost of the same string at 600. `inline-grid` with one column
 * makes the column as wide as its widest item, and the ghost — always bold — is that item. The
 * visible text then changes weight inside a box whose width never changes.
 *
 * `height: 0` keeps the ghost out of the vertical rhythm; `visibility: hidden` keeps it out of the
 * accessibility tree, so the label is not announced twice.
 */
.tab-label {
  display: inline-grid;
  grid-template-columns: max-content;
}

.tab-label::after {
  content: attr(data-label);
  font-weight: 600;
  height: 0;
  overflow: hidden;
  visibility: hidden;
  pointer-events: none;
}

/*
 * Focus rings draw *inside* the element within a scroll region, because the region clips them.
 *
 * The global ring is `outline: 2px` at `outline-offset: 1px`, so it reaches 3px outside the border
 * box. A child that fills a `.scroll-x` — a tab does, exactly — has that ring cropped top and
 * bottom by the `overflow-y: hidden` above. Measured at 3px lost on top, 2px on the bottom.
 *
 * A negative offset puts the same 2px ring just inside the border box instead: fully visible, same
 * weight, same colour, nothing clipped. The alternative was padding every scroll region by 3px to
 * make room, which moves every table and tab strip in the product to fix a ring.
 *
 * This is the product's "focus is never removed" rule holding under a constraint, not an exception
 * to it.
 */


/* ================================================================== scrolling
 *
 * Every scroll region in the product, in one place — and one decision made here once, because it
 * was first made wrong: **which regions may hand the wheel on, and which must keep it.**
 *
 * `overscroll-behavior: contain` reads like a safety property, so it went on all of them. On a page
 * built from panels that is not a safe default, it is a broken one. The panels tile the entire
 * scroll region, so once a panel had reached its last row the wheel stopped dead there and the page
 * behind it — longer than the window, with more panels below the fold — would not move. The only
 * place left to point at was the 16px gap between two cards. The page reads as frozen, and nothing
 * on screen says why.
 *
 * Containment is now scoped to the two cases where escaping is genuinely wrong:
 *
 *   - **Overlays.** A slideover or a dialog is a separate context; scrolling the page behind one is
 *     disorienting, because you lose your place in something you never touched. Declared on
 *     `.slideover-body` next to the rest of that component.
 *   - **The horizontal axis, always.** Nothing here intends to move sideways. Reaching the right
 *     edge of a wide table and having the whole layout slide is never what the gesture meant, so
 *     `.scroll-x` and `.ledger-mini` contain on x and leave y alone.
 *
 * Everything that is part of the page — a dashboard panel, the resource nav, a capped table —
 * chains. Scroll it to its end and the page carries on, which is what a wheel does everywhere else.
 *
 * Appearance is a separate concern and applies to all of them. A 15px chrome-grey bar down the side
 * of each of nine panels turns a dashboard into a page of scrollbars. Firefox reads
 * `scrollbar-width`/`scrollbar-color`; WebKit and Blink read the pseudo-elements. Both are written,
 * because a scrollbar styled in one engine and not the other is worse than one styled in neither —
 * whoever makes the change never sees the half they left behind.
 *
 * ## The two halves must never both apply, and that is not a style preference
 *
 * They were both written unconditionally, and it put stepper arrows on scrollbars all over the
 * product. Measured in Chrome 150 under this stylesheet's own rules, six boxes side by side:
 *
 *     ::-webkit-scrollbar rules alone .................. clean 10px pill, no buttons
 *     + scrollbar-width: thin .......................... ▲ ▼ ◄ ► stepper arrows
 *     + scrollbar-color ................................ ▲ ▼ ◄ ► stepper arrows, and 15px not 10px
 *     + both, which is what shipped .................... ▲ ▼ ◄ ► stepper arrows
 *
 * Specifying *either* standard property moves that element off Blink's `::-webkit-scrollbar` path and
 * onto its standard scrollbar, which draws platform buttons. Every rule below it then does nothing —
 * `::-webkit-scrollbar-button { display: none }` was measured too and does not help, because the
 * pseudo-elements are no longer consulted at all.
 *
 * `scrollbar-color` **inherits**, which is what made this product-wide from seven selectors. Declared
 * on `.page-region`, it reaches every scroll container inside the app — including ones with no class
 * for any rule to name. Measured in the AI advice dialog: its body and both `<pre>` code blocks all
 * computed `scrollbar-color: rgb(142, 151, 165) …`, and the body reserved **15px** for a bar this
 * stylesheet asks to be 10px. That is the reported bug — the arrows were inherited, not authored.
 *
 * ## Why a feature query rather than a longer list
 *
 * The old rule named seven selectors while the pseudo-elements below use `*`. An enumerated list is a
 * drift mechanism: every scroller added since fell out of it silently — `.scroll-x`, `.panel-legend`,
 * `.dash-picker-list`, `.ledger-mini`, `.rail-list`, and the `Modal` body, which has no class at
 * all and therefore no selector could ever have reached it.
 *
 * `@supports not selector(::-webkit-scrollbar)` splits on the only thing that matters, and it is
 * correct in both branches by construction rather than by our getting a list right: an engine either
 * understands the pseudo-element — and is then styled by the rules below — or it does not, and takes
 * the standard properties here. No engine falls through to neither, and none can take both.
 */

@supports not selector(::-webkit-scrollbar) {
  * {
    scrollbar-width: thin;
    scrollbar-color: var(--border-strong) transparent;
  }

  /* The advice dialog's answer column, the bastion terminal and the monitoring board switcher draw
     no bar at all — see the notes beside their `::-webkit-scrollbar` rules. In this block rather than
     beside them, because a second `@supports` is a second place to keep in step and `scroll.test.ts`
     refuses one. */
  .advice-split-answer,
  .bastion-screen .xterm-viewport,
  .dash-links {
    scrollbar-width: none;
  }

  /* The other half of hiding the findings list's bar — see the pseudo-element rule above. */
  .triage {
    scrollbar-width: none;
  }
}

.scroll-y {
  overflow-y: auto;
  min-height: 0;
}

/* The track stays transparent so the bar reads as sitting on the card, not as a gutter cut into it. */
*::-webkit-scrollbar {
  width: var(--size-scrollbar);
  height: var(--size-scrollbar);
}

*::-webkit-scrollbar-track {
  background: transparent;
}

*::-webkit-scrollbar-thumb {
  background-color: var(--border-strong);
  border-radius: var(--radius-full);
  /* A transparent border with `background-clip: padding-box` is what makes the thumb narrower than
     its own hit area: 6px of visible bar, 10px of grabbable target. */
  border: 2px solid transparent;
  background-clip: padding-box;
}

*::-webkit-scrollbar-thumb:hover {
  background-color: var(--text-muted);
}

/* Where two scrollbars meet. Left unstyled it renders as a grey square in the corner of every card. */
*::-webkit-scrollbar-corner {
  background: transparent;
}

/* Screen-reader-only, for labels a sighted user gets from context. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: var(--space-2);
  z-index: var(--z-toast);
  background: var(--color-surface-raised);
  color: var(--color-text);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border-strong);
}
.skip-link:focus {
  left: var(--space-4);
}

@keyframes shimmer {
  100% {
    transform: translateX(100%);
  }
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

/*
 * Reduced motion collapses every transition to instant.
 *
 * The spinner keeps turning and the shimmer keeps sweeping — they are the two animations that carry
 * meaning rather than decoration, and freezing them makes a loading state look like a stalled one.
 */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .spin {
    animation-duration: 1.2s !important;
    animation-iteration-count: infinite !important;
  }
  /* The comment above names two meaning-carrying animations; only .spin was actually restored, so
     under reduced motion every skeleton froze into the exact "stalled" look the comment argues
     against. The sweep survives at a gentler cadence rather than full speed. */
  .sk::after {
    animation-duration: 2s !important;
    animation-iteration-count: infinite !important;
  }
}

/*
 * 16px inputs below the medium breakpoint.
 *
 * The spec's 14px form input is right on a desktop console and wrong on a phone: iOS zooms the
 * viewport on focus for anything under 16px, and the zoom does not undo itself. The spec's density
 * is a desktop density; this is the one place it has to give.
 */
@media (max-width: 767px) {
  input,
  select,
  textarea {
    font-size: 16px;
  }
}

/* ================================================================== elevation
 *
 * Three steps and no fourth. The names say where each is allowed, and the aliases exist because 20
 * or so call sites still ask for `.elev-dropdown` and `.elev-modal` by name.
 */

.elev-rest {
  box-shadow: var(--elev-rest);
}
.elev-hover {
  box-shadow: var(--elev-hover);
}
.elev-overlay,
.elev-dropdown,
.elev-modal {
  box-shadow: var(--elev-overlay);
}
.elev-card {
  box-shadow: var(--elev-rest);
}

/*
 * `AccessDeniedState` — a 403, said as a decision rather than a fault.
 *
 * Neutral on purpose: the sunken ground and the secondary ink, never the critical red that
 * `ErrorState` uses. Red says something is broken; nothing is. Dashed rather than solid so it reads
 * as "nothing for you here" next to an empty state's plain box, not as a panel that failed to load.
 */
.access-denied {
  padding: var(--space-6) var(--space-5);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--bg-sunken);
  color: var(--text-secondary);
  text-align: center;
}
.access-denied-title {
  margin-top: var(--space-2);
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--text-primary);
}
.access-denied-needs {
  margin-top: var(--space-1);
  font-size: var(--text-base);
}
.access-denied-detail {
  margin: var(--space-2) auto 0;
  max-width: 60ch;
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

/*
 * The route boundaries — `error.tsx` and `not-found.tsx` in both portals.
 *
 * They render without the shell (the shell is the likeliest thing to have thrown), so this is a
 * whole page of its own: the reader's theme ground, one card centred in the window, and room for the
 * error's own message to wrap rather than run off a phone screen.
 */
.boundary-page {
  min-height: 100dvh;
  display: grid;
  place-items: center;
  padding: var(--space-4);
  background: var(--bg-page);
  color: var(--text-primary);
}
.boundary-card {
  width: 100%;
  max-width: 560px;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  overflow-wrap: anywhere;
}
.boundary-eyebrow {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--text-secondary);
}
.boundary-title {
  font-size: var(--text-xl);
  font-weight: 600;
  letter-spacing: -0.01em;
}
.boundary-body,
.boundary-ref {
  font-size: var(--text-base);
  color: var(--text-secondary);
}
.boundary-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* ================================================================== cards
 *
 * The primary unit of the design — and its edge is a hairline, never a shadow.
 *
 * Design spec v3, blacklist 2: a single 1px border is the only separation between adjacent
 * regions, exactly like Linear/Supabase/Sentry. `tokens.test.ts` reads this rule and fails on a
 * box-shadow reappearing here as readily as the previous suite failed on its absence.
 */

.card {
  /* A hair of transparency for the "glossy" read requested — subtle, not a real glass panel:
     just enough that whatever's behind a card bleeds through at the edges instead of the card
     reading as a fully solid tile. `color-mix()` against the existing token, not a new alpha
     literal, so it can never drift from `--bg-surface` itself. */
  background: color-mix(in srgb, var(--bg-surface) 94%, transparent);
  backdrop-filter: blur(16px) saturate(140%);
  -webkit-backdrop-filter: blur(16px) saturate(140%);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-card);
  /* Corners clip, so a table's first row cannot square off the card it sits in. */
  overflow: hidden;
}

.card-body {
  padding: var(--space-5);
}

/* The spec's 20-24px. 24 is for cards that carry a single composed thing — a chart, a hero figure —
   where 20 makes the content feel pushed against the edge. */
.card-body-roomy {
  padding: var(--space-6);
}

/*
 * A supporting card. Sunken ground, no lift.
 *
 * This is half of the answer to blacklist item 4 — a grid where every card looks equally important.
 * A card that recedes is the cheapest way to make the one beside it read as primary, and it costs no
 * extra size, which matters in a grid where the cells are the same width by construction.
 */
.card-quiet {
  background: var(--bg-sunken);
  box-shadow: none;
}

/*
 * The head is a band, not a padded first child: a card whose body is a full-bleed table needs the
 * title inset while the rows run edge to edge.
 */
.card-head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-5) var(--space-5) 0;
}

.card-head-tight {
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--border-hairline);
  margin-bottom: 0;
}

/* The tinted 28px square. Optional by the spec — but blacklist 10 says once the cards on a surface
   have one, the section header without one reads as unfinished, so it is all or none per surface. */
.card-icon {
  display: grid;
  place-items: center;
  flex: none;
  width: var(--size-card-icon);
  height: var(--size-card-icon);
  border-radius: var(--radius-md);
  background: var(--accent-soft);
  color: var(--accent-text);
}

.card-icon-ok {
  background: var(--ok-soft);
  color: var(--ok-text);
}
.card-icon-warning {
  background: var(--warn-soft);
  color: var(--warn-text);
}
.card-icon-critical {
  background: var(--critical-soft);
  color: var(--critical-text);
}

/*
 * A panel's title and the sentence under it, one step apart.
 *
 * ## Why this is not the serif any more
 *
 * "Load" over "Every node over the last 3 hours." is not the product talking — it is a label naming
 * an instrument and stating its axis. A reader scans it to find the right panel; they do not read
 * it. That is the line the type system now draws: paragraphs you read are serif, things you scan
 * are sans.
 *
 * It also never rendered as designed. The serif stack opens with three licensed faces this product
 * does not ship, and nothing is fetched because some installs are air-gapped — so every host without
 * them fell through to Charter or Georgia, and a bookish system serif at 16px over a 14px line is
 * what a panel title actually looked like. A face that resolves correctly only on the machines of
 * the people who chose it is a local preference, not a typographic decision.
 *
 * Both sizes lose the +1px they carried. That pixel was an optical correction for a serif setting
 * small beside sans; in sans it is just a bigger title.
 */
.card-title {
  font-family: var(--font-sans);
  letter-spacing: -0.01em;
  font-size: var(--text-lg);
  font-weight: 600;
  line-height: var(--leading-heading);
  color: var(--text-primary);
}

.card-subtitle {
  margin-top: 2px;
  font-family: var(--font-sans);
  font-size: var(--text-base);
  /* `body`, not `relaxed`: a subtitle is one or two lines and 1.6 makes the pair look unglued. */
  line-height: var(--leading-body);
  color: var(--text-secondary);
}

/* "View all" — top-right when the card previews a longer list. */
.card-action {
  margin-left: auto;
  flex: none;
  font-size: var(--text-base);
  color: var(--accent-text);
  white-space: nowrap;
}

/*
 * An interactive card lifts. A static one must not: a hover state on something that does nothing
 * when clicked is a promise the card cannot keep.
 */
.card-interactive {
  cursor: pointer;
  transition:
    box-shadow var(--motion-fast) var(--ease-out),
    border-color var(--motion-fast) var(--ease-out),
    transform var(--motion-fast) var(--ease-out);
}
.card-interactive:hover {
  border-color: var(--border-strong);
}
.card-interactive:focus-visible {
}

/*
 * A nested surface. No shadow of its own — depth comes from colour, per the spec.
 *
 * This is where a chart well, a code block, a progress track and a table header all live, and the
 * reason they share a class rather than each declaring a background is that "sunken things do not
 * stack shadows" is one rule, and one rule should have one place to break.
 */
.sunken {
  background: var(--bg-sunken);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
}

/* ================================================================== pills
 *
 * "Every status is a pill." Bare coloured text for state is blacklist item 3.
 *
 * The dot is not decoration: colour alone must never carry meaning, and the dot is what a pill has
 * instead of an icon when the label is already the word. `Badge` in index.tsx has no prop that
 * renders the ground without it.
 */

.pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: var(--radius-full);
  font-size: var(--text-sm);
  font-weight: 500;
  line-height: 1.3;
  white-space: nowrap;
  /* Uppercase is the spec's own casing for pills (`● READY`), and it is the second of exactly two
     sanctioned uppercase treatments — the other is the 11px eyebrow. */
  letter-spacing: 0.02em;
  background: var(--bg-sunken);
  color: var(--text-secondary);
}

.pill-dot {
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: var(--radius-full);
  background: currentcolor;
}

.pill-ok {
  background: var(--ok-soft);
  color: var(--ok-text);
}
.pill-ok .pill-dot {
  background: var(--ok);
}

.pill-info {
  background: var(--info-soft);
  color: var(--info-text);
}
.pill-info .pill-dot {
  background: var(--info);
}

.pill-warning {
  background: var(--warn-soft);
  color: var(--warn-text);
}
.pill-warning .pill-dot {
  background: var(--warn);
}

.pill-critical {
  background: var(--critical-soft);
  color: var(--critical-text);
}
.pill-critical .pill-dot {
  background: var(--critical);
}

/* Unknown is the deliberate exception: no hue at all. "We do not know" is not a mild form of
   "informational", and colouring it blue claims a reading nobody took. */
.pill-unknown {
  background: var(--bg-sunken);
  color: var(--text-secondary);
  border: 1px solid var(--border-hairline);
}
.pill-unknown .pill-dot {
  background: var(--text-muted);
}

/*
 * The spec asks for "a subtle pulse on the dot only" for a live state.
 *
 * On the dot only, and only while something is genuinely in flight — this is the third animation in
 * the product that carries meaning rather than decoration, alongside the shimmer and the spinner,
 * and like them it survives `prefers-reduced-motion` in a reduced form rather than freezing, because
 * a frozen "syncing" dot is indistinguishable from a stalled one.
 */
@keyframes pill-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.35;
  }
}

.pill-live .pill-dot,
.status-dot-live {
  animation: pill-pulse 1.6s var(--ease-out) infinite;
}

@media (prefers-reduced-motion: reduce) {
  .pill-live .pill-dot,
  .status-dot-live {
    animation-duration: 3.2s !important;
    animation-iteration-count: infinite !important;
  }
}

/*
 * The console header's compact freshness indicator: a dot, and nothing else.
 *
 * Its own class rather than `.status-dot`, because that one takes its colour from a
 * `.status-text[data-state]` ancestor and this has no text to sit inside — that is the whole point
 * of it. Same 6px geometry and the same four colours, so the compact form and the spelled-out pill
 * on the overview are visibly the same indicator.
 *
 * Slightly larger than `.status-dot` at 7px: standing alone in a header row with no word beside it,
 * 6px reads as a stray glyph rather than a status. It reuses `.status-dot-live` for the pulse, so
 * there is one pulse animation in the product and it keeps its `prefers-reduced-motion` handling.
 */
.freshness-dot {
  flex: none;
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: var(--radius-full);
  background: var(--text-muted);
}

.freshness-dot[data-level="live"] {
  background: var(--ok);
}
.freshness-dot[data-level="lagging"] {
  background: var(--warn);
}
/* Stale and never are both red: "the numbers on this page are not the cluster" is one state to the
   reader, whatever produced it. The title says which. */
.freshness-dot[data-level="stale"],
.freshness-dot[data-level="never"] {
  background: var(--critical);
}

/* `2 READY · 1 SYNCING · 1 UNKNOWN` — a pill group with its counts. */
.pill-group {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

/*
 * The same pill, when the count is also the filter.
 *
 * It reuses `.chip-button` for the reset, the hit target and the focus ring — the two are the same
 * problem (a bare button that must add interaction and no visual weight), and a second copy of that
 * reset would drift.
 *
 * The pressed pill is ringed rather than merely left bright. `currentcolor` is the pill's own tone,
 * so the ring is critical-red on the critical pill and warn-amber on the warning one without a rule
 * per tone; and a ring is a *shape*, which is what keeps the selected state off colour alone. The
 * dimming of the others is the supporting hint, not the signal.
 */
.pill-filter .pill {
  transition: opacity var(--motion-fast) var(--ease-out);
}

.pill-filter[aria-pressed="true"] .pill {
  box-shadow: 0 0 0 2px color-mix(in srgb, currentcolor 55%, transparent);
}

.pill-group-filtering .pill-filter[aria-pressed="false"] .pill {
  opacity: 0.45;
}

.pill-group-filtering .pill-filter[aria-pressed="false"]:hover .pill {
  opacity: 1;
}

/*
 * Status as a sentence — the spec's other rendering, and the default one.
 *
 * `● Healthy` as a word beside a 6px dot, for contexts that read as prose: a hero verdict, a
 * detail-panel field, a line in a card. The filled pill above is reserved for dense rows where many
 * statuses stack; giving every surface the pill is what made the old design read as a wall of
 * chips. This is not the "bare coloured text" the pill comment bans — the word stays in primary
 * ink and only the dot carries the hue, so the meaning survives greyscale intact.
 */
.status-text {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-weight: 500;
  color: var(--text-primary);
  white-space: nowrap;
}

.status-dot {
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: var(--radius-full);
}

.status-text[data-state="ok"] .status-dot {
  background: var(--ok);
}
.status-text[data-state="warn"] .status-dot {
  background: var(--warn);
}
.status-text[data-state="critical"] .status-dot {
  background: var(--critical);
}
/* Hueless for the same reason .pill-unknown is: "we do not know" is not a mild severity. */
.status-text[data-state="unknown"] .status-dot {
  background: var(--text-muted);
}


/* ================================================================== topology: the dependency map
 *
 * Everything below was missing, and its absence did not look like missing CSS.
 *
 * `chain-view.tsx` and `graph.tsx` were written and shipped with no stylesheet at all — 26 classes,
 * none of them defined anywhere. SVG has no inherited "sensible default": a `<text>` with no `fill`
 * paints **black**, and this canvas is near-black, so every chip rendered as an empty outlined box
 * with its name invisible inside it. The names were in the DOM the whole time, in the accessibility
 * tree and in each chip's `<title>`, which is why hovering worked and looking did not.
 *
 * The legend failed the same way and read as one run-on sentence — "healthy degraded or progressing
 * failed completed or not reported" — because its four swatches had no size and its items had no
 * gap, so the words closed up against each other.
 *
 * The rule this is worth stating for: an unstyled component in HTML degrades to something ugly you
 * notice immediately. An unstyled component in SVG degrades to something *absent*, and absent reads
 * as "this cluster has nothing to show".
 */

/*
 * The viewports, one per view. `.depmap` is the name `page.tsx` actually uses — an earlier draft of
 * this block invented `.depmap-canvas-wrap`, which matched nothing and left the radial view exactly
 * as unstyled as before while looking like it had been handled.
 *
 * ## Why a viewport height and not the drawing's own
 *
 * Both canvases carry a `viewBox`, so with `height: auto` the browser sizes them by *aspect ratio* —
 * the taller the graph, the taller the element, without limit. A chain through six layers pushed the
 * card past the fold, and a map you have to scroll to see the bottom of is not a map: the whole
 * value of the picture is that the relationships are visible at once.
 *
 * Giving the box a height derived from the window and letting the SVG fill it hands the scaling to
 * `preserveAspectRatio`, which defaults to `xMidYMid meet` — fit inside, centred, nothing cropped.
 * The trade is real and is the right way round: a very large graph draws small, and small-but-whole
 * beats large-and-half-off-screen. `min-height` stops a short chain from being squashed into a strip
 * on a laptop, and the `max` keeps it from becoming a letterboxed void on a tall monitor.
 */
.chain-view,
.depmap {
  width: 100%;
}

/*
 * The map canvas is a single-look surface, and that is a deliberate exception to theme-following.
 *
 * Every other surface in this product takes the viewer's theme. This one does not: it is a dark
 * instrument panel on a white page as readily as on a black one, the way a photograph or a video
 * frame is. The reason is that the thing being drawn is *light* — neon edges, glowing packets, an
 * orb — and light needs a dark ground to be light against. On the light theme's #EFF1F3 the same
 * glow renders as a grey smudge, and the traffic that is the entire point of the picture becomes the
 * least visible thing on it.
 *
 * So the on-canvas palette is scoped here rather than read from the page's tokens. Nothing outside
 * this element sees these, and the two components use them by name — see `TONE` in `graph.tsx` and
 * `chain-view.tsx` — so the canvas cannot half-follow the theme and end up with light-theme ink on
 * a dark ground.
 *
 * The state hues are tuned for this ground specifically. They are the same *meanings* as the page's
 * severity tokens and not the same values: `--critical` is mixed to read against a light card, and
 * on near-black it goes muddy.
 */
.depmap-stage,
.chain-canvas,
.depmap-canvas {
  /*
   * The palette is declared on the stage as well as on the two canvases, and that is what lets the
   * instruments around the drawing speak the same language as the drawing.
   *
   * It used to sit on the canvases alone, which was correct while nothing but a canvas read it. The
   * floating legend, the minimap and the hover panel are not canvases and they carry the same four
   * state colours; reading `--map-ok` from outside the `<svg>` resolved to nothing, and an
   * undeclared custom property does not warn — `background: var(--map-ok)` simply paints nothing,
   * which on this surface is a legend of four invisible swatches.
   *
   * Declared once, on a list. Two copies of the values would be a second palette that agrees with
   * the first until somebody tunes one of them.
   */
  --map-ground: #05070b;
  --map-grid: rgba(126, 162, 255, 0.11);
  --map-ink: #e8eeff;
  --map-ink-dim: #8b9ac2;

  /* State: what is happening on a connection. */
  --map-ok: #35e39a;
  --map-warn: #ffc247;
  --map-critical: #ff5f70;
  --map-unknown: #6b7899;

  /* Kind: what a thing is. A second channel, never mixed with the first — see 28-design.md. */
  --map-infra: #4d8dff;
  --map-orchestrate: #a879ff;

  /* The instruments: a hairline, a panel ground, and the ink that reads on it. */
  --map-edge: rgba(126, 162, 255, 0.16);
  --map-panel: rgba(9, 13, 22, 0.94);
  --map-panel-edge: rgba(126, 162, 255, 0.2);
}

.chain-canvas,
.depmap-canvas {
  display: block;
  width: 100%;
  height: auto;
  /*
   * A ceiling, not a height — and the first version of this got that wrong in a way only a render
   * showed. Setting `height` outright made every map that tall, so a cluster whose chain is a single
   * row of nodes drew seven small chips marooned in 62vh of empty canvas: `meet` centres the drawing
   * in whatever box it is given, and the box was being given a size the picture did not ask for.
   *
   * As a max it only binds when it needs to. A short map keeps its natural height; a six-layer chain
   * is scaled down by `preserveAspectRatio` — `xMidYMid meet` by default — to fit inside, centred,
   * nothing cropped and nothing to scroll.
   *
   * ## The ceiling was set low enough that it bound on every map, which cost the picture twice
   *
   * At 52vh the cap was *under* the natural height of the radial canvas at any normal page width, so
   * it bound always rather than rarely — and a cap that always binds is a fixed height wearing a
   * different word. That produced the two failures at once. The element was shorter than the drawing
   * wanted, so `meet` letterboxed it: the map was scaled to the *height* and left a dead margin down
   * each side, drawing a 1900-unit canvas at about 0.47 with 17-unit chip text landing near 8px. A
   * cluster map is not a decoration you glance at — it is read, chip by chip — and 8px is not read.
   *
   * The ceiling is now high enough that on an ordinary window it does not bind at all: the canvas
   * takes its own aspect ratio, there is no letterbox, and the drawing is as large as the column is
   * wide. It stays a `max` because that is still the right shape — a tall chain on a short window
   * must scale rather than run past the fold. Anyone lowering this should check what it does to a
   * chip label before deciding it is only a number.
   */
  max-height: clamp(360px, 78vh, 1000px);
  background: var(--map-ground);
  border-radius: var(--radius-md);
}

/* ---------------------------------------------------------------- the chain view: rows and boxes */

/*
 * Type on this canvas is in viewBox units, and the canvas is **always** rendered smaller than its
 * viewBox — 1900×920 fitted into roughly 1250×450 is a scale of about 0.49. So every size here is
 * roughly double what it would be in CSS pixels: 12px chip text rendered at 6px, which is why the
 * first live screenshot of this map had legible geometry and unreadable labels.
 *
 * `CHAR_W` in `graph.tsx` is the other half of this and has to move with it — it is the estimated
 * width of one character *at the chip font size*, and it sizes the bracket around the label. They
 * have drifted apart once already, and the symptom was every chip on the map ending `…arning.com]`
 * with the closing bracket printed through the final character.
 */
.chain-row-label {
  fill: var(--map-ink-dim);
  font-size: 15px;
  letter-spacing: 0.04em;
}

.chain-row-rule {
  stroke: var(--map-grid);
  stroke-width: 1;
}

.chain-node-box {
  /* Barely lighter than the ground: a chip is a pane of glass over it, not a card on top of it. */
  fill: rgba(255, 255, 255, 0.04);
}

.chain-node-name {
  fill: var(--map-ink);
  font-size: 17px;
  font-weight: 500;
}

.chain-node-meta {
  fill: var(--map-ink-dim);
  font-size: 15px;
}

.chain-node {
  cursor: pointer;
  transition: opacity 120ms var(--ease-out);
}
.chain-node:focus-visible {
  outline: none;
}
/* The focus ring is drawn on the box, because an SVG `<g>` cannot carry one. */
.chain-node:focus-visible .chain-node-box,
.chain-node.is-selected .chain-node-box {
  /* Selection is carried by the stroke, not by a lighter fill: this product's surfaces are
     separated by borders rather than elevation (blacklist 2), and there is no raised-surface token
     to reach for. */
  stroke-width: 2.4;
}
.chain-node.is-focus {
  cursor: default;
}

/* ------------------------------------------------------------------------------ edges and motion
 *
 * The distinction the page's own caption promises: "a solid, moving line is a connection this
 * cluster's service mesh actually measured; a dashed one is declared but not observed". That is a
 * real difference in what is known, so it is carried by line *form* — dashed against solid — and
 * never by colour alone, because colour here already means severity and cannot mean two things.
 */

.chain-edge,
.depmap-edge {
  fill: none;
  stroke-linecap: round;
  stroke-dasharray: 3 5;
  transition:
    opacity 120ms var(--ease-out),
    stroke-width 120ms var(--ease-out);
}

.chain-edge-live {
  stroke-dasharray: none;
}

/* -------------------------------------------------------------------- packets, glow and failure
 *
 * ## The neon is a second copy of the line, not a filter per line
 *
 * A blurred duplicate under each edge is the only way to get a glowing stroke in SVG without paying
 * for an offscreen pass per element. One `filter` on the halo *group* blurs ninety paths in a single
 * pass; ninety filtered paths is ninety passes, and on a map that repaints every frame that is the
 * difference between a live canvas and a slideshow.
 *
 * ## Why the failure blink is on the line and not only the node
 *
 * A red chip says "this object is failing". A red *edge* says "this path is not carrying anything",
 * which is the question a dependency map is being read to answer. Both are drawn, and the edge is
 * the one that moves, because motion is what the eye finds without being told where to look.
 *
 * The blink is slow — 1.4s, opacity only, never geometry. A fast flash on a monitoring surface is
 * the aesthetic of an alarm nobody can think next to, and this is a screen people work at for an
 * hour during an incident.
 */

.chain-particle,
.depmap-packet {
  fill: var(--map-ok);
  /* The trail. A single drop-shadow in the packet's own colour reads as motion blur at these sizes
     and costs far less than a real trail of fading copies. */
  filter: drop-shadow(0 0 4px var(--map-ok));
}

/*
 * The edge layer. Its own group so the halos, the lines and the packets stack in one predictable
 * order — the halo must never draw over the line it is behind, and a packet must never be behind
 * either.
 *
 * `pointer-events` is off on the two decorative layers only, and that distinction is load-bearing.
 * Putting it on this group instead — which is what it said first — silences the `<title>` on every
 * edge, so hovering a line stops naming the relation and the manifest field behind it. A blurred
 * copy of a line and a 2.4px dot are the wrong things to be able to hover; the line is not.
 */
.depmap-edges {
  pointer-events: auto;
}

.depmap-edge-glow,
.depmap-packet {
  pointer-events: none;
}

/* The halo group: one blur for every edge on the canvas. */
.depmap-edge-glow {
  filter: blur(3px);
}

.depmap-edge-halo {
  fill: none;
  stroke-width: 3.5;
  stroke-linecap: round;
}

/*
 * A broken connection announces itself.
 *
 * `is-broken` is set from the edge's own state, so this cannot fire on a healthy link — and the
 * particle loop already refuses to move packets across a `critical` edge, so a blinking red line is
 * always a line with nothing on it. The two halves say the same thing in different channels.
 */
@keyframes depmap-break {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.28;
  }
}

.chain-edge.is-broken,
.depmap-edge.is-broken,
.depmap-edge-halo.is-broken {
  animation: depmap-break 1.4s var(--ease-out) infinite;
}

/*
 * Reduced motion stops the blink and leaves the colour. The state must still be visible — freezing
 * a failure indicator into invisibility is the one thing this must never do — so the animation is
 * removed rather than paused, which leaves the element at its own full opacity.
 */
@media (prefers-reduced-motion: reduce) {
  .chain-edge.is-broken,
  .depmap-edge.is-broken,
  .depmap-edge-halo.is-broken {
    animation: none;
  }
}

/*
 * The particles are positioned by `requestAnimationFrame` in the component, not by CSS — their
 * speed is the measured request rate, so it cannot be a fixed keyframe duration. All this does is
 * make them visible; without a `fill` they were black dots on a black canvas, moving correctly and
 * invisibly.
 */
@media (prefers-reduced-motion: reduce) {
  .chain-particle,
  .depmap-packet {
    display: none;
  }
}

/* ----------------------------------------------------------------------- the radial view's chips */

/*
 * The focus sphere's group. It carries the blur filter in the markup, and this stops it swallowing
 * the pointer: it is drawn well outside its own circle by that filter, and an invisible 60%-larger
 * hit area over the middle of the canvas made the chips nearest the centre unclickable.
 */
.depmap-orb {
  pointer-events: none;
}

/*
 * The orb breathes. It is the one animation on this canvas that is not a measurement.
 *
 * Everything else that moves here is carrying a number — packet speed is the measured request rate,
 * the failure blink is an edge state. This is not, and it is allowed for one reason: it says the
 * canvas is live even when the cluster is quiet. A map with no measured traffic renders completely
 * still, which is indistinguishable from a screenshot, and "is this thing updating" is a question
 * people answer by looking rather than by reading. Slow and shallow enough that it never competes
 * with a packet: 4.5s, and 6% of opacity.
 */
@keyframes depmap-breathe {
  0%,
  100% {
    opacity: 0.94;
  }
  50% {
    opacity: 1;
  }
}

.depmap-orb-breathe {
  animation: depmap-breathe 4.5s var(--ease-out) infinite;
  transform-origin: center;
}

@media (prefers-reduced-motion: reduce) {
  .depmap-orb-breathe {
    animation: none;
  }
}

/*
 * The orb's two labels sit **on the orb**, not on the canvas, and take their contrast from it.
 *
 * `--map-ink-dim` is chosen to be quiet against near-black ground. Printed over the orb's bright
 * core it is quiet against the wrong thing: on a live render the eyebrow simply was not there, and
 * the name below it survived only because it is heavier. Both are light ink now, with the eyebrow
 * held back by opacity rather than by hue — the same demotion, against the surface it is actually
 * on.
 */
.depmap-orb-kind {
  fill: var(--map-ink);
  opacity: 0.72;
  font-size: 14px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.depmap-orb-name {
  fill: var(--map-ink);
  font-size: 21px;
  font-weight: 600;
}

.depmap-orb-sub {
  fill: var(--map-ink-dim);
  font-size: 15px;
}

.depmap-chip {
  cursor: pointer;
  transition: opacity 120ms var(--ease-out);
}
.depmap-chip:focus-visible {
  outline: none;
}
.depmap-chip:focus-visible .depmap-chip-name,
.depmap-chip:focus-visible .depmap-chip-quiet,
.depmap-chip.is-selected .depmap-chip-name,
.depmap-chip.is-selected .depmap-chip-quiet {
  fill: var(--map-ink);
  font-weight: 600;
}

.depmap-chip-name {
  fill: var(--map-ink);
  font-size: 17px;
  font-weight: 500;
}

/*
 * The quiet chips take `fill` from the element when the object is not `ok`, and fall through to
 * this when it is. That is why this is a colour and not `inherit`: an attribute-level `fill` wins
 * over an inherited value but loses to a class, so a class with `!important`-free `fill` here would
 * silently override the severity colour the component is passing in.
 */
.depmap-chip-quiet:not([fill]) {
  fill: var(--map-ink-dim);
}
.depmap-chip-quiet {
  font-size: 17px;
}

.depmap-bracket {
  fill: none;
  stroke-width: 1.4;
}

/*
 * The kind mark, in both layouts.
 *
 * `rx: 1` was the whole rule when this was a `<rect>` — it rounded the bar's corners. The element is
 * now a `<path>` whose shape comes from `marks.ts`, and `rx` does nothing to a path, so what is left
 * is the joint style: a square mark with mitred corners reads as sharper than a Node should at ten
 * pixels, and a chevron's point needs the round join or it renders as a spike.
 *
 * Fill and stroke are set per element rather than here, because both depend on state — the colour
 * channel — and on whether the mark is an outline. Putting either in CSS would mean a Service's ring
 * and a Pod's dot could not take the same class.
 */
.depmap-marker,
.chain-node-mark {
  stroke-width: 1.4;
  stroke-linejoin: round;
  stroke-linecap: round;
  /* The mark is identity, not a control: it must never take the pointer from the chip's hit area. */
  pointer-events: none;
}

/* ---------------------------------------------------------------- the capability form, in place
 *
 * The form that grants a policy opens inside the Policies card, above the table it adds a row to.
 * Its padding is `.card-body`'s, not a value of its own: the card is `padded={false}` because the
 * table underneath must reach the card's edges, so the one child that is *not* a table has to carry
 * the padding the body would have given it. A number picked independently here reads as a form
 * indented differently from every other form in the product.
 *
 * The rule below it is what says the form ends and the list begins. Without it the last control sits
 * directly on the table's first header cell and the two read as one block of interface.
 */
.policy-form {
  padding: var(--space-5);
  border-bottom: 1px solid var(--border-hairline);
}

/* ------------------------------------------------------------------ the map at window size
 *
 * ## Why an expanded mode exists at all, when the map already fills its column
 *
 * The column is not the constraint the reader is up against — the *aspect ratio* is. The radial
 * canvas is 1900x920, so a card 1350px wide can only ever be 653px tall, and the page spends the
 * 200-odd pixels above it on a page header, a summary strip and the card's own head. Everything
 * left over is where a 1,343-object cluster has to be legible. The drawing is scaled to whatever
 * that leaves, and so is every label on it: type on this canvas is in viewBox units, and shrinking
 * the box shrinks the words.
 *
 * Taking the map out of the column changes both terms. Fixed to the viewport it gets the window's
 * full width instead of the column's, and the header rows it was sharing the height with are simply
 * not drawn - roughly double the scale on the same screen, which for canvas type is the difference
 * between a label you can read and one you have to hover to identify.
 *
 * ## It is the card that expands, not a copy of the map
 *
 * The obvious build is an overlay containing a second `<DependencyGraph>`. That is two of everything
 * -- two selections, two hover states, two React trees polling the same query - and they disagree
 * the moment anybody clicks. Here the same card is moved: one element, one state, and closing it is
 * a class coming off. A reader who opened a namespace expanded is still in that namespace when they
 * come back out, because there was never a second map to be in.
 *
 * ## The canvas is positioned, not sized
 *
 * `height: 100%` on an `<svg>` inside a flex item is resolved against a parent whose height the flex
 * algorithm has not necessarily made definite, and the failure mode is the element collapsing to its
 * intrinsic size - a map that vanishes at the moment it was asked to get bigger. Absolute against a
 * relative parent has no such question in it: the box is the parent's box, and `preserveAspectRatio`
 * fits the drawing inside it centred.
 */

.depmap-card.is-full {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  margin: 0;
  border-radius: 0;
  display: flex;
  flex-direction: column;
  min-height: 0;
  /* The card is the whole window now, so a scroll gesture inside it must not reach the page it is
     covering - otherwise the map appears to drift while the reader is only hovering a chip. */
  overscroll-behavior: contain;
  /* Opaque, and no blur. A card is 94% of the surface token so that whatever is behind it bleeds
     through at the edges; across a whole viewport that same 6% is the page's own headings showing
     faintly through the map, which on this canvas reads as marks. The blur is dropped with it —
     compositing one over the entire window costs a full-screen pass on every frame of an animated
     edge, for an effect that is now behind an opaque fill anyway. */
  background: var(--bg-surface);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

.depmap-card.is-full > .card-head {
  flex: 0 0 auto;
}

.depmap-card.is-full > .card-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  flex: 1 1 auto;
  min-height: 0;
}

/* The drawing takes every pixel the head and the legend do not; both of those keep their own. */
.depmap-card.is-full .depmap-legend,
.depmap-card.is-full .panel-note {
  flex: 0 0 auto;
}

.depmap-card.is-full .depmap,
.depmap-card.is-full .chain-view {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
}

.depmap-card.is-full .depmap-canvas,
.depmap-card.is-full .chain-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-height: none;
}

/* ------------------------------------------------------------------------- the legend and picker */

.depmap-picker {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.depmap-legend {
  display: flex;
  align-items: center;
  gap: var(--space-3) var(--space-4);
  flex-wrap: wrap;
  font-size: var(--text-xs);
  color: var(--text-muted);
}

.depmap-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}

.depmap-swatch {
  flex: none;
  width: 10px;
  height: 3px;
  border-radius: var(--radius-full);
}

.depmap-swatch-ok {
  background: var(--ok);
}
.depmap-swatch-warn {
  background: var(--warn);
}
.depmap-swatch-critical {
  background: var(--critical);
}
.depmap-swatch-unknown {
  background: var(--text-muted);
}

/* ================================================================== progress
 *
 * "Progress and health are always visualised, never a bare number."
 *
 * The whole reason this section exists is blacklist item 2: an empty or dashed bar for missing data
 * looks broken, and a person reading a broken-looking bar goes and investigates a cluster that is
 * fine. There is no "empty" variant of `.bar` — missing data renders `.pill-unknown` reading
 * "not reported", which is a different shape, not a paler version of the same one.
 */

.bar-track {
  position: relative;
  height: var(--size-bar);
  border-radius: var(--radius-full);
  background: var(--bg-sunken);
  border: 1px solid var(--border-hairline);
  overflow: hidden;
}

.bar-fill {
  height: 100%;
  border-radius: var(--radius-full);
  background: var(--info);
  transition: width var(--motion-base) var(--ease-out);
}

.bar-fill-ok {
  background: var(--ok);
}
.bar-fill-info {
  background: var(--info);
}
.bar-fill-warning {
  background: var(--warn);
}
.bar-fill-critical {
  background: var(--critical);
}

.bar-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.bar-row .bar-track {
  flex: 1;
  min-width: 0;
}

/* The fraction or percentage beside the bar. Tabular so a bar animating from 61% to 100% does not
   shuffle the digits beside it. */
.bar-value {
  flex: none;
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  color: var(--text-secondary);
}

.bar-label {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-2);
}

.bar-name {
  font-size: var(--text-base);
  color: var(--text-primary);
}

.bar-detail {
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  color: var(--text-muted);
}

/* ================================================================== toasts
 *
 * Top-right, stacked, in the same language as the alert rail below: a large radius, a translucent
 * ground over a blur, and the theme's own surface colour.
 *
 * They used to be a hard-coded dark box with six hex colours in them, on the reasoning that a dark
 * surface reads as a system message rather than as one more card. That was defensible while it was
 * the only floating surface in the product. It is not any more — the rail and the bell float over
 * every page and are built from the theme's tokens — and a black box arriving over a light page beside
 * a translucent white rail does not read as "a system message", it reads as a component from a
 * different product. Separation now comes from the blur and the elevation, which is what separates
 * the other two.
 *
 * The stack is a live region, so a screen reader hears a toast without focus moving to it. It is
 * `polite` rather than `assertive` on purpose: a save confirmation interrupting someone mid-sentence
 * is worse than arriving a moment late.
 */

.toast-stack {
  position: fixed;
  right: var(--space-4);
  /* Below the topbar, not over it. The bar carries the cluster switcher and the account menu, and a
     notification that covers the control you would use to act on it is working against itself. */
  top: calc(var(--size-topbar) + var(--space-3));
  z-index: 1000;
  /* `column-reverse` against a top anchor puts the newest toast in the corner and pushes older ones
     down, so the position a person learns to glance at always holds the latest thing. Anchored at the
     bottom it meant the opposite — the newest arrival was the one furthest from the corner. */
  display: flex;
  flex-direction: column-reverse;
  gap: var(--space-2);
  /* The stack must not eat clicks on the page behind it; each toast re-enables them. */
  pointer-events: none;
}

.toast {
  pointer-events: auto;
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  width: var(--size-toast);
  max-width: calc(100vw - var(--space-8));
  padding: var(--space-3) var(--space-4);
  /* The bell's radius, because a toast is the same kind of object: a small panel that arrives over
     the page and leaves again. */
  border-radius: 18px;
  box-shadow: var(--elev-overlay);
  background: color-mix(in srgb, var(--color-surface) 92%, transparent);
  backdrop-filter: blur(20px) saturate(180%);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  border: 1px solid var(--border-hairline);
  color: var(--color-text);
  font-size: var(--text-base);
  animation: toast-in var(--motion-slow) var(--ease-out);
}

/* The severity on the left edge, the way an alert card in the rail carries it. A tinted panel would
   put several lines of text on a coloured field, which is hardest to read at the one moment it
   matters; a 3px edge is legible at a glance and costs the message nothing. */
.toast-ok { border-left: 3px solid var(--severity-ok-fill); }
.toast-warning { border-left: 3px solid var(--severity-warning-fill); }
.toast-critical { border-left: 3px solid var(--severity-critical-fill); }
.toast-info { border-left: 3px solid var(--severity-info-fill); }

/* Enters and leaves through the edge it is anchored to, so the motion reads as coming from off-screen
   rather than rising out of the page. */
@keyframes toast-in {
  from {
    opacity: 0;
    transform: translateY(-8px);
  }
}

.toast[data-leaving="true"] {
  opacity: 0;
  transform: translateY(-8px);
  transition:
    opacity var(--motion-slow) var(--ease-out),
    transform var(--motion-slow) var(--ease-out);
}

.toast-icon {
  flex: none;
  margin-top: 1px;
}
/* The same severity tokens every badge, pill and rail item reads, so a warning is one colour across
   the product rather than one colour per component. */
.toast-ok .toast-icon {
  color: var(--severity-ok-fg);
}
.toast-warning .toast-icon {
  color: var(--severity-warning-fg);
}
.toast-critical .toast-icon {
  color: var(--severity-critical-fg);
}
.toast-info .toast-icon {
  color: var(--severity-info-fg);
}

.toast-message {
  flex: 1;
  min-width: 0;
  line-height: var(--leading-body);
  overflow-wrap: anywhere;
}

.toast-action {
  background: none;
  border: 0;
  padding: 0;
  margin-top: var(--space-1);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--accent-text);
  text-align: left;
  text-decoration: underline;
  cursor: pointer;
}
.toast-action:hover,
.toast-action:focus-visible {
  color: var(--color-text);
}

/*
 * A 24px target around a 14px cross. It was 2px of padding — an 18px target, under WCAG 2.2's 24px
 * floor — on the one control that dismisses a critical toast, which never dismisses itself. The
 * negative margin hands the extra back to the layout, so the toast is no taller than it was.
 * `(pointer: coarse)` below takes it to 44px.
 */
.toast-close {
  flex: none;
  display: inline-grid;
  place-items: center;
  min-width: 24px;
  min-height: 24px;
  background: none;
  border: 0;
  padding: 5px;
  margin: -5px -5px -5px 0;
  color: var(--text-muted);
  border-radius: var(--radius-full);
  cursor: pointer;
}
.toast-close:hover,
.toast-close:focus-visible {
  color: var(--color-text);
}

/* A command-palette result (`command-palette.tsx`): an option the arrow keys move between, so a
   click-drag across rows must not select their text instead. */
.palette-option {
  -webkit-user-select: none;
     -moz-user-select: none;
          user-select: none;
}

/* The palette trigger's label gives way before the bar does: on a phone, beside the bell, it ends in
   an ellipsis rather than wrapping the trigger onto four lines. */
.palette-trigger-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

/*
 * Touch targets, for a finger rather than a cursor.
 *
 * The console's density is a desktop density: a 34px control, a 28px small one, 16px icon buttons in
 * an editor's tab strip. Under a coarse primary pointer — a phone, a tablet — those are targets a
 * thumb misses, and nothing here said so until now. The control sizes are raised at the token, which
 * reaches every `Button` and input because their heights are inline `var(--size-control*)` reads (an
 * inline value cannot be overridden from here, trap 30, but the variable it reads can). The coarse
 * values are tokens themselves (`size.controlCoarse` in `tokens.ts`), so there is still one source.
 *
 * Small fixed controls that cannot grow without moving their neighbours get a larger hit area instead
 * of a larger box: a transparent `::after` that extends past the visible control.
 */
@media (pointer: coarse) {
  :root {
    --size-control: var(--size-control-coarse);
    --size-control-sm: var(--size-control-sm-coarse);
  }
  .toast-close {
    min-width: 44px;
    min-height: 44px;
    margin: -15px -15px -15px 0;
  }
  .nav-toggle-btn {
    min-width: 44px;
    min-height: 44px;
    justify-content: center;
  }
  .rail-handle,
  .rail-chip {
    min-height: 44px;
  }
  .palette-trigger {
    min-height: var(--size-control-sm);
  }
  .palette-option {
    min-height: 44px;
    align-items: center;
  }
  /* A keyboard shortcut is noise on a device whose primary input is a finger. */
  .palette-trigger-kbd {
    display: none;
  }
  /* The editor's tab close sits between the tab's name and the next tab: 28 wide, 44 tall, so the
     wider target never takes a tap meant for the name beside it. */
  .cdw-tab-x {
    position: relative;
  }
  .cdw-tab-x::after {
    content: "";
    position: absolute;
    inset: -14px -8px -14px -4px;
  }
  /* The board-cell resize grip is in a corner; 32px covers it without reaching the cell's content. */
  .bw-grip::after {
    content: "";
    position: absolute;
    inset: -8px;
  }
}

/* ==========================================================================
   Density: Compact (the account menu's Density row; `applyDensity` in index.tsx)
   ==========================================================================

   Tighter rows, controls and gaps, set by overriding the scale the whole stylesheet already reads
   rather than by restyling components, so a page that spaces itself with `var(--space-N)` follows
   without being touched. The 4px step stays: it is the gap between an icon and its label, and shrinking
   it makes a label touch its icon. Fine pointers only — a finger needs the coarse sizes above, and a
   compact setting must not shrink a touch target below 44px. Comfortable is the bare `:root` and has no
   block of its own. */
@media (pointer: fine) {
  :root[data-density="compact"] {
    --space-2: 6px;
    --space-3: 9px;
    --space-4: 12px;
    --space-5: 15px;
    --space-6: 18px;
    --space-8: 24px;
    --space-10: 30px;
    --space-12: 36px;
    --size-row: 32px;
    --size-row-compact: 28px;
    --size-control: 28px;
    --size-control-sm: 24px;
  }
}

@media (max-width: 767px) {
  /* Full width, still under the bar. A thumb has to reach the dismiss control, but a toast that
     covers the navigation on a small screen leaves nowhere to go. */
  .toast-stack {
    left: var(--space-3);
    right: var(--space-3);
    top: calc(var(--size-topbar) + var(--space-2));
  }
  .toast {
    width: 100%;
  }
}

/* ---------------------------------------------------------------------------
   The alert rail, and the bell it shares its data with.

   These replaced a dialog in the middle of the window. The guarantee it carried — nothing leaves
   the screen without a recorded acknowledgement — is unchanged; what changed is that the product no
   longer sits on top of the console somebody is using to fix the thing it is shouting about. See
   `apps/portal/app/critical-alert.tsx` for the full argument.

   The visual language here is deliberately softer than the rest of the product: a large radius, a
   translucent ground and a blur, so the rail reads as a layer floating beside the page rather than
   as another panel *in* it. It is the only surface in the portal that persists across every route,
   and it has to be legible as something separate at a glance.
   --------------------------------------------------------------------------- */

.rail {
  /*
   * The lane the scrollbar occupies inside the list, so everything that is *not* the list can hold
   * the same right edge.
   *
   * Derived rather than typed: `--size-scrollbar` is the width the global `::-webkit-scrollbar` rule
   * reserves and `--space-2` is the list's own padding. Written as a literal 18px it would be right
   * until somebody changed either, and the symptom would be a column whose cards and captions stop
   * at two different places — the exact thing this variable exists to prevent.
   */
  --rail-lane: calc(var(--space-2) + var(--size-scrollbar));

  /*
   * The rail's accent, in one place.
   *
   * It holds criticals *and* warnings now, and a red frame over a list of nothing but warnings is
   * the product overstating what it found — which is how a colour stops being read. So the accent is
   * a variable set from the worst thing on the list, and every red in this section reads it instead
   * of naming the critical token directly. Individual rows still carry their own severity; this is
   * the frame around them.
   */
  --rail-accent: var(--severity-critical-fill);
  --rail-accent-fg: var(--severity-critical-fg);

  position: fixed;
  right: var(--space-4);
  /* Clear of the top bar, and floating rather than filling the edge — a column that ran the full
     height would read as a second sidebar, which is a different promise. */
  top: calc(var(--size-topbar) + var(--space-4));
  max-height: calc(100dvh - var(--size-topbar) - var(--space-8));
  width: min(340px, calc(100vw - var(--space-8)));
  z-index: var(--z-overlay);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
  /* The curve. 22px against the product's 6px is the whole point: this is not one of the page's
     panels and should not be mistaken for one. */
  border-radius: 22px;
  border: 1px solid var(--rail-accent);
  /* Translucent over a blur, so the page underneath stays perceptible — the rail is beside the
     work, not instead of it. `color-mix` keeps it on the theme's own surface colour in both
     palettes rather than hard-coding two rgba values that drift. */
  background: color-mix(in srgb, var(--color-surface) 86%, transparent);
  backdrop-filter: blur(20px) saturate(180%);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  box-shadow: var(--elev-overlay);
  animation: rail-in 220ms var(--ease-out);
}

/* Nothing critical on it: amber frame, amber heading, amber dot. The rows keep their own colours. */
.rail--warning,
.rail-handle--warning {
  --rail-accent: var(--severity-warning-fill);
  --rail-accent-fg: var(--severity-warning-fg);
}

/*
 * The page gives way to the rail rather than hiding under it.
 *
 * `data-rail="open"` is stamped on the root element by the rail itself. Only the scrolling region
 * moves — the top bar and the sidebar keep their geometry, because a rail that shifted the whole
 * frame would reflow the navigation every time a critical arrived, which is movement in the one
 * part of the screen that should never move under a cursor.
 *
 * Below the rail's own breakpoint it becomes a bottom sheet, so there is no column to reserve and
 * the padding must not survive — hence the media query rather than an unconditional rule.
 */
@media (min-width: 721px) {
  :root[data-rail="open"] .page-region {
    /* The rail's width plus its two margins. Written as one sum so a change to either is one edit. */
    padding-right: calc(min(340px, 100vw - var(--space-8)) + var(--space-6));
  }
}

/*
 * A minimised bastion shell is a tab low in the same corner (`.shell-tab`, `data-shell="minimized"`
 * from `shell-route.tsx`), and a full-height rail ran underneath it — the tab sat on top of the last
 * alert. The rail stops above the tab instead: its bottom offset plus its height plus a gap.
 */
@media (min-width: 721px) {
  :root[data-shell="minimized"] .rail {
    max-height: calc(100dvh - var(--size-topbar) - var(--space-4) - (var(--space-4) + 44px + 36px + var(--space-3)));
  }
}

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

/* Everything outside the scrolling list is inset by the list's scrollbar lane, so the whole column
   — heading, chips, captions, cards — shares one right edge. */
.rail-head,
.rail-note,
.rail-error {
  padding-right: var(--rail-lane);
}

.rail-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--space-3);
  flex: none;
}

.rail-head-text { flex: 1; min-width: 0; }

.rail-title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-sans);
  font-size: var(--text-lg);
  line-height: var(--leading-heading);
  letter-spacing: -0.01em;
  color: var(--rail-accent-fg);
}

/* The tally: one chip per severity in its own colour, then the cap in plain words. Counts the eye can
   find without reading the line, which a run of "71 critical · 86 warnings" in grey did not offer. */
.rail-sub {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-2);
  /* Its own row under the title and the chips, the full width of the column: beside Silence and
     Hide it had 176px and the two counts stacked one per line. */
  flex-basis: 100%;
  margin-top: calc(var(--space-1) * -1);
  font-size: var(--text-xs);
  color: var(--text-muted);
}
.rail-count {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 1px var(--space-2);
  border-radius: var(--radius-full);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.rail-count::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}
.rail-count--critical { background: var(--severity-critical-bg); color: var(--severity-critical-fg); }
.rail-count--warning { background: var(--severity-warning-bg); color: var(--severity-warning-fg); }
.rail-sub-cap { white-space: nowrap; }

/*
 * What arrived while the reader was looking.
 *
 * The rail is now the only surface a critical or a warning appears on — the toast that used to
 * announce one is gone, because it was a second copy of a row three inches to its right with no
 * Acknowledge on it. That makes an arrival easy to miss here: the list is worst-first, so a warning
 * that fired ten seconds ago lands underneath every critical, in a column the reader has already
 * been through.
 *
 * Deliberately not red or amber. Those two colours mean severity everywhere else in this product,
 * and a third meaning on the same surface is how a reader stops trusting the first two — so
 * "arrived just now" borrows the accent, which means nothing about how bad it is.
 */
.rail-fresh {
  /* `inline-block`, not `flex-start`: the head's text column is an ordinary block, so a pill that
     relied on `align-self` would silently stretch the full width of the column and stop reading as
     a pill at all. */
  display: inline-block;
  margin-top: var(--space-1);
  padding: 1px var(--space-2);
  border-radius: var(--radius-full);
  background: var(--accent-soft);
  color: var(--accent-text);
  font-size: var(--text-xs);
  font-weight: 600;
}

.rail-head-actions {
  display: flex;
  gap: var(--space-1);
  flex: none;
}

.rail-chip {
  padding: 2px var(--space-2);
  background: none;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  color: var(--text-muted);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  cursor: pointer;
}
.rail-chip:hover,
.rail-chip:focus-visible {
  color: var(--color-text);
  border-color: var(--color-border-strong);
}

/* The list scrolls; the rail does not grow. A node failure produces a burst of criticals and all of
   them have to be reachable without the box running off the bottom of the window. */
.rail-list {
  list-style: none;
  margin: 0;
  /* A lane for the bar, on the right only. Without it the thumb is drawn over the last few pixels of
     every alert summary — and on a translucent panel an overlay bar sitting on top of text is the
     one place it is least readable. `stable` keeps the lane whether or not the bar is showing, so
     the list does not reflow the moment a fourth alert makes it scroll. */
  padding: 0 var(--space-2) 0 0;
  overflow-y: auto;
  scrollbar-gutter: stable;
  /* Contained per `scroll.test.ts`: reaching the end of the list must not carry the gesture into
     the page behind the rail. */
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/*
 * The bar itself, on the rail and on the two lists that share its language.
 *
 * The global `*::-webkit-scrollbar` rules above already draw the house pill, and on a page-height
 * scroller that is enough. Inside these panels it was not: Blink paints an *overlay* bar on a small
 * scroller, which fades out when the pointer stops and leaves a list with 6,000px of content looking
 * like a list that ends. The whole reason the rail scrolls is that a node failure produces more
 * alerts than fit, so "there is more below" has to be visible without moving the mouse first.
 *
 * Only the thumb's colour and the track are set here. `scrollbar-width` and `scrollbar-color` are
 * deliberately absent — declaring either moves the element off Blink's `::-webkit-scrollbar` path
 * and onto its standard scrollbar, which draws stepper arrows; `scroll.test.ts` fails on a stray
 * one outside the feature query.
 */
.rail-list::-webkit-scrollbar-track,
.bell-list::-webkit-scrollbar-track,
.shift-list::-webkit-scrollbar-track {
  background: transparent;
}

.rail-list::-webkit-scrollbar-thumb,
.bell-list::-webkit-scrollbar-thumb,
.shift-list::-webkit-scrollbar-thumb {
  /* One step stronger than the global thumb, because these sit on a sunken or translucent ground
     rather than on the page, and `--border-strong` disappears into both. */
  background-color: var(--text-muted);
  border-radius: var(--radius-full);
  /* 2px of transparent border with `background-clip: padding-box` is what the global rule does: a
     6px visible pill inside a 10px grabbable lane. */
  border: 2px solid transparent;
  background-clip: padding-box;
}

.rail-list::-webkit-scrollbar-thumb:hover,
.bell-list::-webkit-scrollbar-thumb:hover,
.shift-list::-webkit-scrollbar-thumb:hover {
  background-color: var(--text-secondary);
}

.rail-item {
  position: relative;
  padding: 10px var(--space-3) 10px calc(var(--space-3) + 2px);
  border-radius: 12px;
  background: var(--bg-sunken);
  border: 1px solid transparent;
  transition: border-color var(--motion-fast) var(--ease-out);
  /* The severity is carried by the left edge rather than by a fill: several lines of text on a
     coloured field is harder to read at the exact moment reading it matters most. Per row, not per
     rail — the list is mixed, and a reader scanning it has to be able to tell which is which
     without reading a word. */
  --rail-item-sev: var(--severity-warning-fill);
  --rail-item-sev-fg: var(--severity-warning-fg);
}
.rail-item:hover { border-color: var(--color-border); }

.rail-item--critical {
  --rail-item-sev: var(--severity-critical-fill);
  --rail-item-sev-fg: var(--severity-critical-fg);
}

/* The severity bar, inset from the corners rather than drawn as a border: a left border on a 12px
   radius bends round both corners and reads as a bracket, not as a mark. */
.rail-item::before {
  content: "";
  position: absolute;
  left: 0;
  top: 10px;
  bottom: 10px;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: var(--rail-item-sev);
}

.rail-item-top {
  grid-area: top;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  font-size: var(--text-xs);
}
.rail-item-sev {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--rail-item-sev-fg);
}
.rail-item-age {
  margin-left: auto;
  color: var(--text-muted);
  white-space: nowrap;
}

/* The row's half of the same marker. A ring rather than a fill: the card already carries its
   severity on its left edge and its text has to stay the most readable thing on it. */
.rail-item--new {
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent);
}

.rail-new {
  display: inline-block;
  padding: 0 6px;
  border-radius: var(--radius-full);
  background: var(--accent-soft);
  color: var(--accent-text);
  font-size: var(--text-xs);
  font-weight: 600;
  /* Uppercase at this size reads as a label rather than as the first word of the summary under
     it, which is the mistake a bare "New" in sentence case makes on a card of running prose. */
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.rail-item-summary {
  display: block;
  margin-top: var(--space-1);
  font-size: var(--text-sm);
  font-weight: 500;
  line-height: 1.45;
  color: var(--color-text);
  overflow-wrap: break-word;
}

/* The rest of the summary — the explanation behind the headline — once the row is opened. */
.rail-item-why {
  margin: 0;
  font-size: var(--text-xs);
  line-height: var(--leading-body);
  color: var(--text-secondary);
  overflow-wrap: break-word;
}

.rail-item-where,
.rail-item-meta {
  margin-top: var(--space-1);
  font-size: var(--text-xs);
  color: var(--text-muted);
  overflow-wrap: break-word;
}

/* The disclosure that wraps an alert's headline.
   A button reset rather than a styled button: it carries no chrome of its own because it *is* the
   row's first two lines, and anything that made it look like a control would put a second visual
   weight on a card whose left edge is already saying how bad this is. The chevron is the only mark,
   and it is the whole affordance. */
.rail-item-toggle {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "top chevron" "summary summary" "where where";
  align-items: center;
  -moz-column-gap: var(--space-2);
       column-gap: var(--space-2);
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.rail-item-toggle > .rail-item-summary { grid-area: summary; }
.rail-item-toggle > .rail-item-where { grid-area: where; }

/* Rotates to point down when the row is open, which is the only thing on the card that changes
   state — the summary un-clamping is easy to miss on a two-line alert whose text is barely longer. */
.rail-item-toggle::after {
  content: "";
  grid-area: chevron;
  align-self: center;
  margin: 0 2px 2px var(--space-1);
  width: 6px;
  height: 6px;
  border-right: 1.5px solid var(--text-muted);
  border-bottom: 1.5px solid var(--text-muted);
  transform: rotate(-45deg);
  transition: transform var(--motion-fast) var(--ease-out);
}
.rail-item-toggle[aria-expanded="true"]::after {
  transform: rotate(45deg);
}
.rail-item-toggle:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 4px;
  border-radius: 6px;
}
.rail-item-toggle:hover::after {
  border-color: var(--color-text);
}

/* The headline, held to two lines while collapsed.
   `-webkit-line-clamp` is the only property that clamps by line rather than by height, and it is
   supported everywhere this ships despite the prefix — a max-height in em would cut mid-line on any
   summary whose wrap lands differently, which is most of them. */
.rail-item-summary.is-clamped {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* The location line, held to one line while collapsed.
   Without this a long namespace and object name wrap to two, so collapsed rows are three lines or
   four depending on how the name happens to break — and a list whose rows are different heights for
   no reason the reader can see is harder to scan than one with slightly less text in it. Expanded,
   it wraps freely: by then the reader is on this alert and wants the whole name. */
.rail-item-toggle[aria-expanded="false"] > .rail-item-where {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Everything behind the click. Spaced from the headline so the row reads as two parts rather than
   as one that grew. */
.rail-item-detail {
  margin-top: var(--space-2);
  padding-top: var(--space-2);
  border-top: 1px solid var(--color-border);
}

/* The cluster's own words. Mono and sunken, so the reader can tell what this product is saying from
   what Kubernetes said; scrolled sideways rather than wrapped, because a pod UID is one token and a
   wrap breaks it wherever the box happens to end. */
.rail-reported {
  margin: var(--space-2) 0 0;
  padding: var(--space-2);
  background: var(--bg-page);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: var(--leading-body);
  color: var(--color-text);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  white-space: pre;
}

.rail-item-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

/*
 * A shorter bar for the quoted machine text.
 *
 * These blocks are ~260px wide inside the rail, and the product's 10px scrollbar across the foot of
 * one is a third the height of the two lines above it — it reads as a component rather than as the
 * hint that the line runs on. Height only: the width, the thumb colour and the pill radius stay the
 * global ones, so it is the same bar, shorter.
 */
.rail-reported::-webkit-scrollbar {
  height: 6px;
}

.rail-ack {
  padding: 4px var(--space-3);
  border: none;
  border-radius: var(--radius-full);
  /* The button's own row decides its colour, so acknowledging a warning does not look like
     acknowledging a node failure. */
  background: var(--severity-warning-fill);
  color: #FFFFFF;
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: 500;
  cursor: pointer;
}
.rail-ack:hover:not(:disabled),
.rail-ack:focus-visible:not(:disabled) { filter: brightness(1.1); }
.rail-ack:disabled { opacity: 0.5; cursor: not-allowed; }

.rail-link,
.bell-link {
  padding: 0;
  border: none;
  background: none;
  color: var(--accent-text);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  text-decoration: underline;
  cursor: pointer;
}

.rail-note,
.bell-note {
  flex: none;
  font-size: var(--text-xs);
  line-height: var(--leading-body);
  color: var(--text-muted);
}

.rail-item--critical .rail-ack {
  background: var(--severity-critical-fill);
}

/* Where Open goes: a quiet outlined pill beside Acknowledge rather than an underlined link, so the
   two actions read as a pair and the one that writes stays the one with the fill. */
.rail-open {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px var(--space-2) 3px var(--space-3);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-full);
  background: none;
  color: var(--color-text);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: 500;
  cursor: pointer;
}
.rail-open:hover,
.rail-open:focus-visible { border-color: var(--color-text); }
.rail-open svg { width: 10px; height: 10px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }

.rail-error,
.bell-error {
  flex: none;
  font-size: var(--text-xs);
  line-height: var(--leading-body);
  color: var(--severity-critical-fg);
}

/* Collapsed: a handle on the right edge that still carries the count. Collapsing is "I am dealing
   with this", never "this is gone", so the number does not disappear with the list. */
/* Bottom right, not under the rail's own top corner: the expanded rail reserves a column and the
   handle does not, so a handle in the top-right sits on top of whatever the page header put there —
   on every board that is the Refresh control and the "last updated" line. The bottom-right corner is
   free (`.rail-paused` is the only other thing that claims it, and the two are mutually exclusive)
   and the assistant's launcher is bottom-centre. */
.rail-handle {
  --rail-accent: var(--severity-critical-fill);
  --rail-accent-fg: var(--severity-critical-fg);

  position: fixed;
  right: var(--space-4);
  bottom: var(--space-4);
  z-index: var(--z-overlay);
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-full);
  border: 1px solid var(--rail-accent);
  background: color-mix(in srgb, var(--color-surface) 86%, transparent);
  backdrop-filter: blur(20px) saturate(180%);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  box-shadow: var(--elev-overlay);
  color: var(--rail-accent-fg);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  cursor: pointer;
}
.rail-handle:hover,
.rail-handle:focus-visible { border-color: var(--rail-accent-fg); }

.rail-handle-count { font-weight: 600; }
.rail-handle-word { color: var(--text-muted); font-size: var(--text-xs); }

/*
 * The standing "alerts are stopped" marker.
 *
 * Muted rather than alarming, and deliberately so: this is a state the user chose, and a red banner
 * shouting about a deliberate decision is the kind of nagging that gets a product's warnings ignored
 * generally. It has to be present and unmissable-once-looked-for, not loud.
 */
.rail-paused {
  position: fixed;
  right: var(--space-4);
  bottom: var(--space-4);
  z-index: var(--z-overlay);
  padding: var(--space-2) var(--space-3);
  background: var(--color-surface);
  border: 1px dashed var(--color-border-strong);
  border-radius: var(--radius-full);
  color: var(--text-muted);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  cursor: pointer;
}
.rail-paused:hover {
  color: var(--color-text);
  border-color: var(--severity-critical-fill);
}

/* The visual counterpart of the tone, for a muted tab or a deaf user — the alarm must not be
   audible-only, or the whole feature reaches one kind of person and not another. */
.rail-pulse {
  flex: none;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--severity-critical-fill);
  animation: rail-pulse 1.4s var(--ease-out) infinite;
}

/* Amber, and no movement. The pulse is the visual half of an alarm and there is no alarm for a
   warning — a dot beating away over a list nothing is sounding for would be the animation making a
   claim the sound does not. */
.rail-pulse--warning {
  background: var(--severity-warning-fill);
  animation: none;
}

@keyframes rail-pulse {
  50% { opacity: 0.25; transform: scale(0.8); }
}

@media (prefers-reduced-motion: reduce) {
  /* Still visible, still red, no movement. Removing the element would take the non-audible half of
     the signal away from the people most likely to have this setting on. */
  .rail-pulse { animation: none; }
  .rail { animation: none; }
}

/* On a narrow window the rail becomes a bottom sheet: a 340px column arriving from the side of a
   390px viewport is the modal this replaced, with extra steps. */
@media (max-width: 720px) {
  /* The insets are the home indicator and the rounded corners; they are 0 until the page opts into
     `viewport-fit=cover`, and correct either way. On a phone the sheet opens only when its handle is
     tapped — see `critical-alert.tsx` for why it no longer opens itself. */
  .rail {
    right: calc(var(--space-2) + env(safe-area-inset-right, 0px));
    left: calc(var(--space-2) + env(safe-area-inset-left, 0px));
    top: auto;
    bottom: calc(var(--space-2) + env(safe-area-inset-bottom, 0px));
    width: auto;
    max-height: 60dvh;
  }
  .rail-handle,
  .rail-paused {
    right: calc(var(--space-2) + env(safe-area-inset-right, 0px));
    bottom: calc(var(--space-2) + env(safe-area-inset-bottom, 0px));
  }
}

/*
 * A new critical on a phone: the handle says so, briefly, instead of the sheet re-opening over the
 * page. Two pulses of the frame and a "2 new" in place of "unacknowledged"; the component clears it
 * after a few seconds. Reduced motion keeps the words and the colour and drops the movement.
 */
.rail-handle--cue {
  border-color: var(--rail-accent-fg);
  animation: rail-cue 0.9s var(--ease-out) 2;
}
.rail-handle--cue .rail-handle-word {
  color: var(--rail-accent-fg);
  font-weight: 600;
}
@keyframes rail-cue {
  50% { transform: scale(1.06); }
}
@media (prefers-reduced-motion: reduce) {
  .rail-handle--cue { animation: none; }
}

/* --------------------------------------------------------------- the bell */

.bell { position: relative; }

.bell-trigger {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-1);
  height: var(--size-control-sm);
  padding: 0 var(--space-2);
  border: none;
  border-radius: var(--radius-md);
  background: none;
  color: var(--color-text-muted);
  font-family: var(--font-sans);
  cursor: pointer;
  transition: background-color var(--motion-fast) var(--ease-out);
}
.bell-trigger:hover,
.bell-trigger:focus-visible,
.bell-trigger[aria-expanded="true"] { background-color: var(--color-hover); color: var(--color-text); }

/* The bastion button beside the bell (`ShellLauncher` in `shell-route.tsx`) wears the bell's trigger and
   stays lit while the shell window is up, so the bar says whether it is the thing that will hide it. */
.shell-launcher[aria-pressed="true"] { background-color: var(--color-hover); color: var(--color-text); }

.bell-trigger--paused { color: var(--text-muted); }

/*
 * A new critical has arrived — the fourth animation in the product that carries meaning.
 *
 * It reuses `pill-pulse` rather than defining a blink of its own, so there is still one pulse
 * keyframe here and it keeps the `prefers-reduced-motion` treatment the others have: slowed, never
 * frozen. Faster than the live-dot's 1.6s because this one is announcing an event and has a dozen
 * seconds to be noticed in, where the dot is describing a state that will still be true later.
 *
 * The class is added and removed by `alert-bell.tsx`, which is also where the reason it is finite is
 * argued. `infinite` here is bounded by that timer, not by the animation.
 */
.bell-trigger--new {
  animation: pill-pulse 1s var(--ease-out) infinite;
}

@media (prefers-reduced-motion: reduce) {
  .bell-trigger--new {
    animation-duration: 2.4s !important;
  }
}

/* The count, not a dot. A dot says "something"; the number is the difference between glancing and
   having to open the panel to find out whether it matters. */
.bell-count {
  min-width: 16px;
  padding: 0 4px;
  border-radius: var(--radius-full);
  background: var(--color-border-strong);
  color: var(--color-text);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  line-height: 16px;
  text-align: center;
}
.bell-count--critical { background: var(--severity-critical-fill); color: #FFFFFF; }

.bell-paused-dot {
  width: 6px;
  height: 6px;
  border-radius: var(--radius-full);
  border: 1px dashed var(--color-border-strong);
}

/*
 * Fixed and portalled to `document.body`, with `top`/`right` set inline from the trigger's own box.
 *
 * It cannot be `position: absolute` under the bell any more: `.app-topbar` has a z-index and is
 * therefore a stacking context, so a dropdown inside it can never rise above the alert rail on the
 * overlay layer — it opened behind the rail and was clipped. See `apps/portal/app/alert-bell.tsx`
 * for why moving the panel beat re-layering the shell.
 */
.bell-panel {
  position: fixed;
  /*
   * One above the rail, which shares the overlay layer and is later in the DOM.
   *
   * They occupy the same corner of the screen by design — the bell is the summary and the rail is
   * the list — so at equal z-index the rail won and the dropdown opened behind it, cut off down its
   * right-hand edge. The bell is transient and was just opened by a deliberate click; the rail is
   * standing. The thing somebody has this second asked for goes on top.
   */
  z-index: calc(var(--z-overlay) + 1);
  width: min(380px, calc(100vw - var(--space-8)));
  max-height: min(520px, 70dvh);
  display: flex;
  flex-direction: column;
  padding: var(--space-3);
  gap: var(--space-2);
  border: 1px solid var(--border-hairline);
  /* Same curve as the rail: the two are one surface shown two ways, and a reader should recognise
     the second from the first. */
  border-radius: 18px;
  background: color-mix(in srgb, var(--bg-surface) 92%, transparent);
  backdrop-filter: blur(20px) saturate(180%);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
}

.bell-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  flex: none;
}
.bell-title { font-size: var(--text-md); font-weight: 600; color: var(--color-text); }
.bell-sub { font-size: var(--text-xs); color: var(--text-muted); }

.bell-list {
  list-style: none;
  margin: 0;
  padding: 0 var(--space-2) 0 0;
  overflow-y: auto;
  scrollbar-gutter: stable;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.bell-item {
  display: flex;
  gap: var(--space-2);
  padding: var(--space-2);
  border-radius: 12px;
  background: var(--bg-sunken);
}

.bell-dot {
  flex: none;
  width: 8px;
  height: 8px;
  margin-top: 5px;
  border-radius: 50%;
  background: var(--severity-critical-fill);
}

.bell-item-body { min-width: 0; flex: 1; }

.bell-item-summary {
  font-size: var(--text-sm);
  line-height: var(--leading-body);
  color: var(--color-text);
  overflow-wrap: break-word;
}

.bell-item-where {
  margin-top: 2px;
  font-size: var(--text-xs);
  color: var(--text-muted);
  overflow-wrap: break-word;
}

.bell-item-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

.bell-ack {
  padding: 3px var(--space-2);
  border: none;
  border-radius: var(--radius-full);
  background: var(--severity-critical-fill);
  color: #FFFFFF;
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  cursor: pointer;
}
.bell-ack:disabled { opacity: 0.5; cursor: not-allowed; }

.bell-empty { padding: var(--space-2); }
.bell-empty-lead { font-size: var(--text-sm); color: var(--color-text); }
.bell-empty-body {
  margin-top: var(--space-1);
  font-size: var(--text-xs);
  line-height: var(--leading-body);
  color: var(--text-muted);
}

.bell-foot {
  flex: none;
  width: 100%;
  padding: var(--space-2);
  border: none;
  border-top: 1px solid var(--border-hairline);
  border-radius: 0;
  background: none;
  color: var(--color-text);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  text-align: left;
  cursor: pointer;
}
.bell-foot:hover, .bell-foot:focus-visible { color: var(--accent-text); }

/*
 * The alert you were sent to.
 *
 * "Open alert" scrolls this card into the middle of the viewport, and a scroll on its own is not an
 * answer to "which one" — the reader arrives at a list of similar rows with no idea which of them the
 * page moved for. The ring names it.
 *
 * It fades rather than latching. A permanent ring would still be there twenty minutes later claiming
 * an alert is the current one long after the reader moved on, and it would persist through the list
 * refetching underneath it. Three seconds is long enough to be seen after the smooth scroll settles
 * and short enough that the page returns to reading normally.
 */
/*
 * An alert, as a card: what fired, what it means, what can be done — top to bottom.
 *
 * The actions were a column beside the text: every card grew to the height of its button stack,
 * bordered buttons sat over bare centred text, and the column changed width from card to card with
 * the object's name. They are one row under a hairline now, the acting controls first and the two
 * questions quiet at its end. The severity and the incident are the only coloured marks; where the
 * alert came from, its cluster, rule and age are one muted line.
 */
.alc { display: grid; gap: var(--space-3); min-width: 0; }
.alc-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); min-width: 0; }
.alc-facts { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 0 var(--space-2); min-width: 0; font-size: var(--text-sm); color: var(--text-muted); }
/* The separator trails its item, so a wrapped line never opens with a stray dot (see `.advice-foot`). */
.alc-facts > span:not(:last-child)::after { content: "\00B7"; margin-left: var(--space-2); }
.alc-incident { margin-left: auto; }
.alc-summary { margin: 0; font-size: var(--text-md); line-height: var(--leading-relaxed); color: var(--text-primary); overflow-wrap: anywhere; }
.alc-forecast { margin: 0; font-size: var(--text-sm); color: var(--text-muted); }
.alc-explain { display: grid; gap: var(--space-1); padding: var(--space-3) var(--space-4); border: 1px solid var(--border-hairline); border-left: 3px solid var(--info); border-radius: var(--radius-md); background: var(--bg-sunken); }
.alc-explain.is-partial { border-left-color: var(--warn); }
.alc-explain-head { display: flex; align-items: center; gap: 6px; font-size: var(--text-sm); color: var(--text-muted); }
.alc-explain-head strong { font-weight: 600; color: var(--text-secondary); }
.alc-explain-head svg { flex: none; color: var(--info-text); }
.alc-explain.is-partial .alc-explain-head svg { color: var(--warn-text); }
.alc-explain .ai-prose { font-size: var(--text-base); color: var(--text-primary); overflow-wrap: anywhere; }
.alc-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); padding-top: var(--space-3); border-top: 1px solid var(--border-hairline); }
.alc-actions-quiet { display: inline-flex; flex-wrap: wrap; gap: var(--space-1); margin-left: auto; }
/* An object name can be long. The summary above already names it in full and the title carries it,
   so the button keeps the row on one line rather than pushing the questions onto a second. */
.alc-open { max-width: min(100%, 360px); min-width: 0; }
.alc-open > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.alc-resolved { opacity: 0.6; }

/* Incidents on the list, grouped by severity: one surface per group, one line of trouble per row. */
.inc-list { display: grid; gap: var(--space-5); }
.inc-group { --inc-tone: var(--info); --inc-tone-soft: var(--info-soft); --inc-tone-text: var(--info-text); display: grid; gap: var(--space-2); }
.inc-group[data-severity="critical"] { --inc-tone: var(--critical); --inc-tone-soft: var(--critical-soft); --inc-tone-text: var(--critical-text); }
.inc-group[data-severity="warning"] { --inc-tone: var(--warn); --inc-tone-soft: var(--warn-soft); --inc-tone-text: var(--warn-text); }
.inc-group-head { display: flex; align-items: center; gap: var(--space-2); margin: 0; font-size: var(--text-xs); font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--inc-tone-text); }
.inc-group-dot { width: 8px; height: 8px; border-radius: var(--radius-full); background: var(--inc-tone); box-shadow: 0 0 0 3px var(--inc-tone-soft); }
.inc-group-count { padding: 1px 7px; border-radius: var(--radius-full); background: var(--inc-tone-soft); color: var(--inc-tone-text); letter-spacing: 0; }
.inc-rows { list-style: none; margin: 0; padding: 0; border: 1px solid var(--border-hairline); border-left: 3px solid var(--inc-tone); border-radius: var(--radius-lg); background: var(--bg-surface); overflow: hidden; }
.inc-rows > li + li { border-top: 1px solid var(--border-hairline); }
.inc-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: var(--space-3); padding-right: var(--space-4); transition: background-color 0.12s ease; }
.inc-row:hover { background: var(--bg-hover); }
.inc-row-main { display: grid; grid-template-columns: 56px minmax(140px, 15rem) minmax(0, 1fr); align-items: center; gap: var(--space-4); min-width: 0; padding: var(--space-3) 0 var(--space-3) var(--space-4); border: 0; background: none; font: inherit; color: inherit; text-align: left; cursor: pointer; }
.inc-row-main:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: var(--radius-md); }
.inc-age { display: grid; justify-items: start; line-height: 1.1; }
.inc-age-n { font-size: var(--text-md); font-weight: 600; color: var(--inc-tone-text); }
.inc-age-l { font-size: var(--text-xs); color: var(--text-muted); }
.inc-who { display: grid; gap: 2px; min-width: 0; }
.inc-row-name { font-size: var(--text-base); font-weight: 600; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.inc-row-meta { display: flex; flex-wrap: wrap; gap: 0 6px; font-size: var(--text-xs); color: var(--text-muted); }
.inc-row-meta > span:not(:last-child)::after { content: "\00B7"; margin-left: 6px; }
.inc-what { display: grid; gap: 2px; min-width: 0; }
.inc-what-head { font-size: var(--text-base); font-weight: 500; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.inc-what-detail { font-size: var(--text-sm); color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.inc-fig { font-weight: 600; color: var(--text-primary); }
.inc-row-actions { display: flex; align-items: center; gap: var(--space-2); }
.inc-ack { display: inline-flex; align-items: center; gap: 6px; height: 30px; padding: 0 12px; border: 1px solid var(--ok); border-radius: var(--radius-full); background: var(--ok-soft); font: inherit; font-size: var(--text-sm); font-weight: 600; color: var(--ok-text); cursor: pointer; transition: background-color 0.12s ease, color 0.12s ease; }
.inc-ack:hover:not(:disabled) { background: var(--ok); color: var(--on-accent, #fff); }
.inc-ack:focus-visible { outline: 2px solid var(--ok); outline-offset: 2px; }
.inc-ack:disabled { opacity: 0.6; cursor: progress; }
@media (prefers-reduced-motion: reduce) { .inc-ack { transition: none; } }
.inc-quiet { margin: 0; padding: var(--space-3) var(--space-4); border: 1px dashed var(--border-hairline); border-radius: var(--radius-lg); font-size: var(--text-sm); color: var(--text-muted); }
.inc-held { padding: 2px 8px; border-radius: var(--radius-full); background: var(--info-soft); color: var(--info-text); font-size: var(--text-xs); font-weight: 500; }
.inc-go { display: inline-flex; align-items: center; gap: 6px; height: 30px; padding: 0 12px; border: 1px solid var(--border-hairline); border-radius: var(--radius-full); background: var(--bg-surface); font: inherit; font-size: var(--text-sm); font-weight: 600; color: var(--accent-text); cursor: pointer; transition: background-color 0.12s ease, border-color 0.12s ease, color 0.12s ease; }
.inc-go > span:last-child { transition: transform 0.12s ease; }
.inc-row:hover .inc-go, .inc-go:focus-visible { border-color: var(--accent-solid); background: var(--accent-solid); color: var(--on-accent); }
.inc-row:hover .inc-go > span:last-child { transform: translateX(2px); }
.inc-go:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (max-width: 900px) {
  .inc-row-main { grid-template-columns: 48px minmax(0, 1fr); }
  .inc-what, .alr-what { grid-column: 1 / -1; }
  .inc-what-head, .inc-what-detail { white-space: normal; }
}
@media (max-width: 560px) {
  .inc-row { grid-template-columns: minmax(0, 1fr); padding: 0 var(--space-4) var(--space-3) 0; }
  .inc-row-actions { padding-left: var(--space-2); }
}
@media (prefers-reduced-motion: reduce) { .inc-row, .inc-go, .inc-go > span:last-child { transition: none; } }

/* Firing alerts use the incident list's rows (alerts/alerts-panel.tsx); these are the alert-only parts. */
.alr-what { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; min-width: 0; font-size: var(--text-sm); line-height: var(--leading-relaxed); color: var(--text-primary); overflow-wrap: anywhere; }
.alr-incident { max-width: 14rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 2px 8px; border: 1px solid var(--border-hairline); border-radius: var(--radius-full); font-size: var(--text-xs); color: var(--text-secondary); }
.alr-incident::before { content: "incident \00B7  "; color: var(--text-muted); }
.alr-more { justify-self: start; padding: 4px 0; border: 0; background: none; font: inherit; font-size: var(--text-sm); font-weight: 600; color: var(--accent-text); cursor: pointer; }
.alr-more:hover { text-decoration: none; }
.alr-more:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
@media (max-width: 1100px) { .alr-incident { display: none; } }

/* Rules and built-in checks on the Alerts page (alerts/rules-panel.tsx): one surface, one row per rule. */
.rule-section-head { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: var(--space-3); }
.rule-section-title { display: flex; align-items: center; gap: var(--space-2); margin: 0; font-size: var(--text-md); font-weight: 600; color: var(--text-primary); }
.rule-section-title .inc-group-count { background: var(--bg-sunken); color: var(--text-secondary); font-size: var(--text-xs); }
.rule-section-sub { margin: var(--space-1) 0 0; max-width: 72ch; font-size: var(--text-sm); color: var(--text-muted); }
.rule-section-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.rule-rows { list-style: none; margin: 0; padding: 0; border: 1px solid var(--border-hairline); border-radius: var(--radius-lg); background: var(--bg-surface); overflow: hidden; }
.rule-row { --rule-tone: var(--info); display: grid; grid-template-columns: 10px minmax(0, 1fr) minmax(9rem, 12rem) auto; align-items: center; gap: var(--space-4); padding: var(--space-3) var(--space-4); transition: background-color 0.12s ease; }
.rule-row + .rule-row { border-top: 1px solid var(--border-hairline); }
.rule-rows > .rule-row:hover { background: var(--bg-hover); }
.rule-row[data-severity="critical"] { --rule-tone: var(--critical); }
.rule-row[data-severity="warning"] { --rule-tone: var(--warn); }
.rule-row.is-off { color: var(--text-muted); }
.rule-row.is-off .rule-name, .rule-row.is-off .rule-cond, .rule-row.is-off .rule-now-v { opacity: 0.55; }
.rule-dot { align-self: start; margin-top: 6px; width: 10px; height: 10px; border-radius: var(--radius-full); background: var(--rule-tone); }
.rule-row.is-off .rule-dot { background: transparent; box-shadow: inset 0 0 0 2px var(--rule-tone); opacity: 0.6; }
.rule-body { display: grid; gap: 4px; min-width: 0; }
.rule-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1) var(--space-2); min-width: 0; }
.rule-name { font-size: var(--text-base); font-weight: 600; color: var(--text-primary); }
.rule-sev { font-size: var(--text-xs); font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); }
.rule-row[data-severity="critical"] .rule-sev { color: var(--critical-text); }
.rule-row[data-severity="warning"] .rule-sev { color: var(--warn-text); }
.rule-cond { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-secondary); overflow-wrap: anywhere; }
.rule-scope { font-size: var(--text-xs); color: var(--text-muted); overflow-wrap: anywhere; }
.rule-desc { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; margin: 2px 0 0; font-size: var(--text-sm); line-height: var(--leading-relaxed); color: var(--text-muted); }
.rule-now { display: grid; gap: 1px; justify-items: end; text-align: right; }
.rule-now-v { font-size: var(--text-base); font-weight: 600; color: var(--text-primary); }
.rule-now-l { font-size: var(--text-xs); color: var(--text-muted); }
.rule-actions { display: flex; align-items: center; gap: var(--space-1); }
.rule-actions > * { flex-shrink: 0; }
.rule-actions .switch { margin: 0 var(--space-2); }
.rule-fold > summary { display: flex; align-items: center; gap: var(--space-2); width: -moz-fit-content; width: fit-content; padding: var(--space-2) var(--space-3); border: 1px solid var(--border-hairline); border-radius: var(--radius-full); cursor: pointer; list-style: none; font-size: var(--text-sm); font-weight: 600; color: var(--text-secondary); }
.rule-fold > summary::-webkit-details-marker { display: none; }
.rule-fold > summary::before { content: "\25B8"; color: var(--text-muted); transition: transform 0.15s ease; }
.rule-fold[open] > summary::before { transform: rotate(90deg); }
.rule-fold[open] > summary { margin-bottom: var(--space-3); }
.rule-fold > summary:hover { background: var(--bg-hover); }
.rule-fold > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.rule-fold-meta { font-weight: 400; color: var(--text-muted); }
@media (prefers-reduced-motion: reduce) { .rule-fold > summary::before { transition: none; } }
.rule-delete { color: var(--critical-text); }
.rule-rows .rule-row:not(:has(.rule-now)) { grid-template-columns: 10px minmax(0, 1fr) auto; }
.rule-silent { margin-top: var(--space-2); }
.rule-silent > summary { cursor: pointer; font-weight: 600; }
.rule-silent > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
.rule-silent ul { display: flex; flex-wrap: wrap; gap: var(--space-1); margin: var(--space-2) 0 0; padding: 0; list-style: none; }
.rule-silent li { padding: 1px 8px; border: 1px solid var(--border-hairline); border-radius: var(--radius-full); background: var(--bg-surface); font-size: var(--text-xs); }
@media (max-width: 980px) {
  .rule-row { grid-template-columns: 10px minmax(0, 1fr); }
  .rule-now { grid-column: 2; justify-items: start; text-align: left; }
  .rule-actions { grid-column: 2; flex-wrap: wrap; }
}
@media (prefers-reduced-motion: reduce) { .rule-row { transition: none; } }

/* Notification settings (alerts/notification-settings.tsx): one row per setting, its control on the right. */
.nset { display: grid; gap: var(--space-4); }
.nset-lead { justify-self: start; font-size: var(--text-sm); }
.nset-rows { list-style: none; margin: 0; padding: 0; border: 1px solid var(--border-hairline); border-radius: var(--radius-lg); overflow: hidden; }
.nset-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: var(--space-5); padding: var(--space-4); }
.nset-row + .nset-row { border-top: 1px solid var(--border-hairline); }
.nset-text { display: grid; gap: 2px; min-width: 0; }
.nset-label { font-size: var(--text-base); font-weight: 600; color: var(--text-primary); }
.nset-hint { margin: 0; max-width: 72ch; font-size: var(--text-sm); line-height: var(--leading-relaxed); color: var(--text-muted); }
.nset-control { display: flex; align-items: center; justify-content: flex-end; gap: var(--space-2); }
.nset-control select { min-width: 16rem; }
.nset-row-danger { background: color-mix(in srgb, var(--critical-soft) 45%, transparent); }
.nset-row-danger .nset-label { color: var(--critical-text); }
@media (max-width: 720px) {
  .nset-row { grid-template-columns: minmax(0, 1fr); gap: var(--space-3); }
  .nset-control { justify-content: flex-start; flex-wrap: wrap; }
  .nset-control select { min-width: 0; width: 100%; }
}

/* The popup: what happened, why, how to fix it — a numbered sequence on one rail. */
.inc-steps { list-style: none; margin: 0; padding: 0; display: grid; gap: 0; }
.inc-step { position: relative; display: grid; grid-template-columns: 28px minmax(0, 1fr); gap: var(--space-4); padding-bottom: var(--space-5); }
.inc-step:not(:last-child)::before { content: ""; position: absolute; left: 13px; top: 30px; bottom: 2px; width: 2px; background: var(--border-hairline); }
.inc-step-mark { display: grid; place-items: center; width: 28px; height: 28px; border-radius: var(--radius-full); border: 1px solid var(--border-hairline); background: var(--bg-surface); font-size: var(--text-sm); font-weight: 600; color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.inc-steps[data-severity="critical"] .inc-step:first-child .inc-step-mark { border-color: var(--critical); background: var(--critical-soft); color: var(--critical-text); }
.inc-steps[data-severity="warning"] .inc-step:first-child .inc-step-mark { border-color: var(--warn); background: var(--warn-soft); color: var(--warn-text); }
.inc-step-fix .inc-step-mark { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-text); }
.inc-step-body { display: grid; gap: var(--space-2); min-width: 0; padding-top: 3px; }
.inc-step-q { margin: 0; font-size: var(--text-sm); font-weight: 600; letter-spacing: 0.02em; color: var(--text-muted); text-transform: uppercase; }
.inc-step-a { margin: 0; font-size: var(--text-md); line-height: var(--leading-relaxed); color: var(--text-primary); overflow-wrap: anywhere; }
.inc-step-more { margin: 0; line-height: var(--leading-relaxed); color: var(--text-secondary); overflow-wrap: anywhere; }
.inc-popup-answer { display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; }
.inc-readbar { position: sticky; bottom: 0; z-index: 1; display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-3); padding: var(--space-3) var(--space-4); border: 1px solid var(--accent); border-radius: var(--radius-lg); background: var(--bg-surface); box-shadow: 0 -6px 18px -12px rgba(0, 0, 0, 0.35); font-size: var(--text-sm); font-weight: 500; color: var(--text-primary); }
.inc-step-read .inc-step-mark { border-color: var(--ok); background: var(--ok-soft); color: var(--ok-text); }
.inc-reading { display: grid; gap: var(--space-3); padding: var(--space-4); border: 1px solid var(--border-hairline); border-left: 3px solid var(--ok); border-radius: var(--radius-lg); background: var(--bg-sunken); min-width: 0; }
.inc-reading.is-unshown { border-left-color: var(--warn); }
.inc-reading-issue { margin: 0; font-size: var(--text-md); font-weight: 600; line-height: var(--leading-relaxed); color: var(--text-primary); }
.inc-reading-lines { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.inc-reading-lines li { display: grid; gap: 2px; }
.inc-reading-lines code { display: block; padding: 6px 10px; border-radius: var(--radius-md); background: var(--bg-surface); border: 1px solid var(--border-hairline); font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-primary); white-space: pre-wrap; overflow-wrap: anywhere; }
.inc-reading-lines span { font-size: var(--text-sm); color: var(--text-secondary); }
.inc-reading-fixes { display: grid; gap: var(--space-2); }
.inc-reading-fix { display: grid; gap: var(--space-2); padding: var(--space-3); border: 1px solid var(--border-hairline); border-radius: var(--radius-md); background: var(--bg-surface); }
.inc-reading-fix-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.inc-popup-foot { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); padding-top: var(--space-3); border-top: 1px solid var(--border-hairline); }
/* The read bar as a question box: the lead line above, the box and its one button below it. The
   button reads "Read again" while the box is empty and "Ask" once something is typed. */
form.inc-readbar { display: grid; gap: var(--space-2); justify-content: stretch; }
.inc-readbar-lead { min-width: 0; }
.inc-readbar-ask { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
.inc-readbar-input { flex: 1 1 auto; min-width: 0; height: 32px; padding: 0 var(--space-3); border: 1px solid var(--border-hairline); border-radius: var(--radius-md); background: var(--bg-page); color: var(--text-primary); font: inherit; font-size: var(--text-sm); }
.inc-readbar-input::-moz-placeholder { color: var(--text-muted); }
.inc-readbar-input::placeholder { color: var(--text-muted); }
.inc-readbar-input:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; border-color: var(--accent); }
.inc-reading-asked { margin: 0; font-size: var(--text-xs); color: var(--text-muted); }
.inc-reading-why > summary { cursor: pointer; font-size: var(--text-sm); color: var(--text-secondary); }
.inc-reading-why[open] > summary { margin-bottom: var(--space-2); }
/* What earlier readings found, one line each — the short memory "Read again" builds on. Dashed,
   because it is working notes that go away when the incident resolves, not a finding of record. */
.inc-notes { display: grid; gap: var(--space-2); padding: var(--space-3); margin-bottom: var(--space-3); border: 1px dashed var(--border-strong); border-radius: var(--radius-lg); min-width: 0; }
.inc-notes-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2); }
.inc-notes-forget { margin-left: auto; }
.inc-notes-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.inc-note { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: var(--space-2); align-items: start; font-size: var(--text-sm); }
.inc-note-mark { display: grid; place-items: center; inline-size: 18px; block-size: 18px; margin-top: 1px; border-radius: var(--radius-full); font-size: 11px; line-height: 1; background: var(--warn-soft); color: var(--warn-text); }
.inc-note[data-shown="yes"] .inc-note-mark { background: var(--ok-soft); color: var(--ok-text); }
.inc-note-body { display: grid; gap: 2px; min-width: 0; }
.inc-note-asked { font-size: var(--text-xs); color: var(--text-muted); }
.inc-note-finding { color: var(--text-primary); line-height: var(--leading-body); }
.inc-note-line { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-secondary); white-space: pre-wrap; overflow-wrap: anywhere; }
.inc-note-at { font-size: var(--text-xs); color: var(--text-muted); white-space: nowrap; }
.inc-popup-foot .inc-more-link { margin-right: auto; margin-left: 0; }
.inc-cause { font-weight: 600; }
.inc-src { justify-self: start; font-size: var(--text-xs); color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.04em; }
.inc-unknown { color: var(--text-muted); }
.inc-link, .inc-more-link { padding: 0; border: 0; background: none; font: inherit; color: var(--accent-text); cursor: pointer; text-decoration: underline; text-underline-offset: 2px; }
.inc-link:focus-visible, .inc-more-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
.inc-more-link { font-size: var(--text-sm); text-decoration: none; }
.inc-more-link:hover { text-decoration: none; }
.fix-panel-bare { display: grid; gap: var(--space-3); min-width: 0; }
.fix-panel-footnote { padding-top: var(--space-2); border-top: 1px solid var(--border-hairline); }
.fix-panel-footnote .fix-panel-why { margin: 0; font-size: var(--text-sm); color: var(--text-muted); }
.inc-workspace { display: inline-block; margin-top: var(--space-5); font-size: var(--text-sm); }

/* The incident dialog in the same order, with the working folded under "How we know". */
.inc-detail { display: grid; gap: var(--space-5); min-width: 0; }
.inc-sec { display: grid; gap: var(--space-3); min-width: 0; }
.inc-sec-q { margin: 0; font-size: var(--text-md); font-weight: 600; color: var(--text-primary); }
.inc-sec-a { margin: 0; display: grid; gap: var(--space-1); line-height: var(--leading-relaxed); color: var(--text-primary); }
.inc-sub { font-size: var(--text-sm); color: var(--text-secondary); }
.inc-more { border-top: 1px solid var(--border-hairline); padding-top: var(--space-3); }
.inc-more > summary { display: flex; align-items: center; gap: var(--space-2); cursor: pointer; list-style: none; font-size: var(--text-sm); font-weight: 600; color: var(--text-secondary); }
.inc-more > summary::-webkit-details-marker { display: none; }
.inc-more > summary::before { content: "\25B8"; width: 1em; color: var(--text-muted); transition: transform 0.15s ease; }
.inc-more[open] > summary::before { transform: rotate(90deg); }
.inc-more > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
.inc-more-body { display: grid; gap: var(--space-3); margin-top: var(--space-3); }
@media (prefers-reduced-motion: reduce) { .inc-more > summary::before { transition: none; } }

.alert-focused {
  border-color: var(--severity-critical-fill);
  animation: alert-focused 3s var(--ease-out) forwards;
}

@keyframes alert-focused {
  0%,
  60% {
    box-shadow: 0 0 0 3px var(--severity-critical-bg);
  }
  100% {
    box-shadow: 0 0 0 0 transparent;
  }
}

@media (prefers-reduced-motion: reduce) {
  /* No fade, and no ring left behind either — the border colour above already marks the card, which
     is the whole job. An animation held at its first frame would be the permanent ring. */
  .alert-focused {
    animation: none;
  }
}

/* ================================================================== overview layout
 *
 * Two columns: reference material on the left, the signature card on the right.
 *
 * The ratio is the hierarchy. `auto-fit` with a shared minimum gave three equal columns, which is
 * blacklist item 4 — every card the same weight, so none of them reads as the one to look at first.
 * Below 1100px the split collapses rather than squeezing: a 96px chart in a 320px column is a
 * gesture at a chart, and the bars above it already carry the reading.
 */

.overview-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr);
  gap: var(--space-6);
  align-items: start;
}

/*
 * The general two-column split, weighted either way.
 *
 * These were inline `gridTemplateColumns: "minmax(0, 1fr) minmax(0, 1.6fr)"` on four pages, and an
 * inline grid template cannot carry a media query — so those layouts held two columns all the way
 * down to a phone, where each was 180px wide. A class can collapse; a style attribute cannot, which
 * is the whole reason this is here rather than there.
 */
.split-wide-right {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr);
  gap: var(--space-6);
  align-items: start;
}

.split-wide-left {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
  gap: var(--space-6);
  align-items: start;
}

@media (max-width: 1100px) {
  .overview-split,
  .split-wide-right,
  .split-wide-left {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ---- page region ----------------------------------------------------------------------------
 *
 * 24px of padding on each side of a 390px viewport leaves 342px for the content, and the rail takes
 * 64 of that. The page steps down rather than letting cards run to the edge.
 */

/*
 * Vertical padding one step below horizontal, and the gap one step below what it was.
 *
 * `gap` here is the distance between *every* stacked block on *every* page — title to hero, hero to
 * first card, card to card — so it is the one number that decides how much of a window is spent on
 * air. At 24px a 1000px screen was giving up around a third of itself to chrome before the first
 * panel. 20px is still a clear separation at this type size and returns that space to the content.
 *
 * The horizontal padding stays 24px: the note above is about a 390px viewport, where the side
 * padding is what stops cards running to the edge, and that argument is unaffected by the vertical
 * one.
 */
.page-region {
  padding: var(--space-5) var(--space-6);
  /* The one region that scrolls vertically. Nothing inside it may widen the window: a table that
     cannot fit scrolls inside its own box, which `.scroll-x` gives it. */
  overflow-x: clip;
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
  /*
   * Floating-panel treatment, requested to match the reference: header + sidebar are one flat
   * chrome surface (--bg-surface, no dividing border between them — see index.tsx), and the
   * content region reads as a distinct, rounded, inset panel sitting within that surface rather
   * than a plain rectangle butted up against two hairlines.
   *
   * Border, not shadow, for the same structural reason every other surface in this file uses a
   * border: box-shadow outside `.elev-*` is disallowed and checked by `tokens.test.ts`. The
   * "floating" read here comes entirely from the margin gap + background/chrome colour contrast
   * + the border defining the edge — no shadow needed or used.
   */
  margin: var(--space-4);
  background: var(--bg-page);
  border: 1px solid var(--border-hairline);
  border-radius: 16px;
}

/*
 * A stacked panel keeps its own height, and the region scrolls.
 *
 * Without this a long page silently squashes its panels instead of scrolling, and the squashing is
 * invisible in the markup. Two defaults combine to cause it: a flex item's automatic minimum size
 * is content-based only while its `overflow` is `visible`, and these panels set `overflow: hidden`
 * so their rounded corners can clip a table. That drops the panel's minimum height to zero,
 * `flex-shrink: 1` then takes whatever it needs from every panel on the page, and each one crops its
 * own content to the new height with no scrollbar and no overflow to notice.
 *
 * **The trigger is `overflow: hidden`, not the class.** It was found first as a table row cut in
 * half by a card's bottom edge, fixed for `.card` alone, and came straight back on `.metric-strip` —
 * which sets `overflow: hidden` for the same reason and was therefore always going to. A strip on a
 * full page rendered its labels intact and its 20px values sheared off along the bottom, so
 * "stable-4.19" and "VSphere" read as a font problem rather than a layout one.
 *
 * So: anything added here that clips its own overflow belongs in this selector. Panels that are
 * *meant* to fill the region — the dashboard grid — are not in it and set their own flex.
 */
.page-region > .card,
.page-region > .metric-strip,
/*
 * The Applications page's latency plate and the golden-signals legend under it.
 *
 * Both are `overflow: hidden` for their rounded corners and both sit straight in the region, so both
 * were crushed. Measured on `storefront` at a 900px window: the plate stood 38px tall — its title row
 * and nothing under it, which read as an unexplained empty box rather than as the page's one
 * instrument — and the legend stood **2px**, so traffic, error rate, p95, CPU and memory were absent
 * from the page altogether.
 */
.page-region > .apx-plate,
.page-region > .apx-legend,
/*
 * A page-level scroll region, for the reason above turned inside out.
 *
 * `.scroll-x` sets `overflow-y: hidden`, and CSS Flexbox 1 §4.5 gives an item whose overflow is not
 * `visible` an automatic minimum size of **zero** — so the one property that lets a long strip
 * scroll sideways is also the property that removes the floor stopping the page crushing it. A
 * `.card` shrinks to its content and stops; this shrinks to nothing.
 *
 * Measured on the Collection page at an 863px viewport: the tab strip rendered 4px tall — its own
 * border and nothing else — with all five tabs in the DOM at full size, findable by the inspector
 * and reachable by keyboard. It read as "the tabs are gone", was identical in both themes, and came
 * back on a taller window, which is what kept it looking like anything but a layout bug.
 */
.page-region > .scroll-x {
  flex-shrink: 0;
}

/*
 * Room at the foot of the page for the controls fixed over it.
 *
 * The assistant's launcher sits centred on the bottom edge of the window and the "Alerts stopped"
 * reminder in its bottom-right corner. Both are fixed to the viewport on purpose (`chat-dock.tsx`,
 * `critical-alert.tsx`), so with the region's ordinary 20px foot the last row of every page — the
 * severity table on Reliability, the last card anywhere — could never be scrolled out from under
 * them. 48px more clears the launcher's button: its top is 70px above the window edge and the region
 * ends 16px above it. Scoped with `:has()` to the shell that mounts the launcher; the operator
 * console has none and keeps its foot. Specific enough to win over the narrow-window padding below.
 */
.page-region:has(> .chatdock-launcher) {
  padding-bottom: calc(var(--space-5) + 48px);
}

@media (max-width: 767px) {
  .page-region {
    padding: var(--space-4);
    gap: var(--space-4);
  }
}

/*
 * On a phone the floating panel gives its gutter back.
 *
 * 16px of margin outside a 16px padding, beside a 56px rail, left a 390px phone 268px of page. With
 * the rail gone (the drawer replaces it at this width) the region runs edge to edge and its own
 * padding is the whole gutter: 16px a side, 358px for the content. The border and the radius go with
 * the margin — a rounded frame flush against the glass edge is a frame around nothing.
 */
@media (max-width: 768px) {
  .page-region {
    margin: 0;
    border-width: 0;
    border-radius: 0;
    padding-left: max(var(--space-4), env(safe-area-inset-left, 0px));
    padding-right: max(var(--space-4), env(safe-area-inset-right, 0px));
  }
}

/* ---- page header spacing --------------------------------------------------------------------
 *
 * `<PageHeader>` needs a fixed space under it, and exactly one thing may supply it. The value is the
 * region's own `gap`, so the two cannot drift — a header inside the region and one outside it must
 * hold the title off its content by the same amount.
 *
 * Inside `.page-region` the region's own `gap` already does. A flex gap does not collapse with a
 * margin — it adds — so carrying a margin as well doubled the space under every page title and the
 * header read as detached from its content. Outside the region (a header rendered into
 * `.console-body`, which is a plain block box) nothing supplies it and the margin is the only thing
 * holding the title off the panel below.
 *
 * Hence: the class carries it, the child selector takes it back where it is already accounted for.
 * This cannot be an inline style on the component — an inline declaration outranks both rules.
 */
.page-header {
  margin-bottom: var(--space-5);
}

/*
 * Every band that carries its own bottom margin has it taken back inside the region.
 *
 * Same argument as the header above, and it was only ever applied to the header. `Notice`,
 * `.needsyou-bar` and `.ov-hero` each carry `margin-bottom: var(--space-4)` for the callers that
 * render them into a plain block box - and inside `.page-region`, whose `gap` is `var(--space-5)`,
 * a flex gap does not collapse with a margin, it adds. Measured on the Overview at 1920px: the
 * bands were spaced 36 / 20 / 20 / 36, so the page had two rhythms and neither was the region's.
 */
.page-region > .page-header,
.page-region > .notice,
.page-region > .needsyou-bar,
.page-region > .ov-hero {
  margin-bottom: 0;
}

/* ---- section nav ----------------------------------------------------------------------------
 *
 * The pages folded under the current sidebar entry, as a row above the page title (`SectionNav`).
 * Drawn the way `Tabs` draws itself — strong hairline under the row, a 2px primary underline on the
 * current link, 500 → 600 weight — because a reader should recognise it as "views of one place".
 *
 * It wraps rather than scrolls. A scroll container here is a flex item with `overflow` set, whose
 * minimum height is zero, and the page would crush it to its border on a short window — the failure
 * `.page-region > .scroll-x` records. `flex-shrink: 0` holds it regardless.
 *
 * **No horizontal padding on a link; the space between them is the row's gap.** With padding, the
 * first label started 12px in from the page title and every card below it — measured at x273 against
 * x261 — and the underline overhung the word by the same on both sides. Now the first label is flush
 * with the content edge and the underline is exactly as wide as the word it marks.
 */
.section-nav {
  display: flex;
  flex-wrap: wrap;
  -moz-column-gap: var(--space-6);
       column-gap: var(--space-6);
  flex-shrink: 0;
  border-bottom: 1px solid var(--color-border-strong);
}

/*
 * The section's pages stay on screen while the page scrolls under them.
 *
 * A sticky box sticks at the scroller's padding edge, not its top, so on its own this left the
 * region's top padding as a strip where content scrolled past above the tabs. The strip is pulled up
 * into that padding (negative margin, matching padding) and sticks at minus the padding, so stuck it
 * covers from the region's very top edge, and unstuck it sits exactly where it did. `--region-top`
 * follows `.page-region`'s own top padding, which narrows on a phone below.
 *
 * Only `SectionNav`'s own strip (`.section-nav-held`). `.cluster-tabs` borrows `.section-nav` for a
 * cluster's detail tabs, and a page's children render straight into the region — so a
 * `.page-region > .section-nav` selector pinned those too, on top of this strip.
 */
.page-region {
  --region-top: var(--space-5);
}
@media (max-width: 767px) {
  .page-region {
    --region-top: var(--space-4);
  }
}
.page-region > .section-nav-held {
  position: sticky;
  top: calc(-1 * var(--region-top));
  /*
   * Over a page's own sticky rows (`.alerts-tabrow` at 3), which stop under it via `--section-nav-h`,
   * and *under* the topbar (`--z-sticky`). At `--z-sticky` itself it tied with the topbar and, being
   * later in the document, painted over the account menu that drops out of it.
   */
  z-index: var(--z-raised);
  margin-top: calc(-1 * var(--region-top));
  padding-top: var(--region-top);
  background: var(--bg-page);
}
/*
 * The region's side padding beside the strip. Content scrolls there too — the incident list's red
 * left edge ran up past the strip one pixel outside it — so a cover reaches well past both sides and
 * the region's own clip trims it to its padding box. Behind the links (a negative layer inside the
 * strip's stacking context) and above the strip's rule (it stops at the padding box, the rule is the
 * border below it).
 */
.page-region > .section-nav-held::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: -50vw;
  right: -50vw;
  z-index: -1;
  background: var(--bg-page);
}

.section-nav-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) 0;
  /* Sits on the row's rule, so the current link's underline replaces it rather than stacking. */
  margin-bottom: -1px;
  border-bottom: 2px solid transparent;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
}
/*
 * A tab is marked by its bar, never by underlined text. The global `a:hover` rule underlines every
 * link and outranks `.section-nav-link` (0,1,1 against 0,1,0), so the hover rule restates it — two
 * classes' worth of specificity, which beats it without !important.
 */
.section-nav-link:hover,
.section-nav-link:focus-visible,
.section-nav-link-on {
  text-decoration: none;
}
.section-nav-link:hover {
  color: var(--color-text);
}
.section-nav-link-on {
  color: var(--color-text);
  font-weight: 600;
  border-bottom-color: var(--color-primary);
}
.section-nav-link:focus-visible {
  /* Outside the label now that there is no padding to draw inside — the row does not clip. */
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* The same strip as a set of tabs on a detail page (`clusters/[id]`): buttons, so the browser's own
   button chrome has to go, and room below it for the panel. */
.cluster-tabs { margin-bottom: var(--space-4); }
.cluster-tabs .section-nav-link {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  background: none;
  border-top: 0;
  border-inline: 0;
  font-family: inherit;
  cursor: pointer;
}

.section-nav-badge {
  min-width: 18px;
  padding: 0 5px;
  border-radius: var(--radius-full);
  background: var(--severity-critical-bg);
  color: var(--severity-critical-fg);
  font-size: var(--text-xs);
  font-weight: 500;
  text-align: center;
}

/* ---- console resource nav -------------------------------------------------------------------
 *
 * The row is a flex container whose own `min-width` would otherwise resolve to its content's
 * minimum, so one wide table in the content column pushes the whole row past the viewport and the
 * nav ends up half off the left edge with only its right-aligned counts showing. Both the row and
 * the column need `min-width: 0` for the chain to hold.
 */
.console-frame {
  display: flex;
  gap: var(--space-6);
  align-items: flex-start;
  min-width: 0;
}

.console-body {
  flex: 1;
  min-width: 0;
}

/*
 * Sticky, not fixed: the nav scrolls away with short content, stops at the top on long lists, and
 * never floats over anything.
 *
 * Its height is measured against the region it lives in, not the window. It sits inside
 * `.page-region`, whose scrollport begins below the 60px topbar — so a `100dvh`-derived height ran
 * the last group past the bottom of the window with no way to reach it, because the nav's own
 * scrollbar had already ended. Subtracting the topbar is what makes the bottom of the list
 * reachable. It chains, so scrolling past the end of the nav carries on down the page.
 */
/*
 * `Notice` carries its own bottom margin, for the callers that render it into a plain block box.
 * It is a class rather than an inline style for exactly the reason the header's is: an inline
 * declaration outranks the rule above that takes it back inside `.page-region`.
 */
.notice {
  margin-bottom: var(--space-4);
}

/* The page control bar. Was inline styles on `Toolbar`; moved here so the console can hold it. */
.page-toolbar {
  display: flex;
  gap: var(--space-3);
  align-items: flex-end;
  flex-wrap: wrap;
  margin-bottom: var(--space-4);
}

/*
 * The console holds its filter bar and its verdict under the header, in that order.
 *
 * Each is offset by what is stuck above it: the toolbar by the header, the verdict by the header
 * plus the toolbar. Both heights are measured and published by `ConsoleFrame` rather than assumed,
 * because the header wraps to two rows on a narrow window and the toolbar is not on every page.
 *
 * The margin becomes padding on both. A stuck element's margin is transparent and outside its
 * background, so rows scroll visibly through the gap it leaves — which reads as a rendering fault
 * rather than a layout one.
 */
.console-body > .page-toolbar {
  position: sticky;
  top: var(--console-head-h, 0px);
  z-index: var(--z-raised);
  background: var(--bg-page);
  margin-bottom: 0;
  padding-bottom: var(--space-4);
}

/*
 * The rows are the only thing that scrolls.
 *
 * `.scroll-x` contains the horizontal axis and leaves the vertical to the page — right for a short
 * table inside a longer page, wrong for the console, where the table *is* the page and the card's
 * title, its note and the column header all scrolled away with it.
 *
 * Capping it makes it a scroll container, which is what `.tbl thead th`'s existing
 * `position: sticky` has been waiting for: the column header now holds against the rows instead of
 * against a box that never moved. The height is measured by `ConsoleFrame`; the fallback keeps a
 * usable list if that measurement never arrives.
 */
.console-body .card .scroll-x {
  overflow-y: auto;
  max-height: var(--console-rows-max, 55vh);
}

.console-body .hero {
  position: sticky;
  top: calc(var(--console-head-h, 0px) + var(--console-tools-h, 0px));
  z-index: var(--z-raised);
  background: var(--bg-page);
  margin-bottom: 0;
  padding-bottom: var(--space-4);
}

/*
 * The console header, held while the resource tables scroll under it.
 *
 * `top: 0` is the top of the shell's content region, not the window: `body` is
 * `height: 100dvh; overflow: hidden`, so the document does not scroll and the app's own topbar sits
 * outside this box entirely.
 *
 * The background is load-bearing, not decoration — without it the table's rows scroll visibly
 * through the title and the cluster picker. `--z-raised` puts it over the content and under the
 * topbar's `--z-sticky`, so the two cannot fight over the seam between them.
 */
.console-head {
  position: sticky;
  /*
   * Negative, and that is the whole trick.
   *
   * Sticky offsets resolve against the scrollport's *content* edge, which sits below
   * `.page-region`'s own top padding — so `top: 0` parked the header one padding's width down and
   * left a transparent band above it that rows scrolled through. Pulling the stick point up by that
   * padding, and giving the header the same padding back, puts the band inside its own background
   * while leaving the title exactly where it was.
   */
  top: calc(var(--space-5) * -1);
  z-index: var(--z-raised);
  background: var(--bg-page);
  /*
   * Pulled up over `.page-region`'s own top padding, and given it back as padding.
   *
   * Sticky `top: 0` parks the header at the scrollport's padding edge, which leaves a
   * `--space-5` band above it that belongs to the region and is transparent — so rows scrolled
   * visibly through it, above a header that was otherwise holding correctly. Trading the margin
   * for padding moves that band inside the header's own background.
   */
  margin-top: calc(var(--space-5) * -1);
  padding-top: var(--space-5);
}

.console-nav {
  width: 224px;
  flex-shrink: 0;
  position: sticky;
  /*
   * Below the sticky header, whose height is published by `ConsoleFrame` as `--console-head-h`.
   * The fallback is the offset this had before the header became sticky, so a browser with no
   * `ResizeObserver` gets the old layout rather than a nav wedged behind the title.
   */
  top: calc(var(--console-head-h, 0px) + var(--space-4));
  /*
   * Measured by `ConsoleFrame` as `--console-nav-max`: the room between where the nav sticks and where
   * the frame's bottom stops at full scroll. The formula is only the fallback — it leaves out the
   * region's own padding, so on its own it ran the nav ~40px past the region's floor.
   */
  max-height: var(--console-nav-max, calc(100dvh - var(--size-topbar) - var(--console-head-h, 0px) - var(--space-4) * 2));
  overflow-y: auto;
}

/*
 * Below this, 224px of a 900px window leaves the tables the nav links to without room, and every
 * one of them then scrolls sideways. It used to become a plain band across the top of the page —
 * `position: static`, full width — which read as a bigger loss than it had to be: the moment that
 * band scrolled out of view with everything else, there was no nav on screen at all, not even the
 * current location.
 *
 * It narrows to an icon rail instead — the same trade the primary sidebar already makes at its own
 * breakpoint, `--size-nav-rail`, reused here rather than a second width invented for the same job.
 * Sticky is left on, deliberately: a rail this narrow costs little enough to keep pinned that giving
 * it up was never the point, only the full 224px of label text was.
 */
.console-nav-show-rail-only {
  display: none;
}
@media (max-width: 899px) {
  .console-nav {
    width: var(--size-nav-rail);
  }

  .console-nav-root-link,
  .nav-group-header,
  .console-nav-item {
    justify-content: center;
    padding-left: 0;
    padding-right: 0;
  }

  .console-nav-hide-rail {
    display: none;
  }

  /* The unhealthy count on a group header has nowhere to sit once its own label is gone — collapsed
     to a dot rather than dropped outright, so a rail this narrow can still say something is wrong
     without room to say how much. Matches the primary sidebar's own rail-mode badge treatment. */
  .console-nav-show-rail-only {
    display: inline-block;
  }
  .nav-group-bad-dot {
    width: 6px;
    height: 6px;
    flex: none;
    border-radius: var(--radius-full);
    background: var(--severity-critical-fg);
  }
}

/* ================================================================== dashboard grid
 *
 * Twelve columns, free placement. Each cell publishes its geometry as custom properties and this
 * stylesheet decides how to use them — which is what lets the single-column collapse happen in CSS,
 * with no JavaScript on resize and no chance of a phone overwriting the arrangement someone built on
 * a desktop.
 *
 * `grid-auto-rows` is the height unit. A panel's `h` is a count of these, so a card can be made
 * short and its content scrolls inside it rather than the card growing to fit — otherwise "resize"
 * would only ever mean "make bigger".
 */

.dash {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  grid-auto-rows: 48px;
  gap: var(--space-4);
  align-items: stretch;
  /* A grid inside a flex column inherits `min-width: auto`, so one wide table in one panel can push
     the whole page sideways. `minmax(0, …)` handles the tracks; this handles the container. */
  min-width: 0;
}

.dash-cell {
  position: relative;
  min-width: 0;
  /* Where a jump to this panel stops.
     A fragment link scrolls its target to the very top of the viewport, which puts the panel's
     heading hard against the edge — and under anything sticky above it. A token rather than a
     measured header height: this is breathing room, so it stays right whether or not a surface
     above it is pinned. */
  scroll-margin-top: var(--space-6);
  grid-column: calc(var(--px) + 1) / span var(--pw);
  grid-row: calc(var(--py) + 1) / span var(--ph);
  /* Only the layout properties animate. Transform is excluded because a dragged panel sets it every
     frame, and a transition on it makes the card lag behind the pointer. */
  transition: grid-column var(--motion-fast) var(--ease-out), grid-row var(--motion-fast) var(--ease-out);
}

/*
 * A panel sized shorter than its content scrolls inside itself.
 *
 * Without this the card overflows its cell and lands on top of whatever is below — the one thing the
 * layout model guarantees cannot happen, undone by CSS. The card becomes a column: header fixed,
 * body scrolls.
 */
.dash-cell > .card {
  height: 100%;
  display: flex;
  flex-direction: column;
}

.dash-cell > .card > .card-body,
.dash-cell > .card > .card-body-roomy,
.dash-cell > .card > .card-body-flush {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  /* Deliberately not contained. Nine panels tile the whole scroll region, so a panel that keeps the
     wheel at its last row is a page that will not scroll — there is nowhere else to point. */
  overscroll-behavior-x: contain;
}

/*
 * When the body holds nothing but a table, the table's own wrapper is the scroller.
 *
 * `Table` wraps itself in `.scroll-x`, and `overflow-x: auto` makes an element a scroll container in
 * **both** axes — CSS computes the other axis to `auto` rather than leaving it `visible`. So the
 * wrapper, not the card body, is what `position: sticky` on the header resolves against; and because
 * the wrapper has no height of its own it never scrolls, so the header never sticks. The rows slide
 * up under the card's title and the column names are gone exactly when a long list needs them.
 *
 * Giving the wrapper the height instead puts the scroll and the sticky container on the same
 * element. Without `:has` support the body scrolls as before — the table still works, it just loses
 * the sticky header, so this degrades rather than breaking.
 */
.dash-cell > .card > .card-body:has(> .scroll-x:only-child),
.dash-cell > .card > .card-body-roomy:has(> .scroll-x:only-child),
.dash-cell > .card > .card-body-flush:has(> .scroll-x:only-child) {
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.dash-cell > .card > [class^="card-body"] > .scroll-x:only-child {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  /* x only. Sideways never escapes; downwards it hands off to the page at the last row, because a
     panel is part of the page and not a context of its own. See the scrolling section. */
  overscroll-behavior-x: contain;
  /* No scrollbar-width/-color here. They used to be, and outside the feature query they are what
     switches Blink onto its stepper-arrow scrollbar — the appearance rules live in one place now. */
}

/* The panel under the pointer. Lifted and above its neighbours, so it reads as picked up. */
.dash-cell[data-held] {
  z-index: 3;
  transition: none;
  cursor: grabbing;
}

.dash-cell[data-held] > .card {
  box-shadow: var(--elev-overlay);
  border-color: var(--accent);
}

/* Everything else recedes slightly while a drag is in progress, so the moving panel and the space
   opening for it are what the eye follows. */
.dash[data-dragging] .dash-cell:not([data-held]) > .card {
  opacity: 0.72;
}

/* ---- the control strip -----------------------------------------------------------------------
 *
 * Pinned to the panel's top-right **corner**, and revealed by hover or focus-within. A dashboard
 * whose every panel wears a permanent toolbar is a dashboard about its own configuration.
 *
 * The corner is the fix for how this first looked. Centred on the top edge, the strip floated in the
 * gap between two cards and read as belonging to the one above it — a chip with no owner. On the
 * corner it is unambiguously this panel's, and it clears `card-action` ("View all", "View
 * operators"), which is inset 20px from the same edge.
 *
 * `:focus-within` rather than `:focus` is what makes it keyboard-reachable: tabbing to the first
 * control reveals the rest of the strip instead of it vanishing as focus moves along it.
 *
 * `opacity` and not `display: none` — a hidden element is not focusable, so the strip would be
 * unreachable by keyboard at all, which is the exact failure this component exists to avoid.
 */

.dash-tools {
  position: absolute;
  top: -12px;
  right: -8px;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 2px;
  border-radius: var(--radius-full);
  background: var(--bg-surface);
  border: 1px solid var(--border-hairline);
  box-shadow: var(--elev-overlay);
  opacity: 0;
  transition: opacity var(--motion-fast) var(--ease-out);
}

.dash-cell:hover > .dash-tools,
.dash-cell:focus-within > .dash-tools,
.dash-cell[data-held] > .dash-tools,
.dash-cell:hover > .dash-resize {
  opacity: 1;
}

.dash-grip,
.dash-btn {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-full);
  background: none;
  color: var(--text-secondary);
  transition: background var(--motion-fast) var(--ease-out), color var(--motion-fast) var(--ease-out);
  /* The browser's own text selection and panning would both fight the drag. */
  touch-action: none;
  -webkit-user-select: none;
     -moz-user-select: none;
          user-select: none;
}

.dash-grip:hover,
.dash-btn:hover:not(:disabled) {
  background: var(--bg-hover);
  color: var(--text-primary);
}

.dash-grip {
  cursor: grab;
}
.dash-grip:active {
  cursor: grabbing;
}

/* ---- the corner drag ---------------------------------------------------------------------------
 *
 * Bottom-right, the corner every resizable thing on a desktop has used for thirty years. It sizes in
 * both directions — width in columns, height in rows.
 *
 * Two diagonal rules rather than an icon: at 16px an icon is a smudge, and two strokes read as
 * "grip" at any size. It sits *outside* the card's rounded corner so it never covers content — a
 * table's last row runs to the card's edge, and a handle over it would hide a value.
 *
 * The strokes are pseudo-elements, not a striped `linear-gradient`. The gradient version was written
 * first and `tokens.test.ts` rejected it, correctly: blacklist item 6 allows a gradient only as a
 * chart's area fill, and "mine is really a texture" is the argument that rule exists to refuse.
 */

.dash-resize {
  position: absolute;
  right: -3px;
  bottom: -3px;
  z-index: 2;
  width: 18px;
  height: 18px;
  cursor: nwse-resize;
  color: var(--border-strong);
  opacity: 0;
  transition: opacity var(--motion-fast) var(--ease-out), color var(--motion-fast) var(--ease-out);
  touch-action: none;
}

.dash-resize::before,
.dash-resize::after {
  content: "";
  position: absolute;
  right: 1px;
  height: 2px;
  border-radius: 1px;
  background: currentcolor;
  transform: rotate(-45deg);
  transform-origin: 100% 50%;
}

.dash-resize::before {
  bottom: 4px;
  width: 8px;
}

.dash-resize::after {
  bottom: 9px;
  width: 14px;
}

.dash-cell:focus-within > .dash-resize,
.dash-cell[data-held] > .dash-resize {
  opacity: 1;
}

.dash-resize:hover {
  color: var(--accent);
}

/* ---- the layout bar --------------------------------------------------------------------------- */

.dash-bar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-bottom: var(--space-4);
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

.dash-bar-count {
  color: var(--text-muted);
}

.dash-bar-hidden {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  flex-wrap: wrap;
}

.dash-bar-label {
  color: var(--text-muted);
}

/* ---- narrowing ---------------------------------------------------------------------------------
 *
 * Twelve columns, then one, with nothing in between.
 *
 * A six-column intermediate tier was written first and removed: halving a stored column position
 * needs integer division, CSS has no portable way to do it — `round()` is too new to rely on — and a
 * fractional grid line is simply invalid, so odd positions silently fell back to `auto`. Twelve
 * columns still gives 75px each at 900px wide, which is enough; below that a grid of any width is
 * the wrong shape and one column is the answer.
 *
 * The stored `--px` is ignored rather than rewritten, so the desktop arrangement is still there when
 * the window widens — a phone must not be able to overwrite a layout
 * someone built on a laptop, which is exactly what happens when the narrow view computes and saves
 * its own positions.
 *
 * The component reads the *rendered* track count before it lets a drag start, so placement is
 * disabled here rather than silently landing panels in the wrong cells. The strip's move and hide
 * still work, and the corner handle goes: there is no width left to adjust.
 */

@media (max-width: 899px) {
  .dash {
    grid-template-columns: minmax(0, 1fr);
    grid-auto-rows: auto;
    gap: var(--space-4);
  }
  .dash-cell {
    /* Full width, natural height, in DOM order — which the component renders in reading order, so
       the stack matches what the layout looked like. */
    grid-column: 1 / -1;
    grid-row: auto;
    transform: none !important;
  }
  .dash-cell > .card {
    height: auto;
  }
  .dash-cell > .card > .card-body,
  .dash-cell > .card > .card-body-roomy,
  .dash-cell > .card > .card-body-flush {
    overflow: visible;
  }
  /* No hover on a touch screen, so the strip is always visible. The corner handle goes entirely
     rather than offering a drag that cannot change anything. */
  .dash-tools {
    opacity: 1;
  }
  .dash-resize {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .dash-cell,
  .dash-tools,
  .dash-resize {
    transition: none;
  }
}

/* ================================================================== code blocks
 *
 * A nested surface: sunken ground, hairline edge, no shadow of its own.
 */

.code {
  display: flex;
  background: var(--bg-sunken);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  overflow: hidden;
  font-size: var(--text-base);
  line-height: 1.7;
}

.code-gutter {
  flex: none;
  margin: 0;
  padding: var(--space-4) var(--space-2) var(--space-4) var(--space-3);
  text-align: right;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
  border-right: 1px solid var(--border-hairline);
  /* So a drag-select of the code does not carry the numbers into the clipboard. */
  -webkit-user-select: none;
     -moz-user-select: none;
          user-select: none;
}

.code-text {
  flex: 1;
  min-width: 0;
  margin: 0;
  padding: var(--space-4);
}

/*
 * Copy, and the block's one action, as the frame's last column.
 *
 * This replaces `padding-right: 76px` on the text and an absolutely positioned toolbar over it.
 * 76px was room for Copy alone: a block that also carried "Request this change" let a long command
 * scroll 111px underneath the buttons, unreadable and unselectable, at exactly the moment someone is
 * checking the argument they are about to run against a cluster. As a column, the scroller ends
 * where the buttons begin, whatever the slot holds.
 *
 * The top padding centres a small control on the text's first line rather than pinning it to the
 * corner: the text's own `space-4` padding, plus half the difference between one line (`1.7em`,
 * from `.code`) and the control. On a one-line command that puts the buttons and the command on
 * one centre line; on a manifest, beside line 1. `max-width` lets the pair stack on a narrow
 * screen rather than squeezing the command out. The left padding is a gutter of bare ground: a
 * scroller clips its text at its own edge, not at its padding, so without it a long command is cut
 * mid-glyph flush against the first button.
 */
.code-actions {
  flex: none;
  align-self: flex-start;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-2);
  max-width: 50%;
  padding: calc(var(--space-4) + (1.7em - var(--size-control-sm)) / 2) var(--space-3) var(--space-3);
}

/* Spacing between this block and its neighbours belongs to the layout it sits in. The class exists
   so that layout has something to name — `.remedy-card > .code-block` — instead of a bare div. */
.code-block { min-width: 0; }

/* ================================================================== breadcrumbs */

.crumbs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  font-size: var(--text-base);
  color: var(--text-secondary);
}

.crumbs a:hover {
  color: var(--hover-accent-text);
  transition: color var(--motion-fast) var(--ease-out);
}

.crumb-sep {
  color: var(--text-muted);
  -webkit-user-select: none;
     -moz-user-select: none;
          user-select: none;
}

/* The leaf is where you are, so it is not a link and does not pretend to be one. */
.crumb-current {
  color: var(--text-primary);
  font-weight: 500;
}

/* ================================================================== tables
 *
 * Tables are the product. Dense rows, hairline dividers, sticky header, hover on the whole row.
 */

.tbl {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-base);
}

.tbl thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  /* Sunken, per the spec's list of nested surfaces. A header the same colour as the card it sits in
     needs its bottom border to do all the work, and that border scrolls under the first row. */
  background: var(--bg-sunken);
  text-align: left;
  height: 36px;
  padding: 0 var(--space-4);
  white-space: nowrap;
  border-bottom: 1px solid var(--color-border);
  /* The one sanctioned uppercase: 11px column headers and eyebrows. */
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-subtle);
}
/* Alignment only. Deliberately not `.num`, which would also swap the header into the mono face —
   an 11px uppercase column header is not a number and should not be set like one. */
.tbl thead th.right {
  text-align: right;
}

/*
 * Sortable column headers (`DataTable`).
 *
 * The header is a real button filling its cell, so the whole cell is the click target. Base ink is
 * muted; a header that can actually sort answers hover with the secondary ink — without that
 * affordance a sortable column and a frozen one were indistinguishable until you clicked one. The
 * active column takes primary ink via `th[aria-sort]`, keying the style to the attribute a screen
 * reader already announces so the two cannot drift.
 */
.tbl-sort {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  background: transparent;
  border: 0;
  width: 100%;
  height: 100%;
  padding: 0 var(--space-4);
  text-align: left;
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--text-muted);
  transition: color var(--motion-fast) var(--ease-out);
}
.tbl-sort:not(:disabled) {
  cursor: pointer;
}
.tbl-sort:not(:disabled):hover {
  color: var(--hover-accent-text);
  transition: color var(--motion-fast) var(--ease-out);
}
.tbl-sort:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: -2px;
}
th[aria-sort] .tbl-sort {
  color: var(--text-primary);
}
.tbl thead th.right .tbl-sort {
  text-align: right;
}

/*
 * Vertical padding, so a cell that wraps does not run into the hairlines above and below it.
 *
 * It was `0 var(--space-4)`, relying on `height` to space the row — which works exactly as long as
 * every cell is one line. A name over a slug, a sentence, a URL or a stack of badges grows the row
 * past `--size-row`, and then the text sat flush against both dividers: Reference sources, the
 * operator's licence table, the AI assignments. A single line of body text is 18px, so 8px either
 * side still fits inside the 40px floor and single-line rows render exactly as before.
 */
.tbl tbody td {
  height: var(--size-row);
  padding: var(--space-2) var(--space-4);
  vertical-align: middle;
  border-bottom: 1px solid var(--color-border);
}
.tbl tbody tr:last-child td {
  border-bottom: 0;
}
.tbl-compact tbody td {
  height: var(--size-row-compact);
  /* Half the default: 18px of text plus 4px either side still sits inside the 34px compact floor. */
  padding-block: var(--space-1);
}

.tbl-caption {
  caption-side: top;
  text-align: left;
  padding: 0 var(--space-4) var(--space-3);
  font-size: var(--text-sm);
  line-height: var(--leading-body);
  color: var(--text-secondary);
}

.tbl-caption-note {
  color: var(--text-muted);
}

/* ---- responsive columns ---------------------------------------------------------------------
 *
 * The whole of a table's response to window size, and it is three rules.
 *
 * `Column.priority` decides which columns survive a narrow window: 1 always, 2 below 900px, 3 below
 * 1280px. Both the `<th>` and its cells carry the class, so a column leaves as a column rather than
 * a header sliding out of alignment with its data.
 *
 * In CSS rather than JavaScript on purpose. Reading `window.innerWidth` in an effect renders the
 * wide layout and then collapses it — a visible jump on every load, and a hydration mismatch. The
 * browser re-evaluates a media query on every resize for free, so "adjusts to the window" is what
 * you get by *not* implementing it.
 *
 * The breakpoints are the token scale's own (1024/768 either side), pulled outward because a table
 * needs to shed a column before the layout is actually cramped, not once it already is.
 */

@media (max-width: 1279px) {
  .col-p3 {
    display: none;
  }
}

@media (max-width: 899px) {
  .col-p2 {
    display: none;
  }
}

/*
 * Below the medium breakpoint a `DataTable` stops being a grid and becomes a list of records.
 *
 * Scoped to `.tbl-stack`, which only `DataTable` adds — its cells carry their column's header in
 * `data-label` and can therefore label themselves once the `<thead>` is gone. A plain `Table`'s cells
 * are written by the page and have no header to borrow, so it keeps horizontal scroll: a stack of
 * unlabelled values is worse than a table you swipe.
 *
 * Even at priority 1 there are three or four columns, and four columns across 390px gives each of
 * them 80px — which is not a narrow column, it is a column that wraps every value onto four lines.
 * So the row becomes a block, each cell gets its header as a prefix from `data-label`, and the whole
 * thing reads top to bottom like the detail view it is standing in for.
 */
@media (max-width: 767px) {
  .tbl-stack,
  .tbl-stack tbody,
  .tbl-stack tr {
    display: block;
    width: 100%;
  }

  /* `display: block` on the table leaves the caption as a `table-caption` with no table to caption. */
  .tbl-stack > caption {
    display: block;
  }

  .tbl-stack thead {
    /* Not `display: none` — the headers become each cell's own prefix, and a screen reader still
       needs the association. `.sr-only` geometry keeps them in the accessibility tree. */
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
  }

  .tbl-stack tbody tr {
    padding: var(--space-3) 0;
    border-bottom: 1px solid var(--border-hairline);
  }
  .tbl-stack tbody tr:last-child {
    border-bottom: 0;
  }

  .tbl-stack tbody td {
    display: flex;
    align-items: baseline;
    gap: var(--space-3);
    height: auto;
    min-height: 0;
    padding: 2px var(--space-4);
    border-bottom: 0;
    text-align: left;
  }

  /* Only cells that were given a header. `Table`'s own cells are laid out by their page and carry
     no label; an empty `::before` would still reserve its 7rem and indent them for nothing. */
  .tbl-stack tbody td[data-label]::before {
    content: attr(data-label);
    flex: none;
    width: 7rem;
    font-size: var(--text-xs);
    font-weight: 500;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-muted);
  }

  /* The first cell is the record's name and needs no label — it is the heading of the block. */
  .tbl-stack tbody td:first-child {
    padding-bottom: var(--space-2);
  }
  .tbl-stack tbody td:first-child[data-label]::before {
    content: none;
  }

  /* A numeric cell aligned right inside a flex row would float away from its own label. */
  .tbl-stack tbody td.num {
    justify-content: flex-start;
  }
}

/*
 * Clickable rows.
 *
 * Focus is styled the same weight as hover deliberately: a keyboard user working down a 200-row
 * resource list needs to see where they are as clearly as a mouse user, and an outline-only ring on
 * a table row nearly disappears against the row borders.
 */
.row-hover {
  cursor: pointer;
  transition: background var(--motion-fast) var(--ease-out);
}
.row-hover:hover,
.row-hover:focus-visible {
  background: var(--color-hover);
}

/*
 * Primary nav row hover ground.
 *
 * The chevron affordance (`.appnav-item-chevron`) is disabled — hidden with `display: none` at
 * all times, hover included.
 *
 * Active rows keep the brand tint from `index.tsx`'s inline styles; this only adds the *hover*
 * ground for inactive rows, which inline styles cannot express.
 */

/* Sidebar drag-to-resize handle — a thin hit target on the trailing edge, invisible until
   hover/drag so it doesn't read as a stray line on every render. */
.nav-resize-handle {
  position: absolute;
  top: 0;
  right: -3px;
  width: 6px;
  height: 100%;
  cursor: col-resize;
  z-index: 5;
  background: transparent;
}
.nav-resize-handle:hover,
.nav-resize-handle--active {
  background: var(--hover-accent-soft);
}

.appnav-item {
  background: transparent;
  transition: background var(--motion-fast) var(--ease-out);
}
.appnav-item:hover,
.appnav-item:focus-visible {
  background: var(--hover-accent-soft);
}
.appnav-item-chevron {
  display: none;
}
@media (prefers-reduced-motion: reduce) {
  .appnav-item {
    transition: none;
  }
}

/* Sidebar hamburger toggle — base transparent background lives here (not inline in index.tsx)
   so it doesn't out-rank this file's :hover/:focus-visible rules; inline style there still owns
   color/cursor/opacity since those are dynamic per-render. */
.nav-toggle-btn {
  background: transparent;
  border-radius: var(--radius-sm, 6px);
  transition: background var(--motion-fast) var(--ease-out);
}
.nav-toggle-btn:hover,
.nav-toggle-btn:focus-visible {
  background: var(--hover-accent-soft);
}
@media (prefers-reduced-motion: reduce) {
  .nav-toggle-btn {
    transition: none;
  }
}

/*
 * Console resource-nav row hover ground (Pods, Deployments, StatefulSets, ...).
 *
 * Same treatment as `.appnav-item`: a transparent resting ground and `--hover-accent-soft` on
 * hover/focus, so the primary sidebar and the console's resource tree read as one hover language
 * instead of two. Selected/active rows should keep whatever background they carry (inline, or an
 * `.console-nav-item-active` modifier) — this only supplies the hover ground for rows that are not
 * currently selected. Apply `.console-nav-item` to whatever element wraps each row (Link/button).
 */
.console-nav-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  width: 100%;
  border-radius: var(--radius-md);
  background: transparent;
  color: inherit;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--motion-fast) var(--ease-out);
}
.console-nav-item:hover,
.console-nav-item:focus-visible {
  background: var(--hover-accent-soft);
}
@media (prefers-reduced-motion: reduce) {
  .console-nav-item {
    transition: none;
  }
}

/*
 * Toggle switch (`Switch` / `ThemeToggle` in index.tsx).
 *
 * The "on" track uses `--color-primary-solid` (tokenized, repaints correctly under
 * `[data-theme="light"]`). The thumb is a **fixed light colour in both states** rather than
 * `--color-on-primary` — that token is `--on-accent`, meant for text sitting on an accent-filled
 * button (dark text on a light-blue fill), and reusing it here put a near-black thumb
 * (`--on-accent: #0A0B0D`) on a near-black "off" track, which is why it was invisible in dark
 * mode. A switch thumb has to read against both grounds it can sit on, not just one.
 */

/*
 * `.btn` — the hover state `Button` never had.
 *
 * Global hover-red pass: border and label go to `--hover-accent`/`--hover-accent-text` on every
 * variant. The 1px `translateY` is the non-colour cue WCAG 1.4.1 asks for — someone with a red/
 * green deficiency still gets a distinct, felt state change on mouseover, not just a colour swap
 * they may not perceive. `box-shadow: none` stays explicit even on hover: `tokens.test.ts` blocks
 * a shadow appearing anywhere outside `.elev-modal`/`.elev-overlay`, hover included.
 */
.btn:hover:not(:disabled) {
  border-color: var(--hover-accent);
  color: var(--hover-accent-text);
  transform: translateY(-1px);
  box-shadow: none;
}
/*
 * A link that has to sit in a row of buttons.
 *
 * `Button` carries its variant colours as inline style, so `.btn` alone gives an anchor the shape
 * and the hover and none of the ground - it renders as bare text between two real buttons, which
 * reads as a broken control rather than a link.
 *
 * This is an anchor and not a Button-with-onClick on purpose: during an incident, being able to
 * middle-click the failing pod into a new tab and keep the alert list open is worth more than the
 * consistency of routing everything through one component. It matches `secondary` exactly - same
 * three tokens - so the row still reads as one set of controls.
 */
/*
 * The shape every `.btn` needs, which until now only `Button` had.
 *
 * `Button` sets its geometry as *inline style* — display, height, padding, radius, font — and
 * `.btn` in this stylesheet carried only `:hover`, `:focus-visible` and `:active`. So an
 * `<a className="btn btn-link">` got the three colour tokens from `.btn-link` and no shape at all:
 * `display: inline`, no padding, no radius, browser default font. It rendered as a hairline
 * rectangle drawn tight around its text with square corners — which is what "Show me" and
 * "Explain it" have looked like on the overview, next to real buttons, on every one of the six
 * anchors that use this class.
 *
 * The note below this used to claim `.btn-link` "matches `secondary` exactly — same three tokens".
 * It matched the three *colours*. Three colours are not a control.
 *
 * Declared here rather than pushed into `Button`: an inline style beats a stylesheet rule, so every
 * value here is overridden by the component for a real `<button>` and applies only to the anchors
 * that had nothing. Keep the two in step — this is the pair that drifted.
 */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  height: var(--size-control);
  padding: 0 var(--space-3);
  border-radius: var(--radius-md);
  font-size: var(--text-base);
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
  transition:
    background var(--motion-fast) var(--ease-out),
    border-color var(--motion-fast) var(--ease-out);
}

/*
 * A disabled control must not offer a pointer.
 *
 * `cursor: pointer` above is new and would otherwise apply to every disabled button in the product,
 * which says "click this" about the one thing that will not respond. `.btn:hover:not(:disabled)`
 * already takes this care with the hover state; the cursor has to match it or the two disagree.
 */
.btn:disabled,
.btn[aria-disabled="true"] {
  cursor: not-allowed;
}

.btn-link {
  background: var(--bg-surface);
  color: var(--text-primary);
  border: 1px solid var(--border-strong);
  text-decoration: none;
}
.btn-link:hover {
  color: var(--text-primary);
}

.btn:focus-visible {
  /* v4: focus is the one sanctioned hue in chrome, and it comes from --focus-ring, not from
     whatever the hover accent happens to be. */
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  .btn:hover:not(:disabled) {
    transform: none;
  }
}

.switch {
  --switch-w: 40px;
  --switch-h: 22px;
  position: relative;
  display: inline-flex;
  align-items: center;
  width: var(--switch-w);
  height: var(--switch-h);
  padding: 0;
  /* Off state: a visibly light outline against the dark track, not the near-invisible
     `--border-hairline` (#232529 on #0E0F11 is barely a contrast step). */
  border: 1.5px solid var(--text-secondary);
  border-radius: var(--radius-full);
  background: var(--bg-sunken);
  cursor: pointer;
  transition: background var(--motion-fast) var(--ease-out), border-color var(--motion-fast) var(--ease-out);
}
.switch[aria-checked="true"] {
  background: var(--color-primary-solid);
  border-color: var(--color-primary-solid);
}
.switch:hover {
  border-color: var(--hover-accent);
}
.switch[aria-checked="true"]:hover {
  border-color: var(--hover-accent);
  filter: brightness(1.08);
}
.switch:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}
/*
 * Disabled has to look it. `Switch` gained `disabled` so a toggle can sit still while its change
 * saves, and with no rule here a disabled switch was indistinguishable from a live one — it even
 * answered hover — so a click that did nothing read as a broken control rather than a busy one.
 * Declared after the hover rules so it wins over them at equal specificity.
 */
.switch:disabled {
  cursor: not-allowed;
  opacity: 0.5;
}
.switch:disabled:hover {
  border-color: var(--text-secondary);
  filter: none;
}
.switch[aria-checked="true"]:disabled:hover {
  border-color: var(--color-primary-solid);
}
.switch-thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: calc(var(--switch-h) - 6px);
  height: calc(var(--switch-h) - 6px);
  border-radius: 50%;
  /* Fixed white, in both states — see the note above on why `--color-on-primary` broke this. */
  background: #ffffff;
  transition: transform var(--motion-fast) var(--ease-out);
}
.switch[aria-checked="true"] .switch-thumb {
  transform: translateX(calc(var(--switch-w) - var(--switch-h)));
  /*
   * The thumb inverts when the track does, and this line is load-bearing under a monochrome accent.
   *
   * The unchecked thumb above is fixed white on a sunken track, which was correct while the checked
   * track was a near-black fill. It no longer is: the accent is now the page's own contrast — near
   * white in dark, near black in light — so a white thumb on a checked track would be white on
   * white, and the control would read as permanently off at exactly the moment it is on.
   *
   * `--bg-page` rather than `--color-on-primary`, for the reason recorded above: that token is the
   * colour of *text* on an accent fill, and pointing the thumb at it is what made this invisible the
   * first time. The page ground is the one value guaranteed to contrast with the accent in both
   * themes, because the accent is derived from being its opposite.
   */
  background: var(--bg-page);
}
@media (prefers-reduced-motion: reduce) {
  .switch,
  .switch-thumb {
    transition: none;
  }
}

/* ================================================================== metric strip
 *
 * One bordered container divided by hairlines — NOT a grid of cards. See `MetricStrip`.
 *
 * The divider logic is the fiddly part: cells carry a right border and the last one in each visual
 * row must drop it, but "last in a row" changes with the column count. Rather than compute it, each
 * breakpoint restates the two rules for its own count, which is verbose and always correct.
 */

.metric-strip {
  display: grid;
  grid-template-columns: repeat(var(--metric-count, 4), minmax(0, 1fr));
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-card);
  background: var(--bg-surface);
  overflow: hidden;
}

.metric-cell {
  padding: var(--space-4);
  border-right: 1px solid var(--color-border);
  min-width: 0;
}
.metric-cell:last-child {
  border-right: 0;
}

.metric-label {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.metric-value {
  margin-top: 2px;
  font-size: var(--text-xl);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--color-text);
}

.metric-unit {
  margin-left: 3px;
  font-size: var(--text-sm);
  font-weight: 400;
  color: var(--color-text-subtle);
}

.metric-delta {
  margin-top: 1px;
  font-size: var(--text-xs);
}

@media (max-width: 1439px) {
  .metric-strip {
    grid-template-columns: repeat(min(var(--metric-count, 4), 4), minmax(0, 1fr));
  }
  .metric-cell:nth-child(4n) {
    border-right: 0;
  }
  .metric-cell:nth-child(-n + 4):not(:nth-last-child(-n + 4)) {
    border-bottom: 1px solid var(--color-border);
  }
}

@media (max-width: 767px) {
  .metric-strip {
    grid-template-columns: repeat(min(var(--metric-count, 2), 2), minmax(0, 1fr));
  }
  .metric-cell:nth-child(2n) {
    border-right: 0;
  }
  .metric-cell:nth-child(4n) {
    border-right: 0;
  }
  .metric-cell:not(:nth-last-child(-n + 2)) {
    border-bottom: 1px solid var(--color-border);
  }
}

@media (max-width: 399px) {
  .metric-strip {
    grid-template-columns: minmax(0, 1fr);
  }
  .metric-cell {
    border-right: 0;
  }
  .metric-cell:not(:last-child) {
    border-bottom: 1px solid var(--color-border);
  }
}

/* ================================================================== heatmap
 *
 * The cluster monitor's one signature element. See `apps/portal/app/console/heatmap.tsx`.
 *
 * Row headers are sticky against the horizontal scroll: on a 1024px window a 30-column grid scrolls
 * sideways, and a heatmap whose namespace labels have scrolled out of view is a grid of anonymous
 * squares. The header must survive the scroll or the picture stops meaning anything.
 */

/* Room on the right for the last column's angled name, which leans past the grid's edge. */
.heat-scroll {
  padding: var(--space-2) 72px var(--space-4) var(--space-4);
}

/* The density hue: a blue in both themes. The accent is near-white in dark, which drew a grid of greys. */
.heat {
  --heat-hue: var(--info);
  display: grid;
  gap: 3px;
  min-width: -moz-max-content;
  min-width: max-content;
}

.heat-corner {
  position: sticky;
  left: 0;
  z-index: 2;
  background: var(--color-surface);
}

/* The group over its run of columns: what the kinds are for, so 23 columns read as five families. */
.heat-band {
  display: flex;
  align-items: flex-end;
  padding: 0 2px 4px;
  border-bottom: 1px solid var(--color-border);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-subtle);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Whole names, angled, in sentence case — never cut off; the full plural is the tooltip. */
.heat-colh {
  position: relative;
  height: 72px;
  min-width: 0;
}
.heat-colh > span {
  position: absolute;
  left: 50%;
  bottom: 8px;
  transform-origin: 0 100%;
  transform: rotate(-40deg);
  font-size: var(--text-xs);
  font-weight: 500;
  white-space: nowrap;
  color: var(--color-text-muted);
}
.heat-colh[data-hot="yes"] > span {
  color: var(--color-text);
  font-weight: 650;
}

.heat-rowh {
  position: sticky;
  left: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  height: 26px;
  padding: 0 var(--space-3) 0 var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.heat-rowh[data-hot="yes"] {
  color: var(--color-text);
  background: color-mix(in srgb, var(--color-text) 6%, var(--color-surface));
}
.heat-rowh-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.heat-rowh-total {
  flex: none;
  margin-left: auto;
  font-size: var(--text-xs);
  color: var(--color-text-subtle);
}
.heat-rowh-bad {
  flex: none;
  padding: 1px 7px;
  border-radius: var(--radius-full);
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--severity-critical-fg);
  background: color-mix(in srgb, var(--severity-critical-fill) 16%, transparent);
}

.heat-cell {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 4px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--color-text-muted);
  cursor: pointer;
  transition: outline-color var(--motion-fast) var(--ease-out), filter var(--motion-fast) var(--ease-out);
  outline: 1px solid transparent;
  outline-offset: -1px;
}
.heat-cell[data-ink="light"] {
  color: #fff;
}
.heat-cell[data-group-start="yes"],
.heat-colh[data-group-start="yes"] {
  margin-left: 8px;
}
.heat-cell[data-cross="yes"] {
  filter: brightness(1.18);
}
.heat-cell:not(:disabled):hover {
  outline: 2px solid var(--color-text);
  outline-offset: -2px;
}
.heat-cell:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 1px;
}
.heat-cell[data-sev="bad"] {
  background: color-mix(in srgb, var(--severity-critical-fill) 30%, transparent);
  box-shadow: inset 0 0 0 1.5px var(--severity-critical-fill);
  color: var(--color-text-muted);
}
.heat-cell[data-sev="bad"] b {
  font-weight: 700;
  color: var(--severity-critical-fg);
}
.heat-cell:disabled {
  cursor: default;
  /* Overrides the global disabled fade: an empty cell is not a disabled control that has been
     greyed out, it is a true zero, and fading it would make it look like data we could not load. */
  opacity: 1;
}

.heat-summary {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
  padding: var(--space-3) var(--space-4) 0;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.heat-summary b {
  color: var(--color-text);
  font-weight: 600;
}
.heat-summary-bad {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--severity-critical-fg);
}
.heat-summary-bad b {
  color: var(--severity-critical-fg);
}
.heat-summary-bad i {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--severity-critical-fill);
}
.heat-summary-ok {
  color: var(--color-text-subtle);
}

.heat-legend {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  flex-wrap: wrap;
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--color-border);
  font-size: var(--text-xs);
  color: var(--color-text-subtle);
}
.heat-legend-item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
.heat-legend-bad {
  color: var(--color-text-muted);
}
.heat-legend-bad b {
  color: var(--severity-critical-fg);
}
.heat-legend-tail {
  margin-left: auto;
  display: flex;
  gap: var(--space-3);
  align-items: center;
}
.heat-ramp {
  --heat-hue: var(--info);
  display: inline-flex;
  gap: 2px;
}
.heat-ramp i,
.heat-swatch {
  width: 16px;
  height: 12px;
  border-radius: 3px;
}
.heat-swatch {
  background: color-mix(in srgb, var(--severity-critical-fill) 30%, transparent);
  box-shadow: inset 0 0 0 1.5px var(--severity-critical-fill);
}
/*
 * A chip that opens its own full value.
 *
 * A bare button wrapping a Tag: it must add a hit target and a focus ring and contribute no visual
 * weight of its own, or a row of metadata turns into a row of buttons and the page reads as a
 * toolbar. `display: contents` is deliberately not used — it removes the element from the box tree
 * and takes the focus ring with it.
 */
.chip-button {
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  border-radius: var(--radius-full);
  line-height: 0;
}

.chip-button:hover {
  border-color: var(--hover-accent);
  color: var(--hover-accent-text);
  transition: border-color var(--motion-fast) var(--ease-out), color var(--motion-fast) var(--ease-out);
}

.chip-button:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}

.heat-more {
  padding: 0;
  font-size: var(--text-xs);
  color: var(--color-primary);
  text-decoration: underline;
}

/* ================================================================== slide-over
 *
 * Detail views are slide-overs, not modals; modals are for confirmation and creation only.
 *
 * On a phone the right-hand panel becomes a bottom sheet — a 480px panel sliding in from the side
 * of a 390px viewport is just a modal that arrived rudely, and the thumb is at the bottom.
 */

.scrim {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  background: var(--color-scrim);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--motion-base) var(--ease-out);
}
.scrim[data-open="true"] {
  opacity: 1;
  pointer-events: auto;
}

.slideover {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: var(--z-modal);
  width: min(var(--size-slideover), 100vw);
  display: flex;
  flex-direction: column;
  background: var(--bg-surface);
  border-left: 1px solid var(--border-hairline);
  box-shadow: var(--elev-overlay);
  transform: translateX(100%);
  transition: transform var(--motion-slow) var(--ease-out);
}
.slideover[data-open="true"] {
  transform: none;
}

.slideover-head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--color-border);
  flex-shrink: 0;
}

.slideover-body {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-4);
  overscroll-behavior: contain;
}

.slideover-foot {
  display: flex;
  gap: var(--space-2);
  padding: var(--space-4);
  border-top: 1px solid var(--border-hairline);
  background: var(--bg-sunken);
  flex-shrink: 0;
}

@media (max-width: 767px) {
  .slideover {
    top: auto;
    left: 0;
    right: 0;
    width: auto;
    max-height: 88dvh;
    border-left: 0;
    border-top: 1px solid var(--color-border-strong);
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
    transform: translateY(100%);
  }
  .slideover[data-open="true"] {
    transform: none;
  }
  /* Grab handle, so the sheet reads as draggable-dismissable rather than stuck. */
  .slideover-head::before {
    content: "";
    position: absolute;
    top: 7px;
    left: 50%;
    transform: translateX(-50%);
    width: 36px;
    height: 4px;
    border-radius: 2px;
    background: var(--color-border-strong);
  }
  .slideover-head {
    position: relative;
    padding-top: var(--space-4);
  }
}

/* ================================================================== skeleton
 *
 * A skeleton mirrors the layout it is standing in for, so nothing jumps when data lands. A lone
 * spinner is on the rejection list.
 */

.sk {
  position: relative;
  overflow: hidden;
  background: var(--bg-sunken);
  border-radius: var(--radius-sm);
}
.sk::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(
    90deg,
    transparent,
    color-mix(in srgb, var(--color-text) 6%, transparent),
    transparent
  );
  animation: shimmer 1.3s infinite;
}

/* ================================================================== the ledger
 *
 * A provisioning request is a plan for machines that do not exist yet, and the thing an operator
 * actually reads is the inventory: names, roles, addresses. So the inventory is the element the
 * interface is built around rather than a table tucked under a summary.
 *
 * Set in mono with a fixed address column because that is how the file it describes is set, and
 * because a column of IPv4 addresses is only scannable when the digits line up. The coloured rule
 * down the left edge encodes which half of the build a machine belongs to — the one fact that
 * decides whether it exists yet.
 */

.ledger {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  line-height: 1.7;
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-card);
  background: var(--bg-surface);
  overflow: hidden;
}

.ledger-row {
  display: grid;
  grid-template-columns: 1.25rem minmax(7rem, auto) minmax(4.5rem, auto) 1fr;
  gap: var(--space-3);
  align-items: baseline;
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--color-border);
}
.ledger-row:last-child {
  border-bottom: 0;
}

/* The rule is the whole signal: it is the left edge of the row, not a dot beside it. */
.ledger-rule {
  width: 3px;
  height: 1.1em;
  border-radius: 1px;
  background: var(--color-text-subtle);
}
.ledger-row[data-phase="machines"] .ledger-rule {
  background: var(--color-primary);
}
.ledger-row[data-phase="openshift"] .ledger-rule {
  background: var(--color-brand);
}

.ledger-name {
  color: var(--color-text);
}
.ledger-role {
  color: var(--color-text-subtle);
}
.ledger-addr {
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}

/* A miniature of the same thing, used inside a scope card to show what a choice leaves you with. */
.ledger-mini {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 1.6;
  color: var(--color-text-subtle);
  border-left: 2px solid var(--color-border-strong);
  padding-left: var(--space-3);
  margin-top: var(--space-3);
  white-space: pre;
  overflow-x: auto;
  overscroll-behavior-x: contain;
}

/* ---- per-feature model assignment ------------------------------------------------------------
 *
 * Deliberately NOT `.ledger`. The ledger is a four-column grid for scannable mono data — a rule, a
 * machine name, a role and an address — and it was reached for here because the shapes looked alike.
 * They are not: this row carries a title, a state, a model name and two buttons, and the fifth child
 * landed in an implicit auto-width column that squeezed "Assign its own" onto three lines. A row
 * with actions in it is a different component from a row of data.
 */

.feature-list {
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-card);
  background: var(--bg-surface);
  overflow: hidden;
}

.feature-row {
  display: grid;
  grid-template-columns: 3px minmax(10rem, 1.1fr) auto minmax(7rem, 1fr) auto;
  gap: var(--space-3);
  align-items: center;
  padding: var(--space-3);
  border-bottom: 1px solid var(--color-border);
}
.feature-row:last-child {
  border-bottom: 0;
}

/* Full-height, so the coloured edge reads as belonging to the row rather than sitting in it. */
.feature-rule {
  width: 3px;
  align-self: stretch;
  border-radius: 1px;
  background: var(--color-border-strong);
}
.feature-row[data-dedicated="true"] .feature-rule {
  background: var(--color-primary);
}

.feature-title {
  font-weight: 500;
  color: var(--color-text);
}
.feature-model {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  /* A model name is one long unbroken token (`nvidia/nemotron-3-ultra-550b-a55b`) and must be
     allowed to break, or it forces the whole grid wider than its container. */
  overflow-wrap: anywhere;
}
.feature-actions {
  display: flex;
  gap: var(--space-2);
  justify-content: flex-end;
  /* Buttons size to their label. Without this they wrap mid-phrase in a narrow column. */
  white-space: nowrap;
}

/* Below this the five columns cannot hold their minimums, so the row becomes a stack beside the
   rule rather than a grid that overflows the card. */
@media (max-width: 760px) {
  .feature-row {
    grid-template-columns: 3px 1fr;
    row-gap: var(--space-2);
  }
  .feature-row > :not(.feature-rule) {
    grid-column: 2;
  }
  .feature-actions {
    justify-content: flex-start;
  }
}

/* ---- scope cards ---------------------------------------------------------------------------- */

.scope-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: var(--space-4);
}

.scope-card {
  display: flex;
  flex-direction: column;
  text-align: left;
  gap: var(--space-2);
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  color: inherit;
  cursor: pointer;
  /* Only what changes. Animating the border colour alone keeps the card off the compositor's layout
     path, which matters on a grid that re-renders as the customer types. */
  transition: border-color var(--motion-fast) var(--ease-out), box-shadow var(--motion-fast) var(--ease-out);
}

.scope-card:hover {
  border-color: var(--border-strong);
}

.scope-card:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 1px;
}

.scope-card[aria-pressed="true"] {
  border-color: var(--color-primary);
  /* An inset rule rather than a drop shadow: the selected card gains the same left edge the ledger
     uses, so selection and phase read as one visual language — and in-page separation stays 1px. */
  box-shadow: inset 3px 0 0 var(--color-primary);
}

.scope-eyebrow {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-subtle);
}
.scope-card[aria-pressed="true"] .scope-eyebrow {
  color: var(--color-primary);
}

.scope-title {
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--color-text);
}

.scope-body {
  font-size: var(--text-base);
  color: var(--color-text-muted);
}

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

/* ================================================================== KPI cards and charts
 *
 * The two-register split this section used to describe is gone.
 *
 * It existed because the previous direction banned in-page shadows, so a raised card had to be an
 * exception carved out for "summary surfaces" and kept away from everything else. The spec now makes
 * the card the primary unit everywhere, so there is one register: a KPI tile is a `.card` with a
 * hero figure in it, and `.metric-strip` is what a surface uses when eight readings have to fit in
 * one band, not a lesser-status alternative to a card.
 */

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: var(--space-4);
}

/*
 * The Overview's opener: one card leads, three sit beside it.
 *
 * `.stat-grid` above is four equal columns, which is blacklist item 4 — every card the same size, so
 * nothing on the page says what matters. This is the same four figures arranged by priority instead
 * of by source order, and which one leads is decided from the data by `lib/lead-tile.ts`.
 *
 * Below 900px it is a plain single column and the lead card is simply first. The asymmetry is a
 * large-screen affordance, not a structure the layout depends on — an operator on a laptop still
 * reads the same card first because it is still at the top.
 */
.stat-board {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
}

@media (min-width: 900px) {
  .stat-board {
    /*
     * Two columns, and the lead spans all three rows the secondary cards create.
     *
     * `span 3` rather than `1 / -1`: the rows here are implicit, and `-1` only resolves against an
     * explicit track list — against implicit rows it silently resolves to the first line and the
     * card collapses to one row tall. That failure looks like a styling nit and is actually the
     * whole layout.
     */
    /*
     * One row, aligned to the window. The lead takes the double column and `order` moves it to
     * the front whichever card is promoted — auto-placement honours `order`, so the 2fr track is
     * always the lead's. The previous shape (lead spanning three stacked rows) put the board's
     * whole height behind one figure and left the other three floating in white space.
     */
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr);
  }

  .stat-board > .stat-card--lead {
    order: -1;
  }
}

/*
 * The lead card. Bigger figure, more room, and a stated reason — never bigger on its own.
 *
 * The hero step (`--text-3xl`) is reserved for this one card, which is what keeps the spec's "1-3
 * hero numbers per page" true: promoting a card is also the act of demoting the other three, so the
 * count cannot drift upward.
 */
.stat-card--lead {
  /*
   * Row, not column: the text block and the (optional) figure sit side by side, and with no figure
   * the single `.stat-card-main` child fills the row so the card looks exactly as it did before
   * the figure slot existed. `flex-wrap` is the narrow-screen behaviour — the comb drops under the
   * text rather than crushing it.
   */
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-5);
}

.stat-card--lead .stat-card-main {
  flex: 1 1 240px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-2);
}

/*
 * The lead card's figure slot — the hex hive on the Overview.
 *
 * It exists because the lead card spans three rows of its neighbours and centred four lines of
 * text in the rest, which read as an unfinished page. The slot's contract is the same as the
 * card's: whatever renders here restates the card's own numbers, never decorates around them.
 */
.stat-card-figure {
  flex: 1 1 220px;
  min-width: 0;
  display: flex;
  justify-content: center;
}

/*
 * The page hero — the one figure in this product allowed to be this big.
 *
 * v3 orders a page hero at 36-40px, and `tokens.ts` deliberately refuses to carry a step for it:
 * `tokens.test.ts` asserts "32px is the ceiling; there is nothing to escalate to", so the scale
 * cannot grow one. Hence a literal. The spec's own wording is "the page hero itself is 36-40px,
 * **set at the component**", and this is that component.
 *
 * What was here before re-declared `--text-3xl` — the same 32px `.stat-card-value` already gives
 * every other card. Promoting a card changed its padding and earned it a reason line, but the
 * number did not grow, so nothing on any page dominated and the spec's own five-second test failed
 * everywhere at once. `stat-card-lead.test.ts` compares the two rules against each other rather
 * than reading either alone, because equal-but-separately-declared is exactly how this survived
 * review the first time.
 */
.stat-card--lead .stat-card-value {
  font-size: 40px;
  line-height: var(--leading-hero);
  /* Tighter than the 32px step's -0.02em: the same optical tracking costs more at 40px. */
  letter-spacing: -0.03em;
}

/*
 * An alarming lead card colours its figure, and this is not a second accent hue.
 *
 * Blacklist 7 forbids more than one accent; severity is a separate, already-sanctioned channel —
 * the same one the 3px `--severity-critical-fill` rail on this very card is drawn from, and the one
 * every pill and dot in the product already reads. This adds no colour that was not already on the
 * card; it puts the existing verdict on the element the reader actually looks at.
 *
 * Scoped to `--lead` on purpose. A strip of four alarming cards each with a red figure is the
 * "wall of undifferentiated boxes" v3 exists to prevent — only the promoted card gets this.
 */
.stat-card--lead.stat-card--alarming .stat-card-value {
  color: var(--severity-critical-fg);
}

/*
 * The trend under the hero: shape where the number alone cannot carry it.
 *
 * v3's "fewer charts than you think" is a rule about *replacing* numbers with charts, not about
 * putting a shape beside one. The figure still answers the question; the sparkline answers the
 * follow-up ("is it getting worse?") that otherwise costs a page load. It is 22px tall and carries
 * no axis, no gridline and no label — it cannot be misread as the measurement.
 */
.stat-card-trend {
  margin-top: var(--space-3);
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 22px;
}

.stat-card-trend-note {
  font-size: var(--text-xs);
  color: var(--color-text-subtle);
}

/*
 * Why this card is the one that grew, in words, on the card.
 *
 * A tile that is larger than its neighbours with no explanation is styling impersonating
 * information. This line always names the count behind the promotion, so the emphasis is auditable
 * by the person reading it.
 */
.stat-card-why {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  font-family: var(--font-sans);
}

.stat-card-why--alarming {
  color: var(--severity-critical-fg);
  font-weight: 500;
}

/*
 * The severity rail, and it is the only place this direction spends a decorative-looking device.
 *
 * It is a state channel, not an accent: it appears solely when `leadTile` reports `alarming`, so its
 * presence carries the same fact as the tone of the sentence beside it. Colour is never the only
 * signal here — the reason line says it in words directly underneath.
 */
.stat-card--alarming {
  border-left: 3px solid var(--severity-critical-fill);
}

.stat-card {
  /* The spec's floor is 16 and its target 20-24. */
  padding: var(--space-5);
  border-radius: var(--radius-card);
  /* Same glass treatment as `.card` (see that rule's comment) — this is a separate class with
     its own `background: var(--bg-surface)`, which is why the earlier `.card` fix didn't reach
     these tiles even though they look identical to a `.card`. Kept in sync deliberately: same
     94%-opacity mix, same blur/saturate recipe, so a page mixing `.card` and `.stat-card` reads
     as one consistent surface, not two different finishes. */
  background: color-mix(in srgb, var(--bg-surface) 94%, transparent);
  backdrop-filter: blur(16px) saturate(140%);
  -webkit-backdrop-filter: blur(16px) saturate(140%);
  /* v4: the 1px border is the whole separation mechanism. No shadow on an in-page surface. */
  border: 1px solid var(--border-hairline);
  min-width: 0;
  /* Corners clip, matching `.card`'s own rule — without this, an inner fill (e.g. the
     `--severity-critical-fill` left border on `.stat-card--alarming`, or any future full-bleed
     child) can sit past the rounded corner and read as square again at the exact spot the curve
     is meant to be visible. */
  overflow: hidden;
}

/* The 11px uppercase eyebrow, spent here rather than on every label. */
.stat-card-label {
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.stat-card-row {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin-top: var(--space-2);
  flex-wrap: wrap;
}

.stat-card-value {
  font-size: var(--text-3xl);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--color-text);
}

.stat-card-delta {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  font-size: var(--text-base);
  font-weight: 500;
}

.stat-card-compare {
  margin-top: var(--space-2);
  font-size: var(--text-base);
  color: var(--color-text-subtle);
}

@media (max-width: 767px) {
  .stat-card {
    padding: var(--space-4);
  }
  /* A 32px figure across a 390px viewport leaves no room for the comparison line to stay on one
     line, and a wrapped baseline reads as two facts. The card steps down rather than reflowing. */
  .stat-card-value {
    font-size: var(--text-2xl);
  }
  /*
   * The lead steps down too, and must be declared here explicitly.
   *
   * `.stat-card--lead .stat-card-value` outranks the bare `.stat-card-value` above it on
   * specificity, so without this rule the 40px hero ignores the step-down entirely and overflows a
   * 390px viewport — the one place the promotion would do visible damage. It keeps its lead over
   * the demoted cards (32 vs 28), because the hierarchy is the point and dropping both to 28 would
   * flatten the page exactly the way the desktop bug already did.
   */
  .stat-card--lead .stat-card-value {
    font-size: var(--text-3xl);
  }
}

/* ---- custom board widgets ------------------------------------------------------------------ */

/*
 * A board widget: the whole card is draggable, and its editing controls stay out of sight until
 * the card is hovered or holds focus. Opacity, not display — the toolbar keeps its space, so
 * revealing it never reflows the card, and hidden controls stay reachable by keyboard (tabbing
 * into one is itself what reveals them, via :focus-within). On touch there is no hover, so the
 * controls are simply always shown rather than unreachable.
 */
.bw-frame > .card-head {
  cursor: grab;
}

.bw-frame:active > .card-head {
  cursor: grabbing;
}

.bw-frame .card-action {
  opacity: 0;
  transition: opacity 120ms var(--ease-out);
}

.bw-frame:hover .card-action,
.bw-frame:focus-within .card-action {
  opacity: 1;
}

@media (hover: none) {
  .bw-frame .card-action {
    opacity: 1;
  }
}

/*
 * The resize grip: the widget's bottom-right corner, dragged sideways to any width from a quarter
 * of the board to all of it. Same reveal contract as the toolbar — hidden at rest, present on
 * hover or focus, always there for touch.
 */
.bw-cell {
  position: relative;
}

/*
 * The free board: the custom page's variant of `.board`, and only there — the fixed monitoring
 * boards keep pure content-driven rows. The auto-row step is what gives "drag down to make it
 * taller" a unit to snap to; `minmax(…, auto)` keeps it a floor, so content taller than the box
 * grows the box rather than clipping inside it. Cards fill their cell so a two-row widget is a
 * two-row card, not a one-row card floating in a two-row hole.
 */
.board--free {
  /* `--bw-unit` is set on the wall (full screen) so the widgets fill the screen's height. */
  grid-auto-rows: minmax(var(--bw-unit, 140px), auto);
}

.board--free > [data-rows="2"] {
  grid-row: span 2;
}
.board--free > [data-rows="3"] {
  grid-row: span 3;
}
.board--free > [data-rows="4"] {
  grid-row: span 4;
}

.board--free > .bw-cell > .card {
  height: 100%;
}

/* The card being moved: dimmed in place while the board reflows around the pointer, which is the
   live feedback that replaced the old wait-for-drop dashed outline. */
.bw-cell--dragging {
  opacity: 0.45;
}

/*
 * The board's Grafana register: banner counters and threshold-tinted figures. The colour is
 * always the severity system speaking — a tile is green because its number is healthy, red
 * because it is not, never because red looked good there. Soft severity backgrounds with the
 * strong foreground, so the same tiles hold up on both themes.
 */
.bw-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: var(--space-2);
}

.bw-stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
  background: var(--color-surface-raised);
  min-width: 0;
}

.bw-stat-value {
  font-size: var(--text-2xl);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.15;
  color: var(--color-text);
}

.bw-stat-label {
  font-size: var(--text-xs);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.bw-stat-sub {
  font-size: var(--text-xs);
  color: var(--color-text-subtle);
}

.bw-stat--ok {
  background: var(--severity-ok-bg);
  border-color: transparent;
}
.bw-stat--ok .bw-stat-value {
  color: var(--severity-ok-fg);
}
.bw-stat--warning {
  background: var(--severity-warning-bg);
  border-color: transparent;
}
.bw-stat--warning .bw-stat-value {
  color: var(--severity-warning-fg);
}
.bw-stat--critical {
  background: var(--severity-critical-bg);
  border-color: transparent;
}
.bw-stat--critical .bw-stat-value {
  color: var(--severity-critical-fg);
}

/*
 * The board's canvas, at reference-board density. Theme-native: every colour here is a token, so
 * dark mode renders the reference NOC wall and light mode renders the same board in the light
 * palette — the canvas follows the reader's theme along with the portal around it.
 */
.bw-canvas {
  background: var(--bg-page);
  color: var(--color-text);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-card);
  padding: var(--space-4);
  display: grid;
  gap: var(--space-3);
  /* Full screen makes this box as tall as the display, and a grid's default `stretch` hands that
     spare height to its rows — the row holding only the Exit button grew into a band of empty space
     above every widget. Pack the rows at the top; spare height belongs below the board. */
  align-content: start;
}

/* Reference-board density: 8px gutters, tighter cards, panel-sized titles. */
.bw-canvas .board--free {
  gap: var(--space-2);
}

.bw-canvas .card-body {
  padding: var(--space-3) var(--space-4) var(--space-4);
}

.bw-canvas .card-head {
  padding: var(--space-3) var(--space-4) 0;
}

.bw-canvas .card-title {
  font-size: var(--text-sm);
}

.bw-canvas .card-subtitle {
  font-size: 11px;
}

/* A threshold-tinted figure inside a table cell — the heat-table treatment. */
.bw-heat {
  display: inline-block;
  min-width: 44px;
  padding: 2px 8px;
  border-radius: var(--radius-sm);
  text-align: right;
  font-size: var(--text-xs);
}

.bw-heat--ok {
  background: var(--severity-ok-bg);
  color: var(--severity-ok-fg);
}
.bw-heat--warning {
  background: var(--severity-warning-bg);
  color: var(--severity-warning-fg);
}
.bw-heat--critical {
  background: var(--severity-critical-bg);
  color: var(--severity-critical-fg);
}
.bw-heat--none {
  color: var(--color-text-subtle);
}

.bw-grip {
  position: absolute;
  right: 2px;
  bottom: 2px;
  width: 16px;
  height: 16px;
  border: 0;
  padding: 0;
  background: transparent;
  cursor: nwse-resize;
  color: var(--color-text-subtle);
  opacity: 0;
  transition: opacity 120ms var(--ease-out);
  touch-action: none;
  line-height: 1;
  font-size: 12px;
}

.bw-cell:hover .bw-grip,
.bw-grip:focus-visible {
  opacity: 1;
}

@media (hover: none) {
  .bw-grip {
    opacity: 1;
  }
}

/* ---- hex hive ------------------------------------------------------------------------------ */

/*
 * The Overview lead card's figure: a comb of hexagons, one cell per real unit the card counts.
 * See `hive.tsx` for the honesty rules (no empty cells, stated scale, worst severity innermost).
 *
 * Colour weights are the hierarchy: critical cells are solid, everything else sits at reduced
 * fill-opacity, so sixteen critical among sixty read as the subject and not as one tint among
 * four. The stroke is the card's own surface colour — that gap is what draws the comb lines
 * without a second border colour existing anywhere.
 */
.hive {
  margin: 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
}

/* The comb is sized by its own geometry (`hiveLayout`), not stretched to the column — a card with
   three alerts draws three cells, not three cells inflated to fill the space. */
.hive-comb {
  position: relative;
  flex: none;
  max-width: 100%;
}

/* One cell. An anchor when the unit has somewhere to go, a span when it does not — the hexagon
   itself is the clipped `<i>` inside, so the focus ring can sit on this unclipped box (an outline
   on a clip-path'd element is clipped along with everything else). */
.hive-cell {
  position: absolute;
  display: block;
  border-radius: var(--radius-sm);
  text-decoration: none;
}

.hive-cell:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 1px;
  z-index: 4;
}

.hive-cell:hover {
  z-index: 3;
}

.hive-hex {
  display: block;
  width: 100%;
  height: 100%;
  clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
  transition:
    transform 120ms var(--ease-out),
    opacity 120ms var(--ease-out);
}

.hive-hex--critical {
  background: var(--severity-critical-fill);
  /* The live-dot family: these are firing now. Slower and shallower than `rail-pulse` —
     sixteen cells at that tempo would strobe. */
  animation: hive-pulse 2.6s ease-in-out infinite;
}

.hive-hex--warning {
  background: var(--severity-warning-fill);
  opacity: 0.38;
}

.hive-hex--ok {
  background: var(--severity-ok-fill);
  opacity: 0.42;
}

.hive-hex--info {
  background: var(--color-primary);
  opacity: 0.5;
}

/* Hover and focus answer "which one is this": the cell steps forward and its tooltip names the
   unit. Pausing the pulse on hover is deliberate — a tooltip over a blinking cell reads as the
   alert changing under the pointer. */
a.hive-cell:hover .hive-hex,
a.hive-cell:focus-visible .hive-hex {
  opacity: 1;
  transform: scale(1.16);
  animation: none;
}

/* Shallow on purpose: at half opacity a mid-pulse critical cell reads as a lesser severity, which
   is a colour telling a lie for a fifth of every cycle. The blink is visible; the red never stops
   being red. */
@keyframes hive-pulse {
  50% {
    opacity: 0.72;
  }
}

@media (prefers-reduced-motion: reduce) {
  /* Still solid, still red — the fact stays, the motion goes. */
  .hive-hex--critical {
    animation: none;
  }
}

/* The identity, on demand: rule name on the first line, severity · subject · cluster on the
   second. Same surface language as every other raised element — one border, no shadow. */
.hive-tip {
  position: absolute;
  bottom: calc(100% + 7px);
  left: 50%;
  transform: translateX(-50%);
  display: none;
  flex-direction: column;
  gap: 2px;
  width: -moz-max-content;
  width: max-content;
  max-width: 240px;
  padding: 6px 10px;
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  font-size: var(--text-xs);
  line-height: 1.4;
  text-align: left;
  pointer-events: none;
  z-index: 5;
}

/* Top-row cells open downward: the card clips, so a tip above them loses its first line — which is
   the alert's name. See the `TIP_ROOM` note in hive.tsx. */
.hive-cell--tip-below .hive-tip {
  bottom: auto;
  top: calc(100% + 7px);
}

.hive-cell:hover .hive-tip,
.hive-cell:focus-visible .hive-tip {
  display: flex;
}

.hive-tip-title {
  color: var(--color-text);
  font-weight: 500;
}

.hive-tip-sub {
  color: var(--color-text-muted);
}

/* The words under the comb: per-severity counts, then what one cell stands for. This line is why
   colour is never the only signal here. */
.hive-caption {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

.hive-key {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  white-space: nowrap;
}

/* A tiny hexagon, not a circle — the legend swatch matches the shape it explains. */
.hive-key-dot {
  width: 9px;
  height: 10px;
  flex: none;
  clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
}

.hive-key-dot--critical {
  background: var(--severity-critical-fill);
}

.hive-key-dot--warning {
  background: var(--severity-warning-fill);
  opacity: 0.65;
}

.hive-key-dot--ok {
  background: var(--severity-ok-fill);
  opacity: 0.6;
}

.hive-key-dot--info {
  background: var(--color-primary);
  opacity: 0.65;
}

.hive-scale {
  color: var(--color-text-subtle);
}

/* ---- fleet chart --------------------------------------------------------------------------- */

.chart-wrap {
  position: relative;
  min-width: 0;
}

/*
 * The radial glow that used to sit behind the plot is gone.
 *
 * It was the previous direction's one sanctioned gradient. The spec now names a different one — "a
 * soft area-fill gradient fading to transparent UNDER the line only (this is the one place a
 * gradient belongs)" — and allows no others, so the wash was removed rather than kept alongside.
 * The replacement is an SVG `<linearGradient>` per series in `FleetChart`, which is why no gradient
 * appears in this stylesheet at all and `tokens.test.ts` can reject every one it finds.
 */

.chart-svg {
  position: relative;
  z-index: 1;
  display: block;
  width: 100%;
  overflow: visible;
}

.chart-grid {
  stroke: var(--color-grid);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.chart-axis {
  fill: var(--color-text-subtle);
  font-size: 11px;
  font-family: var(--font-mono);
}

.chart-line {
  /* The spec's 2px. At 1.5 the line reads as a hairline sitting on the fill rather than its edge. */
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.chart-cross {
  stroke: var(--color-border-strong);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.chart-dot {
  stroke: var(--bg-surface);
  stroke-width: 2;
  vector-effect: non-scaling-stroke;
}

.chart-xaxis {
  display: flex;
  justify-content: space-between;
  margin-top: var(--space-1);
  font-size: var(--text-xs);
  font-family: var(--font-mono);
  color: var(--color-text-subtle);
}

.chart-tip {
  position: absolute;
  top: var(--space-3);
  z-index: 2;
  transform: translateX(-50%);
  pointer-events: none;
  min-width: 150px;
  padding: var(--space-3);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  /* An overlay, so the overlay shadow — it floats over the plot rather than sitting in the card. */
  box-shadow: var(--elev-overlay);
}

.chart-tip-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  white-space: nowrap;
}
.chart-tip-swatch {
  width: 8px;
  height: 8px;
  border-radius: 2px;
  flex: none;
}
.chart-tip-label {
  color: var(--color-text-muted);
}
.chart-tip-value {
  margin-left: auto;
  font-weight: 500;
  color: var(--color-text);
}
.chart-tip-at {
  margin-top: 2px;
  font-size: var(--text-xs);
  color: var(--color-text-subtle);
}

/* Solid, not dashed. A dashed outline is the vocabulary the spec assigns to a broken widget, and an
   empty chart is a chart with nothing to draw yet — a different fact, needing a different shape. */
.chart-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-sunken);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  color: var(--color-text-subtle);
  font-size: var(--text-base);
  text-align: center;
  padding: var(--space-4);
}

/* ---- dashboard panel chart ----------------------------------------------------------------- */

/*
 * The plot and its legend sit side by side, and wrap when they cannot both fit.
 *
 * The legend is a table rather than a row of chips because the number beside a name is what the
 * panel is read for — "which container spiked, and to what" is a comparison between rows, and a chip
 * row cannot be sorted or scanned down a column.
 *
 * ## Why this is flex and not the grid it used to be
 *
 * It was `grid-template-columns: minmax(0, 1.9fr) minmax(220px, 1fr)`, which hands the legend a
 * *share* of the width rather than the width it needs, and `.panel-legend` carried `overflow-x:
 * hidden`. Measured on the capacity board: the table's min-content is 323px, the track gave it 236,
 * and the last 87px — the whole **Mean** column, header and all six values — was cut off with no
 * scrollbar and no other way to reach it. A statistic the panel computes, sorts by and claims to
 * show simply was not on screen, and nothing about the card said so.
 *
 * The stacking rule made it worse rather than better, because it keyed on the **viewport**:
 *
 *     @media (max-width: 900px) { … }
 *
 * The overflow depends on how wide the *card* is, and these cards are grid spans — a `span="7"` card
 * on a 1101px viewport is about 466px wide, far narrower than the 900px breakpoint ever tests for.
 * So the panels most likely to clip were exactly the ones the media query never fired for.
 *
 * Flex wrapping asks the right question. The plot declares the narrowest it is worth drawing at and
 * the legend asks for its content; when both cannot be had, the legend wraps underneath at full
 * width — which is where a table that needs 323px belongs anyway — instead of being trimmed.
 */
.panel-chart {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  align-items: flex-start;
}

/*
 * `340px` is a floor, not a width: below it a three-hour trend is too cramped to read a shape off,
 * and the legend is better placed underneath. Above it the plot takes every pixel the legend does
 * not need — it is the only item that grows, so leftover space goes to the picture rather than
 * padding out a table that has already been given what it asked for.
 */
.panel-plot {
  position: relative;
  /* 1000 against the legend's 1: beside it, the plot takes the spare width. See `.panel-legend`. */
  flex: 1000 1 340px;
  min-width: 0;
}

/*
 * `width: 100%` with `height: auto` would re-introduce the distortion this component exists to
 * avoid. The SVG is given the width it was measured at, so viewBox units are CSS pixels.
 */
.panel-svg {
  display: block;
  width: 100%;
  overflow: visible;
}

.panel-grid {
  stroke: var(--color-grid);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.panel-axis {
  fill: var(--color-text-subtle);
  font-size: 10px;
  font-family: var(--font-mono);
}

/*
 * 1.25px, against `FleetChart`'s 2.
 *
 * That panel draws two series and the line is the subject. This one draws twenty, where a 2px stroke
 * makes overlapping series merge into a single mass and the legend becomes the only readable part.
 */
.panel-line {
  stroke-width: 1.25;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.panel-cross {
  stroke: var(--color-border-strong);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.panel-dot {
  stroke: var(--bg-surface);
  stroke-width: 1.5;
  vector-effect: non-scaling-stroke;
}

/* ---- drag to select a window ------------------------------------------------------------------
 *
 * The band is the accent at low alpha with a solid edge on each side. Alpha rather than a token
 * colour on purpose: it has to sit *over* the plot — the point of the gesture is watching which
 * peak you are catching — and an opaque band would hide the thing being selected.
 *
 * `user-select: none` on the plot is not cosmetic. Without it a drag across an SVG selects the axis
 * labels, so the gesture ends with the chart's text highlighted in blue and the browser's own
 * drag-to-select fighting the one being implemented here.
 */
.panel-svg-brushable {
  cursor: crosshair;
  -moz-user-select: none;
       user-select: none;
  -webkit-user-select: none;
  touch-action: none;
}

.chart-brush-band {
  fill: var(--accent);
  fill-opacity: 0.14;
}

.chart-brush-edge {
  stroke: var(--accent);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

/* Dashed, because a request or a limit is a boundary someone configured, not a thing measured. */
.panel-band {
  stroke-width: 1;
  stroke-dasharray: 4 3;
  vector-effect: non-scaling-stroke;
}
.panel-band-info {
  stroke: var(--severity-info-fg);
}
.panel-band-warning {
  stroke: var(--severity-warning-fg);
}
.panel-band-critical {
  stroke: var(--severity-critical-fg);
}
.panel-band-label {
  fill: var(--color-text-subtle);
  font-size: 9px;
  font-family: var(--font-mono);
}

.panel-tip {
  position: absolute;
  top: var(--space-2);
  z-index: 2;
  pointer-events: none;
  min-width: 180px;
  max-width: 320px;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  box-shadow: var(--elev-overlay);
}

.panel-tip-at {
  margin-bottom: var(--space-1);
  font-size: var(--text-xs);
  color: var(--color-text-subtle);
  white-space: nowrap;
}

.panel-tip-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-xs);
  white-space: nowrap;
}

.panel-tip-swatch {
  width: 8px;
  height: 2px;
  border-radius: 1px;
  flex: none;
}

.panel-tip-label {
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--color-text-muted);
}

.panel-tip-value {
  margin-left: auto;
  padding-left: var(--space-3);
  font-weight: 500;
  color: var(--color-text);
}

.panel-tip-more,
.panel-tip-total {
  margin-top: var(--space-1);
  padding-top: var(--space-1);
  border-top: 1px solid var(--border-hairline);
  font-size: var(--text-xs);
  color: var(--color-text-subtle);
}

/*
 * Sized to the table beside the plot, and to the full row once it wraps underneath it.
 *
 * `flex: 1 1 auto` is three decisions. Basis `auto`, so what it asks for is the width the numbers
 * actually occupy. Shrink allowed, so a card narrower than the table gets a scrollbar rather than an
 * element hanging out of it. And grow — but against `.panel-plot`'s grow of 1000, so beside the plot it
 * takes a thousandth of the spare width (well under a pixel) and the picture keeps the rest.
 *
 * Grow was `0`, and that broke the promise the `.panel-chart` comment makes: a legend that wraps
 * "underneath at full width". With no grow it wrapped at its content width instead — a narrow table
 * hugging the left edge of a wide card, with the rest of the row empty. Alone on its line, any grow
 * factor of 1 or more takes the whole line, so it now fills the card at every window size.
 *
 * `overflow-x` is `auto` and must never go back to `hidden`. Hidden is not a layout fix — it is the
 * clipping made invisible, and it hid a whole column here for as long as this panel has existed.
 */
.panel-legend {
  flex: 1 1 auto;
  min-width: 0;
  max-height: 240px;
  overflow-y: auto;
  overflow-x: auto;
  /* Now that it scrolls sideways it must keep the wheel on that axis, like every other x-scroller
     here — `scroll.test.ts` derives this over the whole stylesheet and caught the omission. Vertical
     is deliberately left to chain to the page; containing both is what froze the dashboard once. */
  overscroll-behavior-x: contain;
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-sm);
}

.panel-legend table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-xs);
}

.panel-legend thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  padding: 0;
  background: var(--bg-sunken);
  border-bottom: 1px solid var(--border-hairline);
  text-align: left;
  font-weight: 500;
}

.panel-legend th.num,
.panel-legend td.num {
  text-align: right;
}

.panel-legend-sort {
  width: 100%;
  padding: var(--space-1) var(--space-2);
  border: 0;
  background: none;
  color: var(--color-text-muted);
  font: inherit;
  text-align: inherit;
  cursor: pointer;
}

.panel-legend-sort:hover {
  color: var(--color-text);
}

.panel-legend-caret {
  margin-left: 2px;
  font-size: 8px;
}

.panel-legend tbody tr {
  cursor: pointer;
  border-bottom: 1px solid var(--border-hairline);
}

.panel-legend tbody tr:last-child {
  border-bottom: 0;
}

.panel-legend tbody tr:hover,
.panel-legend tbody tr:focus-visible {
  background: var(--bg-hover);
}
/* A row is reachable by Tab (`activate()`), so where Tab has landed has to be visible. */
.panel-legend tbody tr:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: -2px;
}

/* Isolation is shown by fading the others rather than hiding them: the row you clicked stays where
   it was in the list, so clicking again to undo does not require finding it a second time. */
.panel-legend tbody tr.dim {
  opacity: 0.4;
}

.panel-legend tbody tr.isolated {
  background: var(--accent-soft);
}

.panel-legend td {
  padding: 2px var(--space-2);
  color: var(--color-text-muted);
  white-space: nowrap;
}

.panel-legend-swatch {
  display: inline-block;
  width: 10px;
  height: 2px;
  margin-right: var(--space-2);
  border-radius: 1px;
  vertical-align: middle;
}

.panel-legend-name {
  color: var(--color-text);
}

.panel-legend-sub {
  margin-left: var(--space-2);
  color: var(--color-text-subtle);
}

/* ---- dashboard controls -------------------------------------------------------------------- */

/*
 * One row above every panel, wrapping rather than scrolling.
 *
 * These controls decide what all three panels below are showing, so they have to stay visible
 * together — a horizontally scrolling bar hides the range picker behind the namespace filter and
 * makes the window the reader is looking at a thing they have to go and check.
 */
.dash-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
  padding: var(--space-3);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  background: var(--bg-sunken);
}

.dash-control {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

.dash-control-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-left: auto;
}

/* The pinned-window chip. Accent-tinted because it is a state the reader put the page into and can
   leave — not a warning, and not another neutral select. */
.dash-pin {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: var(--size-control);
  padding: 0 var(--space-2) 0 var(--space-3);
  border: 1px solid var(--accent);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  color: var(--color-text);
  white-space: nowrap;
}

.dash-pin-window {
  font-size: var(--text-sm);
}

.dash-pin-clear {
  border: 0;
  background: transparent;
  color: var(--accent);
  font: inherit;
  font-size: var(--text-xs);
  cursor: pointer;
  padding: 0 var(--space-1);
}

.dash-pin-clear:hover {
  text-decoration: none;
}

/*
 * "Why did this change?" beside the zoomed window.
 *
 * Styled as the primary of the two: it is the reason most people dragged, and Clear is the way out.
 * Same size and row as Clear so the pair reads as one control rather than a button with a link
 * stuck to it.
 */
.dash-pin-explain {
  border: 1px solid var(--color-border);
  background: var(--bg-surface);
  color: var(--accent-text);
  font: inherit;
  font-size: var(--text-xs);
  font-weight: 500;
  cursor: pointer;
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
  white-space: nowrap;
}

.dash-pin-explain:hover {
  background: var(--accent-soft);
  border-color: var(--accent);
}

/*
 * The legend's ask column.
 *
 * The column exists only while a window is selected, so it must not reserve width the rest of the
 * time — it is rendered conditionally rather than hidden, and the header cell is empty because
 * there is nothing to sort by and a heading over one-word buttons is noise.
 */
.panel-legend-ask-h {
  width: 1%;
}

.panel-legend-ask {
  width: 1%;
  text-align: right;
  white-space: nowrap;
}

.panel-legend-ask-btn {
  border: 1px solid var(--color-border);
  background: var(--bg-surface);
  color: var(--accent-text);
  font: inherit;
  font-size: var(--text-xs);
  cursor: pointer;
  padding: 1px var(--space-2);
  border-radius: var(--radius-sm);
}

.panel-legend-ask-btn:hover {
  background: var(--accent-soft);
  border-color: var(--accent);
}

.dash-updated {
  font-size: var(--text-xs);
  color: var(--color-text-subtle);
}

.dash-picker {
  position: relative;
}

.dash-picker-button {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  min-width: 160px;
  height: var(--size-control);
  padding: 0 var(--space-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--bg-surface);
  color: var(--color-text);
  font: inherit;
  font-size: var(--text-sm);
  cursor: pointer;
}

.dash-picker-button:hover {
  background: var(--bg-hover);
}

.dash-picker-summary {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.dash-picker-menu {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  z-index: var(--z-raised);
  width: 280px;
  padding: var(--space-2);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  box-shadow: var(--elev-dropdown);
}

.dash-picker-filter {
  width: 100%;
  height: var(--size-control-sm);
  padding: 0 var(--space-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--bg-page);
  color: var(--color-text);
  font: inherit;
  font-size: var(--text-sm);
}

.dash-picker-all {
  width: 100%;
  margin-top: var(--space-2);
  padding: var(--space-1) var(--space-2);
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--color-primary-text);
  font: inherit;
  font-size: var(--text-sm);
  text-align: left;
  cursor: pointer;
}

.dash-picker-all:hover {
  background: var(--bg-hover);
}

.dash-picker-list {
  max-height: 260px;
  margin-top: var(--space-1);
  overflow-y: auto;
}

.dash-picker-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  color: var(--color-text);
  cursor: pointer;
}

.dash-picker-item:hover {
  background: var(--bg-hover);
}

.dash-picker-empty {
  padding: var(--space-3) var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-text-subtle);
}

.dash-panels {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.dash-footnote {
  margin: var(--space-2) 0 0;
  font-size: var(--text-xs);
  color: var(--color-text-subtle);
}

/* ---- gauge --------------------------------------------------------------------------------- */

.gauge {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-1);
  min-width: 0;
}

.gauge-svg {
  display: block;
  width: 100%;
  max-width: 140px;
}

.gauge-track {
  stroke: var(--color-grid);
  stroke-width: 9;
  stroke-linecap: round;
}

.gauge-fill {
  stroke-width: 9;
  stroke-linecap: round;
}

.gauge-ok {
  stroke: var(--severity-ok-fg);
}
.gauge-warning {
  stroke: var(--severity-warning-fg);
}
.gauge-critical {
  stroke: var(--severity-critical-fg);
}
.gauge-unknown {
  stroke: var(--color-text-subtle);
}

.gauge-value {
  fill: var(--color-text);
  font-size: 20px;
  font-weight: 600;
}

.gauge-sub {
  fill: var(--color-text-subtle);
  font-size: 9px;
  font-family: var(--font-mono);
}

.gauge-label {
  font-size: var(--text-sm);
  color: var(--color-text);
  text-align: center;
}

.gauge-caption {
  font-size: var(--text-xs);
  color: var(--color-text-subtle);
  text-align: center;
}

/* ================================================================== collapsible nav group
 *
 * The console's resource-type groups (Workloads, Networking, Storage, Cluster) are disclosure
 * headers. Three details carry the behaviour:
 *
 *   - The chevron rotates rather than swapping glyph, so the open and closed states are the same
 *     shape at two angles and the eye reads it as one control moving.
 *   - The whole header is the hit target, not the chevron. A 11px arrow is a pixel-hunt.
 *   - The unhealthy count keeps its colour when the group is closed. A fold that hides an alarm is
 *     a way of not seeing; the header has to carry it.
 */

/* Groups are separated by a hairline, not by spacing alone.
 *
 * With three groups, whitespace was enough to read the sidebar as sections. At seven it is not: the
 * eye needs an edge to count against, and seven evenly-spaced blocks of identical text read as one
 * continuous list. The rule goes *between* groups via the sibling selector — a border above the
 * first group would draw a line under the product name, separating the nav from its own header. */
.nav-group + .nav-group {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-hairline);
}
.nav-group {
  margin-bottom: var(--space-2);
}

/* ------------------------------------------------------------- the Alerts page's cluster filter
 *
 * The tab strip and the filter share one row. The strip owns the rule under it — the filter is a
 * control over what the tabs show, not a sixth tab, so it must not sit on the tab labels' own
 * baseline or it reads as one.
 */
.alerts-tabrow {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  border-bottom: 1px solid var(--border-hairline);
  /*
   * Pulled up towards the opener and close to the panel below: the page's section gap above and
   * `--space-5` below left about 40px of air on each side of a 34px row, which read as three loose
   * bands rather than a header over its content.
   */
  margin-top: calc(-1 * var(--space-3));
  margin-bottom: var(--space-3);
  /*
   * Held at the top while the panel under it scrolls, on every tab (2026-10-22, user: "these menu
   * need to go top and we can scroll under"). Firing already kept its tabs in view by sizing its own
   * list to the window; Acknowledged, Rules, Notifications and Maintenance scrolled the whole page and
   * took the tabs with them. Sticky against `.page-region`, the one scroller, so it works whatever
   * height a panel is. Opaque in the region's own ground so rows pass under it rather than through it.
   */
  position: sticky;
  /*
   * Under the section's pages strip (`.page-region > .section-nav-held`), with a step of air between
   * them — flush, the two rules read as one doubled line.
   */
  top: calc(var(--section-nav-h, 0px) + var(--space-3));
  z-index: 3;
  background: var(--bg-page);
}
/*
 * While held: cover everything around the row that rows would otherwise scroll through — the air
 * above it (and the scroller's top padding when there is no strip), the region's side padding (the
 * incident list's red edge showed there), and the same step of air below its rule, so the first row
 * starts clear of the line rather than under it. Behind the tabs (negative layer) and clear of the
 * rule, which is the border outside both boxes.
 */
.alerts-tabrow.is-stuck::before {
  content: "";
  position: absolute;
  left: -50vw;
  right: -50vw;
  bottom: 0;
  height: calc(100% + var(--space-5) + 2px);
  z-index: -1;
  background: var(--bg-page);
}
.alerts-tabrow.is-stuck::after {
  content: "";
  position: absolute;
  left: -50vw;
  right: -50vw;
  top: calc(100% + 1px);
  height: var(--space-3);
  background: var(--bg-page);
}
/*
 * Clear of the rule, so the select's own border is not read as part of it.
 *
 * `css()` in this package is the identity function (trap 30), so `Select`'s geometry comes from an
 * inline style nothing here can override — the spacing is on the wrapper for that reason.
 */
.alerts-cluster {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  /*
   * On the tab labels' line, and still clear of the rule. At `--space-2` the select's middle sat
   * 5.6px above the middle of "Incidents" beside it (297 against 302.6, measured at 1918px), so the
   * filter read as floating over the row rather than belonging to it. 3px put the two middles within
   * a pixel at the full 34px height. The select is the 28px compact control now (so it no longer sets
   * the row's height), and a tab's label sits 19px above the rule (2px border, 8px padding, half its
   * 18px line), so 5px puts the select's middle on the same line.
   */
  padding-bottom: calc(var(--space-1) + 1px);
}
.alerts-cluster-label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  white-space: nowrap;
}
/* A refused control still has to be readable: it is carrying its own reason in a title. */
.alerts-cluster select:disabled { opacity: 0.55; cursor: not-allowed; }

.nav-group-header {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  height: var(--size-control-sm);
  padding: 0 var(--space-2) 0 var(--space-1);
  margin-bottom: var(--space-1);
  border: 0;
  border-radius: var(--radius-md);
  background: none;
  /* NOT --color-text-subtle, which resolves to #55585F.
   *
   * Against --bg-page (#0A0B0D) that measures 2.76:1 — below WCAG's 4.5:1 for body text and below
   * even the 3:1 allowed for large text, on an 11px uppercase label with letter-spacing. It was
   * legible enough while three group headings sat in a short list and stopped being legible at
   * seven, which is how it was found: a person could not tell which section they were looking at.
   *
   * --text-secondary measures 6.08:1. `nav-contrast.test.ts` resolves this declaration through the
   * token chain and fails if it drops below 4.5:1 again. */
  color: var(--text-secondary);
  text-align: left;
  transition: background var(--motion-fast) var(--ease-out), color var(--motion-fast) var(--ease-out);
}
.nav-group-header:hover {
  background: var(--color-hover);
  /* Brighter than the resting state, not dimmer. The previous pair moved from --color-text-subtle to
     --color-text-muted, which are near-identical, so hovering a group header produced no visible
     response at all and the row read as inert. */
  color: var(--text-primary);
}

/* The one sanctioned uppercase treatment: 11px eyebrows and column headers. */
.nav-group-label {
  flex: 1;
  min-width: 0;
  font-size: var(--text-xs);
  /* 600, not 500. Weight is what makes a heading scan as a heading against the items beneath it —
     at 500 the group labels carried the same visual weight as their own children and the hierarchy
     had to be inferred from the uppercase alone. */
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.nav-group-chevron {
  flex: none;
  /* Moves the chevron to the row's right edge, matching the reference (OpenShift Console puts its
     expand/collapse indicator at the far right, not before the label). `order` alone does this —
     every sibling here (icon, label, badge) stays at the default order (0), so setting this one
     higher renders it last regardless of where it sits in the markup. No JSX change needed. */
  order: 2;
  /* Was inheriting `--color-text-subtle` from `.nav-group-header` — too dim to read at a glance,
     the same gap the reference doesn't have (its chevron reads clearly against the row). Explicit
     `--text-primary` here, independent of the header's resting text colour. */
  color: var(--text-primary);
  transition: transform var(--motion-base) var(--ease-out);
}
.nav-group-header[aria-expanded="true"] .nav-group-chevron {
  transform: rotate(90deg);
}

.nav-group-bad,
.nav-group-total {
  flex: none;
  font-size: var(--text-xs);
}
.nav-group-bad {
  color: var(--severity-critical-fg);
  font-weight: 500;
}
.nav-group-total {
  color: var(--color-text-subtle);
}

/* Gauges sit in one row and wrap to two on a narrow viewport rather than shrinking below legibility:
   the value inside the arc is the point of the component, and it is text. */
.dash-gauges {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}

/* The panel's own heading, inside a card that already has one. Smaller than the card title so the
   two do not compete, and present because "CPU" and "Memory" are what the reader is scanning for —
   the raw metric name that used to sit here is an implementation detail. */
.metric-panel-title {
  margin: 0 0 var(--space-2);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--color-text-muted);
}

/* ---- the container workbench -----------------------------------------------------------------
 *
 * A picker and one open container, not a grid of equal cards. The grid gave six containers six tall
 * cards of duplicated labels and no way to look at any one of them properly; the questions people
 * actually bring to a pod page — why did it restart, what is it using, what did it print — are all
 * questions about one container at a time.
 *
 * The list keeps its own width and the panel takes the rest, so opening a container never reflows
 * the list under the pointer that is choosing from it.
 */
/*
 * Every breakpoint below asks about the workbench's own width, not the window's.
 *
 * This is the one place in the product where those are not interchangeable. The workbench renders
 * inside the console, whose resource tree is a fixed width, so the panel's share of the page and the
 * viewport differ by an amount that is not constant across the surfaces this component appears on. A
 * viewport-keyed breakpoint at 899px therefore left the list and the panel side by side with about
 * 250px each on a 900px window — and inside that, a mount path rendered one character per line.
 *
 * `@container` is the tool for exactly this and is available everywhere this product runs. It is the
 * first use in the codebase; put a query here rather than a media query when what you need to know
 * is how much room a component actually got.
 */
.cw-scope {
  container-type: inline-size;
}

/* Stacked is the base case, so a browser that somehow does not apply the query gets the layout that
   works at any width rather than the one that only works when there is room. */
.cw {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-5);
  align-items: start;
}

@container (min-width: 760px) {
  .cw {
    grid-template-columns: minmax(220px, 260px) minmax(0, 1fr);
  }
}

.cw-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

.cw-item {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-3);
  text-align: left;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text);
  font: inherit;
  cursor: pointer;
  min-width: 0;
}

.cw-item:hover {
  background: var(--color-hover);
}

/* The accent rail marks the open one. A background change alone is too quiet next to a hover state
   that also changes the background. */
.cw-item-on,
.cw-item-on:hover {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  box-shadow: inset 3px 0 0 var(--accent);
}

.cw-item-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  min-width: 0;
}

.cw-item-name {
  font-size: var(--text-sm);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.cw-item-sub {
  font-size: var(--text-xs);
  color: var(--color-text-subtle);
}

/*
 * A container in its own right, so the sections inside it can ask how much room *they* got.
 *
 * The panel's width is the scope's minus the list, and the list is only there above 760px — so one
 * query against the scope cannot answer both regimes, and the stacked one would keep the narrow
 * layout at exactly the width where the panel finally has the whole row to itself.
 *
 * Note for whoever adds to this panel: `container-type` establishes a containing block for
 * `position: fixed`, so a `Modal` opened from inside here would be trapped in the panel instead of
 * covering the page. Nothing in the workbench opens one today; if that changes, render it from the
 * page rather than from within this element.
 */
.cw-panel {
  container-type: inline-size;
  min-width: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  padding: var(--space-5);
}

.cw-panel-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-bottom: var(--space-4);
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--color-border);
}

.cw-panel-title {
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  font-size: var(--text-md);
  font-weight: 600;
}

/* ---- a cluster as a record ---------------------------------------------------------------------
 *
 * The cluster list was a nine-column table, and the honest reason it read badly is that a cluster is
 * not tabular. Half its facts are qualified — six nodes the cluster reported or six machines built
 * and silent, "ready" according to a heartbeat from an hour ago or from last Tuesday — so every cell
 * carried a caption, every row became three lines of 11px text at four different alignments, and
 * "84 cores" wrapped across two of them. A table earns its density from cells holding single values.
 *
 * The card keeps the property that made the table worth having: the figures sit in the same grid on
 * every card, so the eye still runs down a column. What it adds is somewhere for the captions to go,
 * and a rail carrying the worst state on the card — so twenty clusters are triaged before a word is
 * read, which is the one thing a wall of equally grey rows cannot do.
 */
.cc-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.cc {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  background: var(--color-surface);
}

/* Colour is never the only carrier: the same state is written out under the figure it belongs to
   ("1 degraded", "4 of 6"). The rail is the index, not the message. */
.cc-rail-warning {
  box-shadow: inset 3px 0 0 var(--severity-warning-fill);
}

.cc-rail-critical {
  box-shadow: inset 3px 0 0 var(--severity-critical-fill);
}

.cc-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
}

.cc-id {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  min-width: 0;
}

.cc-name {
  font-size: var(--text-lg);
  font-weight: 600;
}

.cc-actions {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  font-size: var(--text-sm);
  white-space: nowrap;
}

/*
 * Remove, in the card's action row.
 *
 * A link-weight control rather than a red button. It sits beside "Console" and "Add nodes" on a
 * board people scan quickly, and a filled danger button in that row would be the loudest thing on
 * a page whose job is to show which cluster is unhealthy — the colour on this page means severity,
 * and a control is not a severity. The weight it needs is in the dialog it opens, which asks for
 * the cluster's name typed out; this only has to be findable.
 */
.cc-remove {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--color-text-subtle);
  cursor: pointer;
}
.cc-remove:hover {
  color: var(--critical);
  text-decoration: none;
}
.cc-remove:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
  border-radius: 3px;
}

/* Why a cluster is being removed, on the card while it goes. Quiet: it is context for a colleague
   reading the list, not a warning — the badge beside the name already carries the state. */
.cc-remove-why {
  margin: var(--space-2) 0 0;
  font-size: var(--text-xs);
  line-height: var(--leading-body);
  color: var(--color-text-muted);
  overflow-wrap: anywhere;
}

/* A removal that is not finishing, said on the card. Warning rather than critical: the cluster is
   intact and the reader has a control — it is stuck, not broken. */
.cc-remove-error {
  margin: var(--space-2) 0 0;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  background: var(--severity-warning-bg, var(--warn-soft));
  color: var(--severity-warning-fg, var(--warn-text));
  font-size: var(--text-xs);
  line-height: var(--leading-body);
  overflow-wrap: anywhere;
}

/* The dialog's own prose. The lead is the sentence that has to be read; the notes qualify it. */
.cc-remove-lead {
  margin: 0 0 var(--space-3);
  font-size: var(--text-base);
  line-height: var(--leading-body);
  color: var(--color-text);
}
.cc-remove-note {
  margin: 0 0 var(--space-3);
  font-size: var(--text-sm);
  line-height: var(--leading-body);
  color: var(--color-text-muted);
}

.cc-sub {
  margin: -4px 0 0;
  font-size: var(--text-xs);
  color: var(--color-text-subtle);
  overflow-wrap: anywhere;
}

/* `auto-fit` rather than a fixed count, so the same definition holds for a card with six figures and
   one with seven — the on-prem AI stat exists on a minority of estates and must not leave a hole in
   the others. */
.cc-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(128px, 1fr));
  gap: var(--space-4);
  margin: 0;
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border);
}

.cc-stat {
  min-width: 0;
}

.cc-stat-label {
  font-size: var(--text-xs);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-subtle);
}

.cc-stat-value {
  margin: 2px 0 0;
  font-size: var(--text-lg);
  font-weight: 600;
  line-height: 1.25;
  color: var(--color-text);
}

.cc-stat-sub {
  margin: 1px 0 0;
  font-size: var(--text-xs);
  line-height: 1.4;
  color: var(--color-text-subtle);
  overflow-wrap: anywhere;
}

.cc-stat-warning {
  color: var(--severity-warning-fg);
}

/* The failing total, when there is one. Its own class rather than reusing the warning colour: this
   cell is the card's answer to "is anything wrong here", and a red figure is the whole point of
   putting it beside five green ones. */
.cc-stat-critical {
  color: var(--severity-critical-fg);
}

/* An absent figure at full weight reads as a value. A dash that means "the cluster has not told us"
   should not look like one that means zero. */
.cc-stat-muted,
.cc-stat-value.cc-stat-muted {
  color: var(--color-text-subtle);
  font-weight: 400;
}

/* ---- the open container's details ------------------------------------------------------------
 *
 * Laid out by how wide each fact actually is, which is the thing the definition list it replaced
 * could not do: one `auto-fit` track gave a 71-character image digest and the word "yes" the same
 * 240px column, so the digest wrapped into four lines, a service-account mount path broke mid-word
 * and ran past the edge, and every short value sat over a third of a panel of white space — a grid
 * row being as tall as its tallest cell.
 *
 * Three bands instead: the reference across the full width, the short figures in a strip that fills
 * whatever their number, and the three list-shaped facts in a row sized to what each one holds.
 */
.cd {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.cd-label {
  margin: 0;
  font-size: var(--text-xs);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-subtle);
}

.cd-count {
  margin-left: 6px;
  color: var(--color-text-muted);
  letter-spacing: 0;
}

.cd-image {
  min-width: 0;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-sunken);
}

/* The copy button sits in the label row, not in the text flow. Inline it competed with the digest
   for the same line box and landed in the middle of the reference it was offering to copy. */
.cd-image-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-2);
}

.cd-image-repo {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--color-text);
  overflow-wrap: anywhere;
}

.cd-image-ref {
  margin: 2px 0 0;
  font-size: var(--text-xs);
  color: var(--color-text-subtle);
  overflow-wrap: anywhere;
}

.cd-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
  gap: 1px;
  margin: 0;
  /* One hairline between cells rather than a border each: the 1px grid gap shows the background
     through, so the strip reads as one object divided rather than four boxes placed near each other. */
  background: var(--color-border);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
}

.cd-fact {
  min-width: 0;
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface);
}

.cd-fact-value {
  margin: 2px 0 0;
  font-size: var(--text-xl);
  font-weight: 600;
  line-height: 1.2;
  color: var(--color-text);
}

/* A value that is a phrase rather than a figure steps down a size. "37 minutes ago" set at 20px
   wraps inside a 110px track, and a strip whose cells are different heights stops reading as a row. */
.cd-fact-value.cd-fact-text {
  font-size: var(--text-md);
  font-weight: 500;
}

.cd-fact-warning {
  color: var(--severity-warning-fg);
}

.cd-fact-critical {
  color: var(--severity-critical-fg);
}

/*
 * Resources and ports share a row; mounts take the whole of the next one.
 *
 * Not three tracks. Sized `auto`, the first two grew to fit their own longest sentence — "None
 * declared — this container can be scheduled anywhere and is capped at nothing" is a legitimate
 * value — and squeezed the `1fr` mounts track down to about ten pixels, which `minmax(0, …)` permits
 * and which rendered a mount path vertically, one character per line. Two equal halves cannot be
 * blown out by their contents, and the longest strings on the panel get the full width they need.
 */
.cd-cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-5) var(--space-6);
  align-items: start;
}

/* Against `.cw-panel`, the nearest container — the panel's own width, not the workbench's. */
@container (min-width: 520px) {
  .cd-cols {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
}

.cd-wide {
  grid-column: 1 / -1;
}

.cd-section {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.cd-none {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--color-text-subtle);
}

/* Requests and limits side by side per resource. As two sentences — "cpu 100m · memory 128Mi" over
   "cpu 2 · memory 1Gi" — the comparison that matters is a twenty-fold burst ratio nobody can see. */
.cd-res {
  border-collapse: collapse;
  font-size: var(--text-sm);
}

.cd-res th,
.cd-res td {
  padding: 3px var(--space-3) 3px 0;
  text-align: left;
  font-weight: 400;
  white-space: nowrap;
}

.cd-res thead th {
  font-size: var(--text-xs);
  color: var(--color-text-subtle);
  padding-bottom: var(--space-1);
}

.cd-res tbody th {
  color: var(--color-text-muted);
}

.cd-unset {
  color: var(--color-text-subtle);
  font-style: italic;
}

.cd-ports {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.cd-port {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  padding: 2px var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-sunken);
  font-size: var(--text-sm);
  white-space: nowrap;
}

.cd-port-name {
  font-size: var(--text-xs);
  color: var(--color-text-subtle);
}

.cd-mounts {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.cd-mounts li {
  min-width: 0;
  font-size: var(--text-sm);
}

.cd-mount-path {
  overflow-wrap: anywhere;
}

.cd-ro {
  margin-left: 6px;
  padding: 0 5px;
  border-radius: var(--radius-sm);
  background: var(--color-hover);
  color: var(--color-text-subtle);
  font-size: var(--text-xs);
  vertical-align: 1px;
}

.cd-mount-vol {
  display: block;
  font-size: var(--text-xs);
  color: var(--color-text-subtle);
  overflow-wrap: anywhere;
}

.cw-panel-sub {
  margin: 2px 0 0;
  font-size: var(--text-xs);
  color: var(--color-text-subtle);
}

.cw-logbar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-bottom: var(--space-2);
}

.cw-logmeta {
  margin: 0 0 var(--space-3);
  font-size: var(--text-xs);
  color: var(--color-text-subtle);
}

/* The stream's state, as a state and not a claim. A confident "Live" over a dropped connection is
   the one thing a tail must never show. */
.cw-live {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

.cw-live-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--color-text-subtle);
}

.cw-live-live .cw-live-dot {
  background: var(--severity-ok-fg);
  animation: cw-pulse 2s ease-in-out infinite;
}

.cw-live-connecting .cw-live-dot {
  background: var(--severity-warning-fg);
}

.cw-live-error .cw-live-dot {
  background: var(--severity-critical-fg);
}

@keyframes cw-pulse {
  50% {
    opacity: 0.35;
  }
}

/* Motion here is decorative — it says "attached", which the word beside it already says. */
@media (prefers-reduced-motion: reduce) {
  .cw-live-live .cw-live-dot {
    animation: none;
  }
}

.cw-download {
  display: inline-flex;
  align-items: center;
  height: var(--size-control);
  padding: 0 var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text);
  font-size: var(--text-sm);
  text-decoration: none;
  white-space: nowrap;
}

.cw-download:hover {
  background: var(--color-hover);
  border-color: var(--border-strong);
}

/* ---- where each feature's data goes ----------------------------------------------------------
 *
 * Deliberately NOT `.ledger`, for the same reason `.feature-row` is not — and this is the second
 * time that mistake has been made, which is why the rule is written down rather than implied.
 *
 * `.ledger` is a four-column monospace grid for scannable machine data: a rule, a name, a role and
 * an address, all short tokens. This row carries a feature title and a *sentence* ("processed
 * outside your network"). Rendering a sentence in monospace at a fixed `minmax(4.5rem, auto)`
 * pushed the grid wider than its card, and `.ledger` sets `overflow: hidden` — so it did not scroll
 * or wrap, it was cut off. The account most likely to see it is one with per-feature models, which
 * is exactly the account the table exists for.
 *
 * The empty third column was the other half. `f.available ? "" : "unavailable"` put an empty string
 * in a 4.5rem track on every healthy row, so the common case showed a gap with nothing in it.
 */

.egress-list {
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-card);
  background: var(--bg-surface);
  overflow: hidden;
}

.egress-row {
  display: grid;
  /* Three tracks, and the last one wraps. The boundary statement is prose and is allowed to take
     two lines on a narrow card rather than widening the grid until it is clipped. */
  grid-template-columns: 3px minmax(8rem, auto) 1fr;
  gap: var(--space-3);
  align-items: center;
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--color-border);
  font-size: var(--text-sm);
}
.egress-row:last-child {
  border-bottom: 0;
}

/* Full-height, so the coloured edge reads as belonging to the row rather than sitting in it. */
.egress-rule {
  width: 3px;
  align-self: stretch;
  border-radius: 1px;
  background: var(--color-border-strong);
}
/*
 * The one fact this table exists to carry, and it is keyed on its own meaning.
 *
 * It used to reuse `data-phase="machines"` to get the accent colour, which coloured "stays on your
 * network" with the attribute that means "phase one of a build". Two unrelated facts sharing an
 * attribute is how one of them silently changes when the other is restyled.
 */
.egress-row[data-egress="internal"] .egress-rule {
  background: var(--color-primary);
}
.egress-row[data-egress="external"] .egress-rule {
  background: var(--warn);
}

.egress-feature {
  font-weight: 500;
  color: var(--color-text);
}
.egress-where {
  color: var(--color-text-muted);
}
.egress-where[data-unavailable="true"] {
  color: var(--color-text-subtle);
}

/* ================================ the commitment band
 *
 * Usage, what the scheduler has promised, and the ceiling it granted — on one axis, drawn in HTML
 * rather than SVG.
 *
 * ## Why not SVG
 *
 * Every mark here is a percentage of the track's width, and a percentage-positioned element scales
 * exactly. An SVG doing the same job needs either a measured viewBox or `preserveAspectRatio="none"`,
 * and the second squashes strokes anisotropically — the note at the top of `chart.tsx` is about
 * exactly that failure. There is no curve here to justify paying for it.
 *
 * ## The wall is at 76% and stays there
 *
 * `band.ts` owns that number. The remaining quarter is the over-region, where a limits total past
 * 100% of allocatable is drawn. Rescaling each row to its own maximum would make a 300%-committed
 * node and a 30%-used one draw the same picture, which is the one thing a column of these must not
 * do — so the geometry is fixed and the tail compresses instead.
 */

.band-wrap {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  width: 100%;
}

.band {
  position: relative;
  display: block;
  width: 100%;
  min-width: 90px;
  overflow: hidden;
  border-radius: var(--radius-sm);
  /* Sunken and hairlined, no shadow: this is a nested surface inside a card. */
  background: var(--bg-sunken);
  border: 1px solid var(--border-hairline);
}

.band-row {
  height: 18px;
}
.band-panel {
  height: 30px;
}

/* Past the wall is not capacity the node owns, so it is not drawn as track. The dashed left edge is
   the boundary; the page ground behind it is what says "nothing here belongs to you". */
.band-over {
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  background: var(--bg-page);
  border-left: 1px dashed var(--border-strong);
}

/*
 * The finding. Capacity the scheduler is holding and nothing is using — the span between the fill
 * and the request rule. Same hue as the usage fill at low alpha, because it is the same resource in
 * a different state, not a different measurement.
 */
.band-reserved {
  position: absolute;
  top: 0;
  bottom: 0;
}

.band-used {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  transition: width var(--motion-base) var(--ease-out);
}
.band-used-ok {
  background: var(--severity-ok-fill);
}
.band-used-info {
  background: var(--severity-info-fill);
}
.band-used-warning {
  background: var(--severity-warning-fill);
}
.band-used-critical {
  background: var(--severity-critical-fill);
}
.band-used-unknown {
  background: var(--bg-hover);
}

/* Allocatable. A hard rule rather than a tint, because it is a boundary and not a quantity. */
.band-wall {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--border-strong);
}

/*
 * What the scheduler has committed.
 *
 * Deliberately achromatic: this is a threshold, not a severity, and giving it a status colour would
 * make a healthy cluster's request line read as a warning. It sits above the fill and needs to stay
 * visible on top of it, hence the outline in the track's own ground.
 */
.band-request {
  position: absolute;
  top: -1px;
  bottom: -1px;
  width: 2px;
  margin-left: -1px;
  background: var(--text-primary);
  box-shadow: 0 0 0 1px var(--bg-surface);
}

/*
 * The granted ceiling. Capped top and bottom so it reads as a marker rather than another fill edge,
 * and coloured only once it is past the wall — overcommit under 100% is simply headroom.
 */
.band-limit {
  position: absolute;
  top: 2px;
  bottom: 2px;
  width: 3px;
  margin-left: -1px;
  border-radius: 2px;
  background: var(--text-secondary);
}
.band-limit-over {
  background: var(--severity-warning-fill);
  box-shadow: 0 0 0 1px var(--bg-surface);
}

.band-button {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  font: inherit;
}
.band-button:hover .band {
  border-color: var(--border-strong);
}
.band-button:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/*
 * The three figures, always printed.
 *
 * The band is a faster way to read numbers that are also on screen, never the only place they exist
 * — the same rule every `<Badge>` in this package follows about colour. Mono and tabular so the
 * column aligns and a row can be compared against the one above it without reading the digits.
 */
.band-readout {
  display: grid;
  grid-template-columns: repeat(3, 3.1rem);
  gap: 2px;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-xs);
  text-align: right;
}
.band-figure {
  color: var(--text-primary);
}
.band-figure-req {
  color: var(--text-secondary);
}
.band-figure-over {
  color: var(--severity-warning-fg);
  font-weight: 600;
}

.band-scale {
  position: relative;
  display: block;
  grid-column: 1 / -1;
  height: 14px;
  font-size: var(--text-xs);
  color: var(--text-muted);
}
.band-scale-wall {
  position: absolute;
  transform: translateX(-50%);
  white-space: nowrap;
}
.band-scale-end {
  position: absolute;
  right: 0;
}

.band-unknown {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-width: 90px;
  height: 18px;
  padding: 0 var(--space-2);
  border: 1px dashed var(--border-hairline);
  border-radius: var(--radius-sm);
  font-size: var(--text-xs);
  color: var(--text-secondary);
}

/* ================================ the node grid
 *
 * One tile per machine, tinted by the resource being asked about. The reference boards this answers
 * put a single hue per host, which finds the hot machine and hides the full-but-idle one; each tile
 * here carries its whole commitment band, so both are visible without opening anything.
 */

.heat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(232px, 1fr));
  gap: var(--space-3);
}

.heat-tile {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-3);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-hairline);
  text-align: left;
  font: inherit;
  color: var(--text-primary);
  min-width: 0;
}
.heat-tile-warning {
  border-color: color-mix(in srgb, var(--severity-warning-fg) 40%, transparent);
}
.heat-tile-critical {
  border-color: color-mix(in srgb, var(--severity-critical-fg) 55%, transparent);
}

.heat-tile-button {
  cursor: pointer;
  transition: box-shadow var(--motion-fast) var(--ease-out), transform var(--motion-fast) var(--ease-out);
}
.heat-tile-button:hover {
}
.heat-tile-button:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

.heat-tile-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}
.heat-tile-dot {
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: var(--radius-full);
}
.heat-tile-name {
  font-weight: 600;
  font-size: var(--text-sm);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.heat-tile-meta {
  margin-left: auto;
  /* Shrinkable and clipped, not `flex: none`. A long role list is worth less than the node's name,
     and unshrinkable it pushed the name out of its own tile. */
  min-width: 0;
  max-width: 45%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--text-xs);
  color: var(--text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
/* The headline reading. Mono, because it is a measurement and it sits above a band drawn to scale. */
.heat-tile-value {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-xl);
  font-weight: 600;
  line-height: var(--leading-heading);
}
.heat-tile-band {
  display: grid;
  gap: var(--space-1);
}

/* The two figures the band's own readout no longer prints here. Mono, so a column of tiles lines up
   down the grid the same way a table column would. */
.heat-tile-figures {
  display: flex;
  justify-content: space-between;
  gap: var(--space-2);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-xs);
  color: var(--text-secondary);
}
.heat-tile-over {
  color: var(--severity-warning-fg);
  font-weight: 600;
}

/* Without a readout the track is the whole width, so it needs no second column. */
.band-wrap-bare {
  grid-template-columns: 1fr;
}
.heat-tile-flag {
  justify-self: start;
  padding: 1px var(--space-2);
  border-radius: var(--radius-full);
  background: var(--severity-critical-bg);
  color: var(--severity-critical-fg);
  font-size: var(--text-xs);
  font-weight: 600;
}

/* ================================ segmented control
 *
 * Two or three views of one panel. Not `Tabs` — see the note on the component about why announcing a
 * tablist for a chart's metric switch is a lie about the page's structure.
 */

.seg {
  display: inline-flex;
  padding: 2px;
  border-radius: var(--radius-md);
  background: var(--bg-sunken);
  border: 1px solid var(--border-hairline);
}

.seg-item {
  padding: 3px var(--space-3);
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--text-secondary);
  font: inherit;
  font-size: var(--text-xs);
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
}
.seg-item:hover {
  color: var(--hover-accent-text);
  transition: color var(--motion-fast) var(--ease-out);
}
.seg-item-on {
  background: var(--bg-surface);
  color: var(--text-primary);
}
.seg-item:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: -1px;
}

/* ================================ the capacity board
 *
 * Twelve columns, fixed composition. The console page offers a board the reader arranges; this one
 * is composed, because the order of these panels *is* the argument the page makes — what is wrong,
 * then where, then what it is made of. A reader who rearranged it into "namespaces first" would have
 * a page that no longer answers the question it opens with.
 */

/*
 * Cards in the same row end at the same line.
 *
 * This was `align-items: start`, which is the ragged bottom in every board screenshot: on the
 * capacity page Commitment ran to 454px and Load stopped at 345px beside it, and the gap read as a
 * card that had failed to finish rather than one with less to say. Measured across four boards, every
 * multi-card row was ragged — 109px, 247px, 117px, 69px, 93px, 265px.
 *
 * `stretch` is the grid default; `start` was the deviation. The whitespace does not disappear, it
 * moves inside the card that has less content, where a border and a heading make it read as room
 * rather than as a broken edge. That is the trade, and it is the right way round: a reader can see
 * that a card is quiet, but a bottom edge that stops early looks like something did not load.
 *
 * Nothing needs to change in `.card` for this: the row's height is the tallest card's content, so a
 * stretched card is never shorter than what it holds and `overflow: hidden` has nothing to cut. The
 * extra height lands under the content, on the card's own ground.
 */
.board {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--space-4);
  align-items: stretch;
}

.board > * {
  grid-column: span 12;
  min-width: 0;
}

/*
 * The Type B board. v4: explorer pages take 8-12px gaps against the overview's 16-24 — "tighter on
 * purpose, that's what makes density legible instead of cramped." A modifier, not a second grid, so
 * every span/collapse rule above applies unchanged.
 */
.board--dense {
  gap: var(--space-3);
}
.board > [data-span="3"] {
  grid-column: span 3;
}
.board > [data-span="4"] {
  grid-column: span 4;
}
.board > [data-span="5"] {
  grid-column: span 5;
}
.board > [data-span="6"] {
  grid-column: span 6;
}
.board > [data-span="7"] {
  grid-column: span 7;
}
.board > [data-span="8"] {
  grid-column: span 8;
}
/* 9–11 exist for the custom board's drag-to-resize; the fixed boards never declare them. */
.board > [data-span="9"] {
  grid-column: span 9;
}
.board > [data-span="10"] {
  grid-column: span 10;
}
.board > [data-span="11"] {
  grid-column: span 11;
}

/*
 * How the board collapses, and the rule that was wrong the first time.
 *
 * A blanket `.board > [data-span] { span 6 }` under 1100px also matched the panels declared
 * `span="12"` — the node grid, the headline rail — and shrank the full-width ones to half. The rail
 * then stacked five tiles into a column and the node grid rendered beside a chart, at exactly the
 * width where there is least room for either.
 *
 * So the two halves are treated separately: a panel that already wanted most of the row takes the
 * whole row, and only the genuinely narrow ones pair up.
 */
@media (max-width: 1100px) {
  .board > [data-span="3"],
  .board > [data-span="4"],
  .board > [data-span="5"],
  .board > [data-span="6"] {
    grid-column: span 6;
  }
  .board > [data-span="7"],
  .board > [data-span="8"],
  .board > [data-span="9"],
  .board > [data-span="10"],
  .board > [data-span="11"] {
    grid-column: span 12;
  }
}
@media (max-width: 760px) {
  .board > [data-span] {
    grid-column: span 12;
  }
}

/*
 * The resource Metrics tab's side rail.
 *
 * A class rather than inline styles because the whole point of it is two rules that only exist at a
 * breakpoint, and an inline style would win over both.
 *
 * Wide: a column beside the charts, its cards sharing the row's height. `flex: 1 1 auto` grows them
 * from their natural sizes rather than to equal ones — equal would make the two-line Containers
 * panel as tall as the rings beside it, a worse shape than the gap it was fixing.
 *
 * Narrow: the board collapses `span="4"` to half a row, which left the rail in the left half with
 * nothing in the right. So under 1100px it takes the whole row and lays its cards out side by side
 * instead — the same panels, turned ninety degrees, rather than a half-width column beside dead
 * ground. Under 760px there is no room for that either and they stack.
 */
.metrics-rail {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.metrics-rail > * {
  flex: 1 1 auto;
  display: grid;
  min-width: 0;
}

@media (max-width: 1100px) {
  .board > .metrics-rail {
    grid-column: span 12;
  }
  .metrics-rail {
    flex-direction: row;
    flex-wrap: wrap;
  }
  .metrics-rail > * {
    flex: 1 1 260px;
  }
}

@media (max-width: 760px) {
  .metrics-rail {
    flex-direction: column;
  }
  .metrics-rail > * {
    flex: 1 1 auto;
  }
}

/*
 * The scope strip: what window, how fresh, and whether it is still polling.
 *
 * Sticky, because it is the answer to "am I looking at now?" and that question is asked while
 * scrolled to the bottom of a node table. Every reference dashboard puts this at the top and keeps
 * it there.
 */
.scope {
  position: sticky;
  /* Under the section's pages strip, which is held above it (`.page-region > .section-nav-held`). */
  top: var(--section-nav-h, 0px);
  /* Under the topbar, so the account menu and the bell's list drop over it rather than behind it. */
  z-index: var(--z-raised);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
  border-radius: var(--radius-card);
  background: var(--bg-surface);
  border: 1px solid var(--border-hairline);
}

.scope-title {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin-right: auto;
  min-width: 0;
}
.scope-name {
  font-size: var(--text-lg);
  font-weight: 600;
}
.scope-sub {
  font-size: var(--text-xs);
  color: var(--text-secondary);
}

.scope-label {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  font-weight: 600;
}

.scope-group {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.scope-stamp {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-xs);
  color: var(--text-secondary);
}

/* A reading old enough that the board is no longer describing the present.
   Toned rather than hidden: the age is the most useful thing on the strip once it is large, and the
   colour is what stops it being read as the confirmation it looks like at a glance. */
.scope-stamp-stale {
  color: var(--warn-text);
  font-weight: 600;
}

/* ---- panel furniture ------------------------------------------------------------------------- */

/* The eyebrow over a figure. Uppercase and tracked, so it reads as a label and never competes with
   the number under it. */
.panel-eyebrow {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 600;
  color: var(--text-muted);
}

/*
 * A headline figure. Mono for the same reason every measurement on this page is mono: a column of
 * proportional digits does not align, and these are meant to be compared down the page rather than
 * read as prose.
 */
.panel-figure {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-2xl);
  font-weight: 600;
  line-height: var(--leading-hero);
}

.panel-note {
  font-size: var(--text-xs);
  color: var(--text-secondary);
  line-height: var(--leading-body);
}

/* The row a resource panel is built from: eyebrow, band, note. */
.res-row {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--color-border);
}
.res-row:last-child {
  border-bottom: 0;
  padding-bottom: 0;
}
.res-row:first-child {
  padding-top: 0;
}
.res-row-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
}
.res-row-value {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

/*
 * Triage rows: a severity rail, the finding, and the way to act on it.
 *
 * The rail is the full height of the row rather than a badge, so a list of eight is scannable by
 * colour down the left edge — and the severity word is still printed, because colour is never the
 * only signal.
 */
/*
 * The findings list, which scrolls inside its card instead of setting the card's height.
 *
 * A capacity board that grows a card every time something breaks is a board whose layout is worst
 * when it is needed most: seventeen findings pushed the namespace ranking beside it off the fold,
 * so the panel reporting the problem hid the panel you check next. A fixed frame with the list
 * scrolling inside it costs nothing and hides nothing.
 *
 * `min-height: 0` is the half that is easy to leave out and impossible to see missing: the card
 * body is a flex column, and a flex item's default `min-height: auto` refuses to shrink below its
 * content — so the list would set the card's height exactly as before and never scroll.
 *
 * No `overscroll-behavior`. This is a bounded list inside a longer page, not an overlay, so the
 * wheel chains to the board at the last row — see `scroll.test.ts`, where containing both axes here
 * is the bug that froze the dashboard.
 */
.triage {
  display: grid;
  gap: var(--space-2);
  align-content: start;
  /*
   * Fills the card when a taller neighbour has already set the row's height, and stops there when
   * nothing else has. `align-items: stretch` on the board means the Capacity card and the namespace
   * ranking beside it are always the same height — so without the cap, seventeen findings would set
   * that height for both and push the rest of the page down.
   */
  flex: 1 1 auto;
  max-height: 26rem;
  min-height: 0;
  overflow-y: auto;
}

/*
 * The card body hands the scroll to the list, exactly as it does for a panel's table wrapper. Two
 * nested scrollbars is one region too many: the outer one moves a few pixels, the inner one moves
 * the rows, and the handoff happens at a boundary nobody can see.
 */
.card > .card-body:has(> .triage-frame:only-child),
.card > .card-body-roomy:has(> .triage-frame:only-child),
/*
 * A loader in a card with no body padding keeps an inset.
 *
 * `padded={false}` exists so a table can run edge to edge; while that table is still loading, the
 * `LoadingState` bars in its place ran edge to edge too, touching both of the card's borders. The
 * inset is the one an ordinary card body gives, so the bars sit where the rows will.
 */
.card-body-flush > [role="status"][aria-busy="true"] {
  padding: var(--space-4);
}

.card > .card-body-flush:has(> .triage-frame:only-child) {
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
/*
 * Tightened because the list is as long as the cluster is unhealthy.
 *
 * Seventeen findings at the old `space-3` padding and a three-line paragraph each made a card
 * taller than the viewport, listing problems worst-first in an order nobody could see past the
 * third of. The rail, the severity word and the title are what a reader scans; the padding and the
 * paragraph are what pushed the fourth row off the screen.
 */
.triage-row {
  display: grid;
  grid-template-columns: 3px 1fr auto;
  gap: var(--space-2) var(--space-3);
  align-items: center;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  background: var(--bg-sunken);
}
.triage-rail {
  align-self: stretch;
  border-radius: 2px;
}

/*
 * A button that has to look like the heading it replaced.
 *
 * It is a button because it toggles the detail and must be reachable by keyboard and announce its
 * state; every one of these resets exists because a bare `<button>` brings a border, a background,
 * centred text and a font that is not the page's.
 */
.triage-title {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  text-align: left;
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  color: inherit;
  cursor: pointer;
}
.triage-title:hover { color: var(--hover-accent-text); }

.triage-detail {
  font-size: var(--text-xs);
  color: var(--text-secondary);
  line-height: var(--leading-body);
  margin-top: 2px;
}

/*
 * One line until the title is pressed.
 *
 * `-webkit-line-clamp` is the only way to clamp to a line count, and it is unprefixed nowhere that
 * matters — every engine implements it under this name. `min-width: 0` on the parent grid cell is
 * what actually lets it shrink; without it the text sets the column width and the clamp never
 * engages, which is the failure mode that makes this look like it simply does not work.
 */
.triage-detail.is-clamped {
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/*
 * A prediction's sentence in a table cell, clamped to its first two lines.
 *
 * A button rather than a div, for the reason the triage title is one: it toggles, so it has to be
 * reachable by keyboard and announce that there is more. The resets below are what make a button
 * look like the prose it replaced — a bare one arrives centred, bordered and in the browser's font.
 *
 * `white-space: normal` is not redundant. A button's default is `pre-line` in some engines and the
 * UA stylesheet's `text-align: center` is not the only thing it brings; without this a long
 * prediction renders as one unwrapped line and the clamp has nothing to clamp.
 */
.pred-message {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  text-align: left;
  white-space: normal;
  font: inherit;
  font-size: var(--text-sm);
  line-height: var(--leading-body);
  color: inherit;
  cursor: pointer;
}
.pred-message:hover { color: var(--hover-accent-text); }

/*
 * The findings list scrolls without drawing a bar.
 *
 * Two halves, and they must live in different places. This one is the pseudo-element path that
 * WebKit and Blink take; the `scrollbar-width: none` that Firefox needs is inside the
 * `@supports not selector(::-webkit-scrollbar)` block further down, because a standard scrollbar
 * property declared anywhere Blink can see it switches that element off the pseudo-element path
 * entirely and onto a stepper-arrow bar — `scroll.test.ts` documents that incident.
 */
.triage::-webkit-scrollbar {
  width: 0;
  height: 0;
}

/*
 * The fade that replaces the bar.
 *
 * Hiding a scrollbar removes the only thing on screen saying there is more, and on a panel that
 * lists what is wrong with a cluster that is not a cosmetic loss: fourteen findings would look like
 * six. The frame carries a gradient over its last few pixels so the list visibly runs under its own
 * edge.
 *
 * `data-more` is set by the component only when the content actually overflows, so a list that fits
 * has no fade — a permanent one would be the same lie in the other direction.
 */
.triage-frame {
  position: relative;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.triage-frame[data-more="yes"]::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: var(--space-5);
  pointer-events: none;
  background: linear-gradient(to bottom, transparent, var(--bg-surface));
}

/*
 * A log template in a table cell, held to two lines.
 *
 * These are whatever an application printed — some are a hundred characters of JSON, and one of
 * those sets the row's height and pushes every other message off the card. `overflow-wrap: anywhere`
 * stays: without it a single long token refuses to break and widens the column instead of wrapping
 * inside it, so the clamp would have nothing to clamp.
 */
.log-template {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: anywhere;
  min-width: 0;
  font-size: var(--text-xs);
}

.pred-message.is-clamped {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.triage-sev {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 700;
}

/*
 * One retention window: a label, a number and its unit, with no trailing margin.
 *
 * `Field` would be the obvious component and is the wrong one here — it carries an inline
 * `margin-bottom: var(--space-4)` that nothing outside it can override, and four of those is four
 * rows of empty space under four numbers on a card that holds nothing else.
 */
.retention-tier {
  display: grid;
  gap: 2px;
  min-width: 0;
}
.retention-tier-label {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--text-secondary);
}
.retention-tier-input {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}
.retention-tier-unit {
  color: var(--text-muted);
  font-size: var(--text-sm);
}

/*
 * The retention explanations, folded away.
 *
 * A `<dl>` rather than paragraphs because it is four terms and their definitions, and the markup
 * saying so is what lets a screen reader move between them instead of reading one run-on block.
 */
.retention-help {
  margin-top: var(--space-3);
  font-size: var(--text-sm);
}
.retention-help > summary {
  cursor: pointer;
  color: var(--text-secondary);
  -webkit-user-select: none;
     -moz-user-select: none;
          user-select: none;
  padding: var(--space-1) 0;
}
.retention-help dl {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--space-1) var(--space-3);
  margin: var(--space-2) 0 0;
}
.retention-help dt { font-weight: 600; }
.retention-help dd { margin: 0; color: var(--text-secondary); }

/* ---- the ranked list ------------------------------------------------------------------------- */

/*
 * Namespaces by weight. A proportional bar behind the row rather than beside it, because the name is
 * long and the bar is the sort order made visible — putting them in separate columns halves the
 * width available to both.
 */
/*
 * Fixed measure columns, not `auto`.
 *
 * Each row is its own grid — it has to be, because the proportional fill is positioned inside it —
 * so `auto` tracks size to that row's own content and the values land at a different x on every
 * line. A ranked list whose numbers do not line up cannot be compared down the column, which is the
 * only reason it is ranked.
 */
.rank-row {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) 6rem minmax(0, 13.5rem);
  gap: var(--space-3);
  align-items: center;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
/*
 * A separator, not a stripe.
 *
 * Zebra striping was tried and removed: on a skewed cluster most bars are slivers, and a full-width
 * sunken row is visually indistinguishable from a full-width bar. Half the list appeared to be at
 * 100% of the largest namespace. A hairline separates rows without ever being mistaken for data.
 */
.rank-row + .rank-row {
  border-top: 1px solid var(--color-border);
  border-radius: 0;
}
.rank-fill {
  position: absolute;
  inset: 0 auto 0 0;
  z-index: 0;
  /* Stronger than `--color-chart-fill`, which is tuned to sit under a line and reads as a highlight
     rather than a bar when it is the only mark in the row. */
  background: color-mix(in srgb, var(--severity-info-fill) 22%, transparent);
}
/*
 * `:not(.rank-fill)`, and that exclusion is load-bearing.
 *
 * Lifting the row's content above the fill needs `position` on the children, and a bare
 * `.rank-row > *` matches the fill too — same specificity, declared later, so it won the cascade and
 * turned the absolutely-positioned bar into a grid item. The bar then took the first column, pushed
 * the name and the value one track right, and the row rendered with its content shoved to the edge
 * and no bar at all. It looked like a layout choice rather than a bug.
 */
.rank-row > *:not(.rank-fill) {
  position: relative;
  z-index: 1;
}
.rank-name {
  font-size: var(--text-sm);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rank-value {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-xs);
  color: var(--text-secondary);
  text-align: right;
}

/* ---- headline tiles -------------------------------------------------------------------------- */

/*
 * Left-aligned, not centred. A row of centred tiles makes the eye find a new left edge for each one;
 * a shared edge is what lets four figures be read as one movement.
 */
.headline-tile {
  display: grid;
  gap: var(--space-1);
  align-content: start;
  padding: var(--space-4);
  width: 100%;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-hairline);
  color: var(--text-primary);
  text-align: left;
  font: inherit;
}

.headline-tile-button {
  cursor: pointer;
  transition: box-shadow var(--motion-fast) var(--ease-out);
}
.headline-tile-button:hover {
}
.headline-tile-button:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

/* The figure and its trend share a baseline row, so the sparkline reads as belonging to the number
   rather than as a second panel stacked under it. */
.headline-tile-row {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-3);
  min-width: 0;
}
.headline-tile-spark {
  flex: none;
  padding-bottom: 3px;
}
.headline-tile-detail {
  font-size: var(--text-xs);
  color: var(--text-secondary);
  line-height: var(--leading-body);
}

.headline-rail {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-3);
}

/* ---- node tiles and pod counts --------------------------------------------------------------- */

/* The denominator, printed small beside the figure rather than in a second line. "6% of 16 cores" is
   a capacity reading; "6%" on its own is a number waiting for a question. */
.heat-tile-of {
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: 400;
  color: var(--text-secondary);
}

/*
 * The kubelet's own pod ceiling — the constraint nobody looks at until nothing schedules.
 *
 * A button rather than a figure, because reaching it is a distinct problem with a distinct fix, and
 * it is reached independently of CPU and memory.
 */
.pod-count {
  display: inline-flex;
  align-items: baseline;
  gap: 1px;
  padding: 2px var(--space-2);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: none;
  font: inherit;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-xs);
  cursor: pointer;
  color: var(--text-primary);
}
.pod-count:hover {
  border-color: var(--border-hairline);
  background: var(--bg-hover);
}
.pod-count:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 1px;
}
.pod-count-value {
  font-weight: 600;
}
.pod-count-warning {
  color: var(--severity-warning-fg);
}
.pod-count-critical {
  color: var(--severity-critical-fg);
}
.pod-count-of {
  color: var(--text-secondary);
}

/* ---- the band's key -------------------------------------------------------------------------- */

/*
 * Four marks that mean four different things need naming once.
 *
 * Shown under the cluster panel only, not repeated on every table row: a legend beside each of
 * twelve bands is noise, and the tooltip on each band carries the same facts in words.
 */
.band-key {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border);
  font-size: var(--text-xs);
  color: var(--text-secondary);
}
.band-key > span {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}
.band-key-swatch {
  display: inline-block;
  width: 12px;
  height: 10px;
  border-radius: 2px;
}
/* The accent, not the ok green — see `heatTone` on why magnitude does not ramp through a status hue.
   The key and the band it describes must not drift apart. */
.band-key-used {
  background: var(--severity-info-fill);
}
.band-key-reserved {
  background: color-mix(in srgb, var(--severity-info-fill) 30%, transparent);
}
.band-key-request {
  width: 2px;
  height: 12px;
  background: var(--text-primary);
}
.band-key-limit {
  width: 3px;
  height: 12px;
  border-radius: 2px;
  background: var(--severity-warning-fill);
}

/* ---- triage ---------------------------------------------------------------------------------- */

.triage-sev-critical {
  color: var(--severity-critical-fg);
}
.triage-sev-warning {
  color: var(--severity-warning-fg);
}
.triage-sev-info {
  color: var(--severity-info-fg);
}

/* ---- ranked namespaces ----------------------------------------------------------------------- */

/*
 * Wraps rather than overflowing.
 *
 * A namespace can carry both flags at once — pods not running *and* containers with no memory limit
 * — and two badges are wider than any track that leaves room for the name. Overflowing pushed them
 * left over the value column, so the figure and the badge printed on top of each other.
 */
.rank-flags {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-2);
  justify-content: flex-end;
  min-width: 0;
}

.missing-list {
  margin: 0;
  padding-left: var(--space-5);
  font-size: var(--text-sm);
  line-height: var(--leading-relaxed);
  color: var(--text-secondary);
}

/* ------------------------------------------------------------------ the Images board (monitoring/images)
 *
 * One board for every panel on the page, a step roomier than the default: the page is mostly tables
 * of long strings, and at the default gap the panels' borders were the only thing separating one
 * table from the next.
 */
.board.img-board { gap: var(--space-5); }
.img-lead { margin: 0 0 var(--space-3); font-size: var(--text-sm); color: var(--text-secondary); }
/* An event's message is the fact; the example pod under it is context, so it steps down. */
.img-event { display: grid; gap: 2px; min-width: 0; }
.img-event-msg { font-weight: 500; color: var(--color-text); line-height: 1.4; }
.img-event-eg { font-size: var(--text-xs); color: var(--text-secondary); }
.img-when { font-size: var(--text-sm); color: var(--text-secondary); }
.img-conditions { margin-bottom: var(--space-3); }
.img-condition-list { margin: 0; padding: 0; list-style: none; display: grid; gap: var(--space-1); font-size: var(--text-sm); }
/* A bar: a volume's fill, and a registry's share of images. */
.img-meter-track {
  position: relative;
  flex: 1 1 auto;
  min-width: 4rem;
  max-width: 16rem;
  height: 8px;
  border-radius: 999px;
  background: var(--color-sunken);
  box-shadow: inset 0 0 0 1px var(--color-border);
  overflow: hidden;
}
.img-meter-fill { position: absolute; inset: 0 auto 0 0; border-radius: inherit; background: var(--ok); }
.img-meter-fill[data-tone="warning"] { background: var(--warn); }
.img-meter-fill[data-tone="critical"] { background: var(--critical); }
.img-meter-fill[data-tone="info"] { background: var(--info); }
.img-meter-wide { display: block; max-width: none; height: 10px; }
/* A registry volume, read as a quantity rather than a table row: size, fill, used and free. */
.img-volumes { display: grid; gap: var(--space-4); }
.img-volume { display: grid; gap: var(--space-2); }
.img-volume-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-3); }
.img-volume-size { font-size: var(--text-lg); font-weight: 600; }
.img-volume-read { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--space-1) var(--space-3); font-size: var(--text-sm); }
.img-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
  gap: var(--space-3);
  margin: var(--space-4) 0 0;
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border);
}
.img-facts dt { font-size: var(--text-xs); color: var(--text-secondary); }
.img-facts dd { margin: 2px 0 0; font-size: var(--text-lg); font-weight: 600; }
.img-share { display: flex; align-items: center; gap: var(--space-3); min-width: 10rem; }
.img-share .num-cell { min-width: 4ch; text-align: right; }
.img-repo { overflow-wrap: anywhere; }
/* Namespaces and workloads, summarised to their first entries; the full list is in the cell's title. */
.img-list { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1) var(--space-2); min-width: 0; }
.img-clip { display: inline-block; max-width: 26ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom; }
.img-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-2) var(--space-4); margin-top: var(--space-3); }
/* Three caveats as columns across a full-width panel, not three paragraphs down a half-width one. */
.img-caveats { -moz-columns: 3 20rem; columns: 3 20rem; -moz-column-gap: var(--space-6); column-gap: var(--space-6); }
.img-caveats li { -moz-column-break-inside: avoid; break-inside: avoid; margin-bottom: var(--space-2); }

/*
 * The trend cell, which is a button because it asks a question the rest of the row cannot.
 *
 * Deliberately quieter than `.pod-count`: it sits in every row of the table, and a visible control
 * repeated six times down a column competes with the sparklines it wraps. The affordance arrives on
 * hover and on keyboard focus, which is where it is needed and nowhere else.
 */
.trend-cell {
  display: inline-flex;
  align-items: center;
  padding: 2px var(--space-1);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: none;
  font: inherit;
  cursor: pointer;
}
.trend-cell:hover:not(:disabled) {
  border-color: var(--border-hairline);
  background: var(--bg-hover);
}
.trend-cell:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 1px;
}
/* Not greyed out. The sparkline still carries what little history there is, and dimming the data to
   signal that a button is unavailable removes information to communicate an affordance. */
.trend-cell:disabled {
  cursor: default;
}

/*
 * Why the cell is inert, printed beside the line rather than hidden in a `title`.
 *
 * The rule above is right that the data must not be dimmed, but on its own it left a disabled cell
 * looking exactly like a live one — same sparkline, same weight, no hover, and a tooltip nobody
 * reaches for before deciding a control is broken. This is the part that is allowed to be quiet: the
 * reading count is chrome, so it takes the muted tone the data is not permitted to.
 */
.trend-cell-note {
  margin-left: var(--space-2);
  color: var(--text-muted);
  font-size: var(--text-xs);
  white-space: nowrap;
}

/* ---- the advice dialog ----------------------------------------------------------------------- */

/*
 * The measured half of a trend answer, set apart from the model's prose below it.
 *
 * The left rule and the sunken ground are doing one job: making it obvious at a glance where the
 * arithmetic stops and the generated explanation starts.
 */
.advice-measured {
  padding: var(--space-3) var(--space-4);
  border-left: 2px solid var(--color-primary);
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
}
.advice-measured-head {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  margin-top: var(--space-1);
  font-size: var(--text-md);
}
.advice-measured-facts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-4);
  margin-top: var(--space-2);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-xs);
  color: var(--text-secondary);
}

.advice-wait {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
  padding: var(--space-4);
  border-radius: var(--radius-md);
  background: var(--bg-sunken);
}
/* One of the three sanctioned loops: it is the thing saying the request is still open. A minutes-long
   wait with no motion reads as a hung dialog, and the reader's next move is to close it. */
.advice-wait-dot {
  flex: none;
  width: 10px;
  height: 10px;
  margin-top: 4px;
  border-radius: var(--radius-full);
  background: var(--color-primary);
  animation: pill-pulse 1.6s var(--ease-out) infinite;
}
/* Slowed, not frozen — for the same reason the live pill is. A still dot on a dialog that is still
   waiting is indistinguishable from one that has given up. */
@media (prefers-reduced-motion: reduce) {
  .advice-wait-dot {
    animation-duration: 3.2s !important;
    animation-iteration-count: infinite !important;
  }
}

/*
 * The fixes, as a numbered sequence.
 *
 * Ordered because the model is asked for them in order and the order carries information — the first
 * step is usually the one that stops the bleeding and the last is usually the one that costs a
 * restart. A bulleted list would throw that away.
 */
.runbook {
  display: grid;
  gap: var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: runbook;
}
.runbook-step {
  display: grid;
  grid-template-columns: 26px 1fr;
  gap: var(--space-3);
  padding: var(--space-4);
  border-radius: var(--radius-md);
  background: var(--bg-sunken);
}
.runbook-index {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: var(--radius-full);
  background: var(--bg-surface);
  border: 1px solid var(--border-hairline);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--text-secondary);
}
/*
 * `minmax(0, 1fr)`, not the implicit `auto`, and `min-width: 0` alone does not do it.
 *
 * `min-width: 0` lets *this box* shrink; it says nothing about the column inside it. An implicit
 * grid column is `minmax(auto, max-content)`, so a single `oc patch` with an inline JSON patch —
 * one unbreakable 190-character token — sized the track to 1404px inside an 858px box. The prose
 * above it was laid out to that width too, so every line of the explanation ran off the right edge
 * and the dialog grew a horizontal scrollbar. Measured, not theorised: `scrollWidth` 1478 against a
 * `clientWidth` of 898.
 */
.runbook-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-2);
  min-width: 0;
}
/*
 * A step's command wraps rather than scrolling.
 *
 * In a 38rem dialog `oc set resources deployment/istiod-default-v1-24-3 -n istio-system -c discovery
 * --requests=memory=…` was cut at `-n istio-` behind a scrollbar — the namespace, the container and
 * the value, the three parts a reader checks before running it, were the parts out of view. Wrapped,
 * the whole command is on screen. Wrapped at the edge, the way a terminal wraps a long line, rather
 * than at hyphens: a browser breaks after every `-`, so `--requests` became `--` on one line and
 * `requests=…` on the next, which reads as two arguments. Copy and Run take the text, not the
 * wrapping, so nothing they send changes.
 */
.runbook-step .code-text {
  white-space: pre-wrap;
  word-break: break-all;
}
.runbook-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  font-size: var(--text-md);
}
/* The step's title. Model-written, but still a title — it is scanned to find the right step, and the
   instructions under it are what gets read. */
.runbook-head > strong {
  font-family: var(--font-sans);
  font-size: var(--text-md);
}
/* Same size as the explanation above it. A step's detail set smaller than the prose that led to it
   reads as a footnote, and these are the instructions. The measure comes from the column. */
.runbook-detail {
  font-family: var(--font-sans);
  font-size: var(--text-md);
  line-height: var(--leading-relaxed);
  overflow-wrap: anywhere;
  text-wrap: pretty;
}
.runbook-withheld { margin: var(--space-2) 0 0; font-size: var(--text-xs); line-height: 1.5; color: var(--text-muted); }
.runbook-withheld strong { color: var(--text-secondary); font-weight: 600; }

/*
 * A command withheld only for its blanks, as a form (`apps/portal/app/command-template.tsx`).
 *
 * Dashed, because it is a command that is not finished yet: the solid code block inside it becomes
 * runnable only once every blank below is filled. Each blank is a numbered row — what it stands for,
 * the field, how to find the value, and the read that prints it.
 */
.cmd-template {
  display: grid;
  gap: var(--space-3);
  margin-top: var(--space-2);
  padding: var(--space-3);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-md);
  min-width: 0;
}
.cmd-template-lead { margin: 0; font-size: var(--text-sm); line-height: var(--leading-relaxed); color: var(--text-secondary); }
.cmd-template-lead strong { color: var(--text-primary); font-weight: 600; }
.cmd-template-lead code { font-family: var(--font-mono); font-size: 0.95em; color: var(--accent-text); overflow-wrap: anywhere; }
.cmd-template-blanks { display: grid; gap: var(--space-3); margin: 0; padding: 0; list-style: none; }
.cmd-template-blank {
  display: grid;
  gap: var(--space-2);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-hairline);
  min-width: 0;
}
.cmd-template-label { display: flex; align-items: baseline; gap: var(--space-2); flex-wrap: wrap; font-size: var(--text-sm); color: var(--text-secondary); }
.cmd-template-label code { font-family: var(--font-mono); font-size: var(--text-sm); font-weight: 600; color: var(--accent-text); overflow-wrap: anywhere; }
.cmd-template-how { margin: 0; font-size: var(--text-xs); line-height: 1.5; color: var(--text-muted); overflow-wrap: anywhere; }
.cmd-template-fault { margin: 0; font-size: var(--text-xs); line-height: 1.5; color: var(--critical-text); }
.cmd-template-picks { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.cmd-template-pick {
  min-height: 26px;
  padding: 0 var(--space-2);
  font-size: var(--text-xs);
  color: var(--text-secondary);
  background: var(--bg-sunken);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-sm);
  cursor: pointer;
  overflow-wrap: anywhere;
}
.cmd-template-pick:hover { color: var(--text-primary); border-color: var(--border-strong); }
.cmd-template-pick:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.cmd-template-pick[aria-pressed="true"] { color: var(--accent-text); border-color: var(--accent); background: var(--accent-soft); }
.cmd-template .code-text { white-space: pre-wrap; word-break: break-all; }

/*
 * The row under a step's command: a Run button, or a sentence saying where it does run.
 *
 * Indented to the command block above it rather than to the step, because it is about that command
 * and not about the step — at the step's indent it reads as another instruction.
 */
.advice-run {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-top: var(--space-2);
}

.advice-run-note {
  font-size: var(--text-xs);
  color: var(--text-muted);
}

/*
 * The other state: no button, because this product cannot honestly run it.
 *
 * Deliberately quiet — muted text at the small size, no warning tone and no icon. It is not a
 * problem with the step and nothing has gone wrong; it is a note about where the reader's terminal
 * is. A warning colour here would put an alarm on five of the five steps in an ordinary answer.
 */
.advice-run--elsewhere {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-top: var(--space-2);
  font-size: var(--text-xs);
  color: var(--text-muted);
  line-height: 1.5;
}

/*
 * The answer and the shell, side by side.
 *
 * The answer column keeps its reading measure — widening prose to fill a 1600px dialog is how a
 * runbook becomes unreadable — and the terminal takes the rest. Below the breakpoint they stack,
 * because a 60-column shell next to a 40-column paragraph is worse than either alone.
 */
/*
 * The docked layout: two panes the height of the dialog, each owning its own scrolling.
 *
 * The first version made the shell `position: sticky` inside the dialog's single scroll container,
 * which is a different thing that looks similar: the terminal was still laid out in the flow of the
 * answer, so it had the answer's height, it scrolled until it stuck, and the composer ran the full
 * width underneath both — a metre-wide text box beneath a terminal it has nothing to do with.
 *
 * Here the dialog body scrolls not at all. The grid is exactly its height, the answer column scrolls
 * inside itself with the composer docked to its foot, and the shell column is a fixed pane. That is
 * what makes the terminal a constant size: it is never taller than the space it has, so it never
 * gives the dialog anything to scroll.
 */
.advice-split {
  display: grid;
  /*
   * Three tracks: the answer, the grip, the shell.
   *
   * `--advice-answer` is set on the element by a drag and is otherwise unset, so the fallback — the
   * reading measure — is the default with no state anywhere. The variable is what lets the media
   * query below replace this declaration outright; an inline `grid-template-columns` would outrank
   * it and carry a laptop's pixel width onto a phone.
   *
   * The fallback used to be `var(--measure-prose, 68ch)`, and `--measure-prose` has never been
   * declared or set by anything — so the layer resolved to 68ch every time it was read. The measure
   * this product actually keeps as a token is `--size-advice-dialog`, which is derived arithmetic,
   * not this. A dead indirection reads as a knob somebody can turn.
   */
  grid-template-columns:
    minmax(0, var(--advice-answer, 68ch))
    var(--space-3)
    minmax(0, 1fr);
  gap: var(--space-3);
  align-items: stretch;
  /*
   * Flex, not `height: 100%`.
   *
   * The percentage was the first attempt and it silently did nothing: the dialog's panel is
   * `height: auto` capped by `max-height`, which is not a definite height, so `100%` resolved to the
   * content's own height and the grid grew exactly as it had before. The dialog now passes `fill`,
   * which makes the body a flex column with a real height — so this asks for the space rather than
   * for a fraction of something indefinite.
   */
  flex: 1 1 auto;
  min-height: 0;
}

/*
 * One column: nothing is docked yet.
 *
 * The tree is the same as the two-column case — the answer is the first child either way — so a
 * shell arriving later does not remount what is already on screen. The grip and the shell column
 * are not rendered at all in this state, so the only work here is the track list, and the flex
 * declarations above are inert in a dialog body that is not a flex column.
 */
.advice-split-single {
  grid-template-columns: minmax(0, 1fr);
}

.advice-split-answer {
  display: flex;
  flex-direction: column;
  min-width: 0;
  /* `min-height: 0` for the same reason the modal body needs it: a flex child will not shrink below
     its content without it, so the column grows instead of scrolling and takes the dialog with it. */
  min-height: 0;
  overflow-y: auto;
}

/*
 * The answer column scrolls without drawing a bar.
 *
 * The product's scrollbar is a deliberate 10px pill and it is right nearly everywhere. It is wrong
 * here: this column is one of two panes with a terminal beside it, and a full-height bar down the
 * middle of the dialog reads as a divider between the two rather than as a control belonging to the
 * text. Reserving a gutter for it made that worse rather than better — the bar was then always
 * there, on an answer short enough not to need one.
 *
 * Nothing about the scrolling changes: wheel, trackpad, and the keyboard once anything in the column
 * has focus, which is most of it — the composer, every step's buttons.
 *
 * The `@supports` split is this file's existing rule and is not optional. Declaring `scrollbar-width`
 * unconditionally moves Blink off its `::-webkit-scrollbar` path and onto the standard scrollbar,
 * which draws platform stepper arrows — so the standard property goes only to engines that have no
 * pseudo-element to be moved off in the first place.
 */
.advice-split-answer::-webkit-scrollbar {
  width: 0;
  height: 0;
}


.advice-split-shell {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  /* The pane clips rather than scrolls. What is inside it is a terminal with its own scrollback,
     which is the scrolling a reader actually wants here. */
  overflow: hidden;
}

/*
 * The grip, which is nothing until the pointer reaches it.
 *
 * A permanent divider down the middle of a dialog is a line the eye steps over on every pass, for a
 * control most readers will never touch. This one appears where the cursor already is. The hit area
 * is the whole track — wider than the mark it draws — because a 2px target is a control that exists
 * for people who already know it is there.
 */
.advice-split-handle {
  position: relative;
  cursor: col-resize;
  border-radius: var(--radius-full);
  /* Or a drag on a touchscreen scrolls the dialog instead of moving the split. */
  touch-action: none;
}

.advice-split-handle::before {
  content: "";
  position: absolute;
  inset: 0 calc(var(--space-1) + 1px);
  border-radius: var(--radius-full);
  background: var(--border-strong);
  opacity: 0;
  transition: opacity var(--motion-fast) var(--ease-out), background var(--motion-fast) var(--ease-out);
}

.advice-split-handle:hover::before,
.advice-split-handle:focus-visible::before,
.advice-split-handle[data-dragging="true"]::before {
  opacity: 1;
}

.advice-split-handle:focus-visible::before,
.advice-split-handle[data-dragging="true"]::before {
  background: var(--accent);
}

/* No ring around the track itself — the mark inside it turning accent *is* the focus state, and a
   rounded outline around a 12px column reads as a stray pill. */
.advice-split-handle:focus-visible {
  outline: none;
}

.advice-split-shell > .bastion {
  flex: 1 1 auto;
  min-height: 0;
}

/* The 320px floor is right for a shell on a page of its own, and wrong inside a fixed pane: on a
   short window it is the one box that refuses to fit, and the dialog scrolls to accommodate it. */
.advice-split-shell .bastion-screen {
  min-height: 120px;
}

/*
 * Inside the column, the dock does not bleed.
 *
 * Its negative margins exist to reach the dialog body's padding edges when it is the full width of
 * that body. In a two-column layout the right-hand bleed reaches into the gap and under the
 * terminal, so the bar is squared off here and `margin-top: auto` drops it to the foot of a short
 * answer instead of leaving it floating mid-column.
 */
.advice-split .chat-dock {
  margin: auto 0 0;
  padding: var(--space-3) 0 var(--space-2);
  bottom: 0;
}

@media (max-width: 1100px) {
  /* Below this the two panes stop being two panes: a 68ch column and a usable terminal do not both
     fit, and a 30ch terminal is worse than one under the answer. The dialog scrolls again here. */
  .advice-split {
    grid-template-columns: minmax(0, 1fr);
    /* Stacked, there is nothing to drag between: the grip would be a full-width bar the reader can
       only move sideways, in a layout with no sideways. */
    /* Stacked, the two panes scroll together — but still inside the dialog's body, which is clipping
       because it was told to fill. Without this the second pane is simply unreachable. */
    overflow-y: auto;
  }
  .advice-split-handle {
    display: none;
  }
  /*
   * `min-height: auto` restates the default the two-column rules turned off, and it is the whole fix
   * for the stacked case. A grid row sized `auto` grows no smaller than its item's minimum, and with
   * `min-height: 0` that minimum is zero — so in a body with a definite height the answer's row
   * collapsed to the space left over, its text overflowed out of it, and the shell's row started
   * mid-answer and was painted on top of the steps it was opened to run.
   */
  .advice-split-answer {
    overflow-y: visible;
    min-height: auto;
  }
  .advice-split-shell {
    overflow: visible;
    min-height: auto;
  }
  .advice-split-shell .bastion-screen {
    min-height: 320px;
  }
  .advice-split .chat-dock {
    margin: var(--space-2) calc(var(--space-5) * -1) calc(var(--space-5) * -1);
    padding: var(--space-4) var(--space-5) var(--space-5);
    bottom: calc(var(--space-5) * -1);
  }
}

/*
 * "First, find the value" — the block under a remedy whose command has a placeholder in it.
 *
 * Indented and quieter than the remedy itself, because it is a step before the step. It exists at
 * all because a command carrying `<n>` was pasted straight into a shell from this panel and came
 * back a bash syntax error: the placeholder said this product does not know the value, and said
 * nothing about where to get it.
 */
.remedy-find {
  display: grid;
  gap: var(--space-1);
  margin-top: var(--space-2);
  padding-left: var(--space-3);
  border-left: 2px solid var(--border-hairline);
}

.remedy-find-label {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--text-secondary);
}

.remedy-find-what {
  font-size: var(--text-xs);
  color: var(--text-muted);
}

/*
 * The rollout-history picker, above the command it fills in.
 *
 * Same left rule as `.remedy-find` because it answers the same question — which revision — and the
 * two are alternatives rather than steps: the picker appears when this product could read the
 * history, and the find block when it could not.
 */
.remedy-revisions {
  display: grid;
  gap: var(--space-1);
  margin: var(--space-2) 0;
  padding-left: var(--space-3);
  border-left: 2px solid var(--border-hairline);
}

.remedy-revisions select { max-width: 34rem; }

/*
 * One remedy's vertical rhythm: the prose is one block, and the controls are a step away from it.
 *
 * Without this the parts sat at whatever their own margins were — the title, why and caution at
 * zero, the picker at `space-2`, and the command block at nothing at all, so on a remedy with no
 * picker the command sat 2px under "Before you do" and read as part of the warning. Title, why and
 * caution stay one tight paragraph; the picker, the command and the find block each start a
 * `space-3` step below it. `minmax(0, 1fr)` because a single `auto` track is sized by the longest
 * command and would push the card out to that width.
 */
.remedy-card {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-1);
}
.remedy-card > .remedy-revisions,
.remedy-card > .code-block,
.remedy-card > .remedy-find {
  margin: var(--space-2) 0 0;
}

/*
 * "Did it work?" is the step after choosing a remedy, not another remedy, so a rule sets it apart
 * from the list of alternatives above it. Its button gets the same step down from the prose.
 */
.fix-outcome {
  padding-top: var(--space-4);
  border-top: 1px solid var(--border-hairline);
}
.fix-outcome-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

/*
 * The rows a fix verdict was drawn from.
 *
 * A definition list rather than prose, and the same left rule as the two blocks above, because it
 * is the third thing on this panel that stands *behind* a claim rather than making one: the find
 * command behind a missing value, the picker behind a revision, and this behind "fixed". A reader
 * who does not believe the sentence can read the observation that produced it, including its age.
 *
 * `auto` on the term column rather than a fixed width: these are object names, which run from `pod`
 * to a 63-character generated one, and a fixed column either truncates the long names or leaves a
 * hand's width of space beside the short ones.
 */
.fix-evidence {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 2px var(--space-3);
  margin: var(--space-2) 0 0;
  padding-left: var(--space-3);
  border-left: 2px solid var(--border-hairline);
  font-size: var(--text-xs);
}
.fix-evidence dt { color: var(--text-secondary); font-weight: 600; overflow-wrap: anywhere; }
.fix-evidence dd { margin: 0; color: var(--text-muted); overflow-wrap: anywhere; }

/*
 * "Ask about this hop", inside the dialog a hop already opens.
 *
 * Separated from the request counts above it by a hairline rather than by a heading, because it is
 * not a second section of the same report — it is the thing the reader does about what they have
 * just read. Nothing here is a status surface, so nothing takes a severity colour: the hop's colour
 * is on the map behind the dialog, and repeating it here would be a second claim about one hop.
 */
.hopask {
  display: grid;
  gap: var(--space-2);
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border-hairline);
  min-width: 0;
}

.hopask-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.hopask-head > .panel-eyebrow { margin-right: auto; }

/*
 * The question itself, set as a quotation rather than as body copy.
 *
 * It is the exact text that will be sent, and a reader deciding whether to spend an inference has to
 * be able to tell it apart from the product's own prose around it at a glance.
 */
.hopask-question {
  margin: 0;
  padding-left: var(--space-3);
  border-left: 2px solid var(--border-hairline);
  font-size: var(--text-sm);
  color: var(--text-primary);
}

.hopask-note {
  margin: 0;
  font-size: var(--text-xs);
  color: var(--text-muted);
}

.hopask-answer { min-width: 0; }

/* The registered bastions, on the Agents page. */
.bastion-list {
  display: grid;
  gap: var(--space-3);
}

.bastion-row {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
}

.bastion-row-main {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.bastion-row-addr {
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

/* Fingerprints and provenance: present, and deliberately not competing with the address above. */
.bastion-row-meta {
  display: flex;
  gap: var(--space-4);
  flex-wrap: wrap;
  font-size: var(--text-xs);
  color: var(--text-muted);
  font-family: var(--font-mono);
}

.bastion-row-actions {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
}

/* The result of one test connection, under the row that made it.
 *
 * Inset rather than a card of its own: it belongs to that host and disappears when the page is left,
 * because it describes a moment and not a property. Nothing here is persisted or re-rendered on a
 * later visit — a green tick that outlived its connection is the failure mode this whole surface was
 * argued against for. */
.bastion-test {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
  border: 1px solid var(--border-hairline);
}

.bastion-test-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.bastion-test-when {
  font-size: var(--text-xs);
  color: var(--text-muted);
}

.bastion-checks {
  display: grid;
  gap: var(--space-1);
  margin: 0;
  padding: 0;
  list-style: none;
}

.bastion-check {
  display: grid;
  grid-template-columns: 1.25rem minmax(9rem, auto) 1fr;
  gap: var(--space-2);
  align-items: baseline;
  font-size: var(--text-sm);
}

/* Glyph and colour together, never colour alone — and the state is also in the accessible name. */
.bastion-check-mark {
  font-weight: 600;
  text-align: center;
}

.bastion-check--ok .bastion-check-mark {
  color: var(--ok-text);
}

.bastion-check--failed .bastion-check-mark {
  color: var(--critical-text);
}

.bastion-check--skipped .bastion-check-mark {
  color: var(--text-muted);
}

.bastion-check-label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--text-primary);
}

.bastion-check-detail {
  color: var(--text-secondary);
  overflow-wrap: anywhere;
}

.bastion-test-note {
  margin: 0;
  font-size: var(--text-xs);
  color: var(--text-muted);
}

@media (max-width: 640px) {
  .bastion-check {
    grid-template-columns: 1.25rem 1fr;
  }

  .bastion-check-detail {
    grid-column: 2;
  }
}

/* ================================================================== bastion shell
 *
 * The one surface in this product that is not the product's.
 *
 * Inside the frame is whatever a remote host printed, so it gets a near-black ground and a plain
 * light foreground rather than the design tokens: colouring a shell with the palette would make its
 * output read as status this product is asserting about the estate, which it emphatically is not.
 * The frame, the header and the notes around it are ours and use the tokens normally.
 */
.bastion {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-height: 0;
}

/*
 * The bastion shell a command opens from any page (`shell-route.tsx`).
 *
 * Floating at the right edge rather than docked into the page, because the commands that open it are
 * everywhere — an incident runbook in a dialog, a root cause, a trace explanation, the assistant's
 * prose — and a dock in each would be a dozen layouts. It stacks above a dialog's scrim
 * (`--z-shell`), since a command pressed in a dialog has to open a shell the reader can reach without
 * closing what they are reading. `--overlay-surface` is the dialog's own ground, opaque in the glass
 * theme, so terminal output is never read through the page behind it.
 */
.shell-float {
  position: fixed;
  right: var(--space-4);
  bottom: var(--space-4);
  z-index: var(--z-shell);
  display: flex;
  flex-direction: column;
  width: min(760px, calc(100vw - 2 * var(--space-4)));
  height: min(560px, calc(100vh - 2 * var(--space-4)));
  padding: var(--space-4);
  background: var(--overlay-surface);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-lg);
}
.shell-float > .bastion {
  flex: 1;
}

.bastion-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.bastion-title {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text-primary);
}

/* The address, beside the title rather than under it. Muted and mono: it is a fact about the
   session, not a heading, and it must not compete with the word "connected" next to it. */
.bastion-head-addr {
  font-size: var(--text-xs);
  color: var(--text-muted);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bastion-head-controls {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.bastion-note {
  margin: 0;
  font-size: var(--text-xs);
  color: var(--text-muted);
  font-family: var(--font-mono);
}

/* The recording notice is prose, not an address, so it is not mono — and it is the one line here
   that must be read rather than scanned. */
.bastion-note--record {
  font-family: var(--font-sans);
}

/*
 * The screen, and the only part of it this stylesheet paints.
 *
 * Black in every theme. It followed the theme for a while, with a whole light ramp in
 * `TERMINAL_THEME`; the reader asked for the terminal to be dark all the time, and the window around
 * it is now dark too (`.bastion-dark`), so a light screen inside it would be the odd one out.
 *
 * The ground is declared here and set on the canvas in `terminal.tsx`, which is one value in two
 * files — `theme.test.ts` fails when they disagree. They cannot be collapsed into one place: xterm
 * paints its own canvas and cannot read a custom property, while the padding ring around that canvas
 * is drawn by this rule and by nothing else.
 */
.bastion-screen {
  --bastion-ground: #000000;
  flex: 1;
  min-height: 320px;
  background: var(--bastion-ground);
  border: 1px solid var(--border-hairline);
  /* Square at the foot: `.bastion-status` closes the box. The two are one unit, not two cards. */
  border-radius: var(--radius-md) var(--radius-md) 0 0;
  padding: var(--space-2);
  overflow: hidden;
  /* Flex, so the element the terminal mounts into is stretched to this box's content area in both
     axes rather than asking for a percentage of it. See the note on `.bastion-screen-fit`. */
  display: flex;
}

/*
 * The element the terminal mounts into, and the reason it exists is one line of somebody else's
 * arithmetic.
 *
 * `FitAddon` decides how many rows fit by reading `getComputedStyle(parent).height` — and in Blink
 * that is the **border box** when `box-sizing: border-box`, which is everything here. It then
 * subtracts the padding of the *terminal's own* element, which has none. So every pixel of padding
 * and border on the mount was counted as usable grid: 26px of frame here, at a 15px row, is nearly
 * two rows more than the box can show, and xterm keeps the cursor on its last row — so the line the
 * frame cut in half was always the prompt being typed at.
 *
 * A cushion cannot fix that, and the previous 16px one made it slightly worse: padding on the
 * measured element is subtracted from what the rows have and added to what the addon thinks they
 * have. Measured over eleven pane heights, eight clipped the last row and three cleared it by 4px.
 *
 * This element carries no padding and no border, so its border box *is* its content box and the
 * addon's number is the true one. The frame keeps the padding, the ground and the corners.
 */
.bastion-screen-fit {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
}

/*
 * Where a dragged command will land.
 *
 * The drop target is the whole frame, and an outline is the only thing that can say so: the ring is
 * drawn over a terminal canvas that has just repainted itself, not over a product surface, so a
 * background or border change would be competing with whatever the remote host last printed. The
 * accent reads on both grounds, which is why it is the accent and not a tinted fill. Without this
 * the state was tracked and never shown — a drag with no target, where the reader lets go over a
 * rectangle and hopes.
 */
.bastion-screen[data-dropping="true"] {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.bastion-screen .xterm {
  height: 100%;
}

/*
 * The strip under the last row, which was black in the light theme.
 *
 * `xterm.css` ships `.xterm .xterm-viewport { background-color: #000; }` with the comment that it is
 * "required in order for the scroll bar to appear fully opaque" on OS X. The palette in
 * `terminal.tsx` never reaches it: `theme.background` paints the *rows*, and the rows are
 * `rows x rowHeight` tall while the viewport is the height of the frame. A row height is fractional
 * and a row count is not, so there is almost always a remainder below the last row — and that
 * remainder is the vendor's black.
 *
 * On a black ground that is invisible, which is why this survived: the dark theme is the one that
 * gets looked at. In light mode it is a black bar across the bottom of a white terminal, and it was
 * reported as exactly that.
 *
 * Bound to `--bastion-ground` rather than to a colour, so it is the same value the frame uses and
 * the two cannot drift. The scrollbar opacity the vendor comment is protecting is not a concern
 * here: this file hides that scrollbar outright, a few rules down.
 *
 * ## `.xterm` is in the selector to win the cascade, and removing it brings the bar straight back
 *
 * This override was written as `.bastion-screen .xterm-viewport` — two classes, the same
 * specificity as the vendor's `.xterm .xterm-viewport` — so which one applied came down to which
 * stylesheet the browser loaded last. It loads the vendor's last, every time, and not by accident:
 * `terminal.tsx` does `import "@xterm/xterm/css/xterm.css"`, so Next emits it into the *route's*
 * CSS chunk (`.next/static/css/app/alerts/page.css` and its siblings) while this file is in
 * `layout.css`, and a layout's stylesheet is linked before a page's. The override therefore lost on
 * every page that can open a shell, which is every page that has one — and the fix read as applied
 * because the rule was right there in the file.
 *
 * Three classes beats two whatever the order, so the bar cannot come back by somebody moving an
 * import. `theme.test.ts` now compares the two selectors' specificity rather than checking that
 * this rule exists, because existing was never the property that mattered.
 */
.bastion-screen .xterm .xterm-viewport {
  background-color: var(--bastion-ground);
}

/*
 * The status strip, under the screen.
 *
 * ## Why a terminal needs a foot and this one had none
 *
 * The pane was a header, a frame and a rectangle. Everything it could tell you was in the header,
 * which is where you look once — and the three things somebody wants while actually sitting at the
 * prompt are all things that change after they have stopped looking up: whether the socket is still
 * open, what grid the remote end is wrapping against, and whether the recording is still running.
 * A strip at the foot is where a terminal has always put those, from `vi`'s ruler to tmux's bar,
 * and the reason is the same: it is the edge of the thing you are already staring at.
 *
 * ## It is chrome, so it must not look like output
 *
 * The one hard rule here. It sits *outside* `.bastion-screen` and takes the product's surface
 * rather than the terminal's ground, because a strip that shared the black would read as a line the
 * remote host printed — and this is a root shell, where a line the product wrote and a line the
 * host wrote must never be confusable. The keys are lowercase and letterspaced for the same reason:
 * nothing a shell prints looks like that.
 */
.bastion-status {
  display: flex;
  align-items: center;
  gap: var(--space-2) var(--space-4);
  flex-wrap: wrap;
  padding: var(--space-2) var(--space-3);
  /*
   * Flush against the screen, not a card below it.
   *
   * `.bastion` is a flex column with `gap: var(--space-3)`, so the strip needs to pull that gap
   * back to sit on the frame's bottom edge — and the frame gives up its bottom corners in return,
   * a few rules up. Two boxes with a gap between them would read as the terminal and then some
   * other panel; one box with a foot reads as the terminal.
   */
  margin-top: calc(var(--space-3) * -1);
  border: 1px solid var(--border-hairline);
  border-top: 0;
  border-radius: 0 0 var(--radius-md) var(--radius-md);
  background: var(--bg-sunken);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

.bastion-status-cell { display: inline-flex; align-items: baseline; gap: var(--space-2); }
.bastion-status-key {
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: lowercase;
  color: var(--color-text-subtle);
}

/*
 * The live state, with the dot that carries it.
 *
 * The dot is the only thing in this strip that is a colour rather than a word, and it still has the
 * word beside it — `status.ts`'s rule, and the one that keeps the strip readable for somebody who
 * cannot separate the green from the amber.
 */
.bastion-status-live {
  --live: var(--text-muted);
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-weight: 600;
  color: var(--live);
}
.bastion-status-live.is-open { --live: var(--ok-text); }
.bastion-status-live.is-connecting { --live: var(--text-secondary); }
.bastion-status-live.is-error { --live: var(--critical-text); }
.bastion-status-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--live);
  flex: none;
}
/*
 * The pulse means "in flight", and stops the moment anything else is true.
 *
 * This file's standing rule is that a loop means something is still happening. A dot that kept
 * pulsing on a closed session would be the product insisting on activity that had ended.
 */
.bastion-status-live.is-connecting .bastion-status-dot { animation: bastion-status-pulse 1.4s ease-in-out infinite; }
@keyframes bastion-status-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}
@media (prefers-reduced-motion: reduce) {
  .bastion-status-live.is-connecting .bastion-status-dot { animation: none; }
}

/* Pushed to the far end: it is a standing fact about the session, not one of its measurements. */
.bastion-status-rec { margin-left: auto; }

/*
 * The terminal draws no scrollbar, and the pane it is in never changes size.
 *
 * xterm keeps its own viewport and its own scrollback, and it renders a bar down the right of the
 * screen whenever the scroll area is even a fraction taller than the viewport — which it is most of
 * the time, because a row height is fractional and the row count is not. So a shell showing five
 * lines of output had a full-height bar beside it, on a pane where nothing had scrolled at all.
 *
 * Hiding it changes nothing about the scrolling: the wheel, the trackpad, Shift+PageUp and the
 * terminal's own auto-scroll-to-bottom on new output all still work, which is the whole of what a
 * scrollback is for. What goes away is a control that was reporting movement that had not happened.
 *
 * The standard property is in the file's one feature query, not here — see the note there.
 */
.bastion-screen .xterm-viewport::-webkit-scrollbar {
  width: 0;
  height: 0;
}

/*
 * The standing way into the command panel, under the steps.
 *
 * Separated by a hairline rather than sitting flush with the last step: it is not a sixth thing to
 * do, it is the door out of the list.
 */
.advice-shell {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-hairline);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

/* In the dock it is a bar above the composer, not a rule under the last step: the hairline and the
   space above it belong to the version that ended a list. */
.chat-dock .advice-shell {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
  margin-bottom: var(--space-2);
}

.advice-foot {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
  font-size: var(--text-xs);
  color: var(--text-secondary);
}
/*
 * The separator trails the item it follows; it never leads the next one.
 *
 * As a `::before` on the following span it belongs to that span, so when the row wraps the dot goes
 * with it and the second line opens with a stray interpunct hanging in the margin. As an `::after`
 * on the preceding one, a wrap leaves the dot at the end of the line above — which is how a
 * separated list is meant to break.
 */
.advice-foot > span:not(:last-child)::after {
  content: "·";
  margin-left: var(--space-3);
}

/*
 * Everything in this dialog sits in a single column that may not grow.
 *
 * `minmax(0, 1fr)` and not the implicit `auto`, for the reason spelled out over `.runbook-body`: one
 * unbreakable token anywhere below — an `oc patch` with an inline JSON body, a 63-character pod name
 * — sizes an `auto` track to itself and lays every paragraph in the dialog out to that width. The
 * symptom is not a wide code block, it is an explanation whose every line runs off the right edge.
 */
.advice,
.advice-body,
.chat,
.chat-turn,
.chat-reply {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  min-width: 0;
}

/* ---- the editorial layer ----------------------------------------------------------------------
 *
 * **Sans is the product, mono is the measurement.** Two voices, and the second one is reserved for
 * things that were counted: figures, addresses, commands, timestamps.
 *
 * There used to be a third. Every paragraph a model wrote — the finding, the cause, a runbook step's
 * instructions, the sentence under a critical alert — was set in `--font-serif`, on the rule "serif
 * is what you read, sans is what you scan". Two problems ended it, and both are worth keeping
 * written down because the rule is a good one and will be proposed again.
 *
 * **The designed face never reached anybody.** The serif stack opened with three licensed faces this
 * product does not ship and will not fetch, because installs are air-gapped. So every host resolved
 * it down to Georgia — and an AI answer set in the system book face, on a dark console, next to a
 * radar and a rack of gauges, reads as a document that wandered into an instrument. That is what it
 * looked like in use, and it is the whole reason this changed. `--font-sans` is the only family with
 * an `@font-face` behind it (top of this file), which is to say the only one whose design is
 * guaranteed to arrive.
 *
 * **A typeface cannot say "a model wrote this".** It arrived as `--font-ai` meaning exactly that,
 * was renamed when someone pointed out a reader cannot decode a convention nobody told them and a
 * screen reader cannot see a font at all — and then went on doing the same job under a name that
 * described the register instead. What actually carries provenance is explicit and now leads the
 * answer rather than trailing it: `.advice-model`, the byline naming provider, model and latency
 * above the first sentence; the eyebrow over each block; the caption reading "measured from N
 * readings — not from the model"; and the figures the model was given, on request.
 *
 * Register is structural now — a tinted answer card, a byline, an eyebrow, a rule down the side of
 * a forecast. Every one of those survives a host with no fonts installed, and every one of them is
 * in the accessibility tree. `measure.test.ts` asserts both halves: that model prose is sans, and
 * that the structure carrying the register is still there.
 *
 * The +1px that used to sit on every prose rule went with the serif that needed it. It was an
 * optical correction for a book face setting small against a grotesque, not a second opinion about
 * reading size, so `--text-md` is the whole answer now.
 */

/** A title: the product naming what you are looking at. Scanned, so sans. */
.type-title {
  font-family: var(--font-sans);
  letter-spacing: -0.01em;
}

/** The sentence under a title. It finishes the title's thought, and is scanned with it. */
.type-lede {
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
}

/** A paragraph a model wrote, wherever it appears outside the advice dialog. */
.ai-prose {
  font-family: var(--font-sans);
  font-size: var(--text-md);
  line-height: var(--leading-relaxed);
}

/*
 * Nothing in here has a width. The dialog is the column.
 *
 * Three attempts got to this. First the measure was on each paragraph, which gave the dialog three
 * different right edges — the finding ran the full width of the panel, the explanation stopped at
 * 594px, and the "if nothing changes" box drew its border at the panel edge around text that stopped
 * two-thirds of the way across. Then the measure moved to the column, which fixed the edges and left
 * a 115px dead band down the right with the scrollbar stranded past it.
 *
 * The band was the tell that the cap was doing the panel's job. `--size-advice-dialog` is the
 * measure plus the dialog's own chrome, so the body *is* the column and every element in it fills
 * that column because it has nothing else to fill. One edge, no band, and no width to keep in sync.
 */
.advice {
  gap: var(--space-5);
}
.advice-body {
  gap: var(--space-5);
}

/* A block per question the reader is actually asking, so the eyebrow above each one has something
   to name. `gap` inside, never margins on the children — collapsing margins between a heading and
   the paragraph under it is what made the old single-block version look arbitrarily spaced. */
.advice-section,
.advice-answer,
.advice-consequence {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-2);
  min-width: 0;
}

/* An empty runbook said as a sentence, in the prose register of the cause above it. */
.advice-nofix { margin: 0; color: var(--color-text-muted); }

/* ---- who is answering, said before the answer -------------------------------------------------
 *
 * The provider, the model and how long it took were in the footer and nowhere else, which put the
 * one fact that changes how a paragraph should be weighed *after* the paragraph. A reader deciding
 * whether to act on "restart the etcd pod" wants to know it came from a 7B model running on their
 * own hardware before they read the instruction, not after.
 *
 * It is also the register signal that replaced the serif. Same job the face was doing — "this is
 * generated" — done in words, in the accessibility tree, on every host regardless of what fonts it
 * has. The footer keeps the caveats that belong next to the controls: that nothing here runs, and
 * where the data went.
 */
.advice-model {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--border-hairline);
  font-size: var(--text-xs);
  color: var(--text-muted);
}
.advice-model-mark {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 2px var(--space-2);
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--color-primary) 14%, transparent);
  color: var(--color-primary);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
/* Mono, because a model name is an identifier the reader may have to type into an assignment page —
   `llama3.1:8b-instruct-q4_K_M` is not prose and setting it as prose makes it unreadable. */
.advice-model-name {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-primary);
}
/* Pushed to the end of the row, because where the data went is the fact someone scans for. */
.advice-model-egress {
  margin-left: auto;
}

/*
 * The finding, in the one tinted box in the dialog.
 *
 * Everything else here is either neutral ground (`--bg-sunken`: the measured facts, a runbook step)
 * or plain prose on the dialog's own surface. This is the sentence the reader clicked for, and
 * before it had a box it was distinguished only by being 18px — which in a dialog that also has an
 * 18px-adjacent measured block was not enough to find at a glance.
 *
 * `color-mix` into `--bg-surface` rather than a fixed tint, so it is a wash of the accent over
 * whatever ground the theme puts behind it and needs no second value in the light block. Gradients
 * are banned outside the skeleton (`tokens.test.ts` checks); this is a flat colour computed from two
 * tokens, which is the sanctioned way to tint.
 */
/*
 * A heading inside a panel, above a block the panel did not open with.
 *
 * The map's inspector needs one — the object's fields, then "What is happening" over the model's
 * answer — and there was nothing to reach for: `.panel-note` is the small print beneath a thing, and
 * a `<Card>` title is the panel itself. Sized as a label rather than as a heading, because it labels
 * one region of a panel and must not compete with the panel's own title.
 */
.panel-heading {
  margin: var(--space-5) 0 var(--space-2);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.advice-answer {
  padding: var(--space-4) var(--space-5);
  border: 1px solid color-mix(in srgb, var(--color-primary) 28%, var(--border-hairline));
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--color-primary) 11%, var(--bg-surface));
}
/*
 * Dark needs more of the accent than light does to read as the same tint.
 *
 * Measured on both: 7% of `--accent` over `#FFFFFF` is a plainly blue card, and the same 7% over
 * `#131417` is a box you have to hunt for — the mix is linear, the eye's response to it against a
 * near-black ground is not. So the wash is stated once per theme rather than once, which is the same
 * reason the palette itself is.
 */
:root[data-theme="light"] .advice-answer {
  background: color-mix(in srgb, var(--color-primary) 6%, var(--bg-surface));
}

/*
 * The finding, set as the one line in the dialog that is bigger than body text.
 *
 * It is the answer to the question that was clicked, and everything else is support for it. Without
 * a step up in size the reader arrives at a wall of `--text-sm` and has to find the sentence that
 * matters.
 */
/*
 * The finding, at `--text-xl` — four steps up the scale from the body, not one.
 *
 * It was `--text-lg`, which is 15px against a 14px paragraph. A single pixel is not a hierarchy: the
 * reader arrived at a uniformly grey block and had to *read* it to find the sentence that answers
 * the question they clicked. This is the answer; everything under it is support.
 */
.advice-lede {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--text-xl);
  line-height: var(--leading-heading);
  font-weight: 600;
  letter-spacing: -0.01em;
  text-wrap: balance;
  /*
   * `break-word`, not `anywhere` — the difference matters only here and it is very visible.
   *
   * `anywhere` lets the line breaker split a word whenever that improves the fit, and `text-wrap:
   * balance` above is exactly a request to improve the fit. Together they cut a pod name in half:
   * "Pod apicast-operator-controller-manager-" / "v2-78689fdd64-gxpmp is healthy". `break-word` only
   * breaks a word that cannot fit on a line of its own, which no object name at this size does.
   */
  overflow-wrap: break-word;
}

/*
 * `--text-md`, not `--text-sm`.
 *
 * 12px is the size of a cell in a dense table, where the job is scanning. This is now several
 * paragraphs of explanation — the thing the dialog exists to deliver — and it was being set smaller
 * than the label above it.
 *
 * `anywhere`, because the nouns here are Kubernetes object names: a 63-character pod name is one
 * unbreakable word and it is in almost every answer. `pretty` because the last line of a paragraph
 * landing as one orphaned word is most of what makes a rag look accidental.
 */
.advice-p {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--text-md);
  line-height: var(--leading-relaxed);
  overflow-wrap: anywhere;
  text-wrap: pretty;
}

/*
 * "If nothing changes", set apart.
 *
 * A warning-toned rule rather than a `Notice`: this is the model's reading of a trajectory, not the
 * product's judgement, and a full alert box would give a generated forecast the same visual weight
 * as a measured fault. The rule marks it as a different kind of statement without promoting it.
 */
.advice-consequence {
  padding: var(--space-3) var(--space-4);
  border-left: 2px solid var(--severity-warning-fill);
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
}

/* ---- the follow-up thread ---------------------------------------------------------------------
 *
 * A ledger, not a chat transcript. Mirrored bubbles would give the question and the answer equal
 * weight and halve the width available to the half that carries commands — and the reader is not
 * having a conversation with a person, they are reading an answer and noting what they asked to get
 * it. So: the question is marked, the answer is prose at full width.
 */

.chat {
  gap: var(--space-5);
  padding-top: var(--space-5);
  border-top: 1px solid var(--border-hairline);
}
.chat-turn {
  gap: var(--space-3);
}
.chat-reply {
  gap: var(--space-3);
}

.chat-ask {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-1);
  padding: var(--space-3) var(--space-4);
  border-left: 2px solid var(--color-primary);
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
  min-width: 0;
}
.chat-said {
  font-size: var(--text-md);
  line-height: var(--leading-relaxed);
  overflow-wrap: anywhere;
}

.advice-list {
  display: grid;
  gap: var(--space-1);
  margin: 0;
  padding-left: var(--space-5);
  font-family: var(--font-sans);
  font-size: var(--text-md);
  line-height: var(--leading-relaxed);
  overflow-wrap: anywhere;
}

/* The composer occupies the whole footer, so it has to opt out of the footer's own right alignment. */
/*
 * `width: 100%` so the composer fills the footer rather than shrinking to its content.
 *
 * The modal footer is `justify-content: flex-end`, so without this the field collapses to the right
 * and its left edge stops lining up with the paragraph above it. It needs no maximum: the footer is
 * as wide as the body, and the body is the column.
 */
/*
 * The controls, docked to the bottom of the dialog's scrolling body.
 *
 * Sticky inside the body rather than pinned in the modal's footer, because the footer is outside the
 * scroll container and is therefore wider than it by exactly one scrollbar — and that width cannot
 * be known from here. Measured in this product: a scroll container inside the dialog reports 15px
 * while an identical one in the document body reports the 10px `::-webkit-scrollbar` asks for, and
 * `scrollbar-gutter: stable` reserves the platform's width rather than the styled one. Every
 * arithmetic fix left a different few pixels of drift.
 *
 * Here there is no arithmetic. The dock is laid out in the same box as the paragraphs above it, so
 * its edges are theirs.
 *
 * The negative side margins let the bar's ground and its top rule span the body's full width, while
 * the matching padding keeps its contents in the column. `bottom` cancels the body's own bottom
 * padding so the bar rests on the container's edge rather than floating 20px above it.
 */
.chat-dock {
  position: sticky;
  bottom: calc(var(--space-5) * -1);
  z-index: 1;
  margin: var(--space-2) calc(var(--space-5) * -1) calc(var(--space-5) * -1);
  padding: var(--space-4) var(--space-5) var(--space-5);
  /* The dialog's own ground, not the card's. They are the same colour in dark and light, but the
     glass theme's cards are translucent: painted with the card colour, the dock was a lighter band
     across the foot of an opaque dialog, with the answer showing through it as it scrolled under. */
  background: var(--overlay-surface);
  border-top: 1px solid var(--color-border);
}

.chat-compose {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-2);
  width: 100%;
  min-width: 0;
}
/*
 * The composer: one bar, field and send button sharing a frame.
 *
 * They were two controls side by side, with hint text and two more buttons on a rule underneath —
 * four horizontal edges that did not line up and a primary button floating beside a box. Fully
 * rounded rather than a rounded rectangle, and the button round and inside it: that is the shape
 * every chat box a reader has used already has, and a familiar shape is doing work that no label
 * does.
 */
.composer {
  display: grid;
  /* `minmax(0, 1fr)`, not the implicit `auto`. An implicit grid column is
     `minmax(auto, max-content)`, so one unbreakable token pasted into the field would size the track
     to itself and take the whole reading column with it — `scroll.test.ts` holds this line for every
     box in this dialog. */
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: end;
  gap: var(--space-2);
  /* More room on the right than the left looks wrong written down and right on screen: the button is
     a circle sitting where the bar's own curve is closing in, and 8px puts its corner inside that
     curve. The text needs the larger inset on the left for a different reason — a letter starting on
     the curve reads as falling off the edge. */
  padding: var(--space-2) var(--space-3) var(--space-2) var(--space-4);
  background: var(--color-background);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-full);
}

/*
 * The frame takes the focus ring for what is inside it.
 *
 * `outline` rather than a border swap or a shadow: a border that thickens on focus moves every
 * pixel inside the box by one, and this stylesheet keeps box-shadows to overlays only — `tokens.test`
 * asserts it. An outline with a negative offset paints on the frame's own edge and costs no layout.
 */
.composer:focus-within {
  border-color: var(--accent);
  outline: 1px solid var(--accent);
  outline-offset: -1px;
}

/*
 * The field and the button are styled from the call site, not from here — see the note there.
 *
 * `Textarea` and `Button` carry `controlStyle` as an **inline** style object, and inline beats every
 * class in this sheet. These two rules existed and did nothing: the field kept its own border inside
 * the bar's and the button kept its rectangle. The classes stay as the hook the markup names and as
 * the place this is written down; anything that has to override a primitive goes through `style`.
 */
.composer-input,
.composer-send {
  /* Intentionally no visual declarations. See above. */
  min-width: 0;
}

/* Under the bar: what Enter does, and the way to throw the thread away. Neither belongs on the send
   button's row. */
.composer-foot {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 0 var(--space-2);
}

.composer-hint {
  font-size: var(--text-xs);
  color: var(--text-muted);
  min-width: 0;
}

/* Pushed right rather than the hint being told to grow, so a wrapped hint does not drag the button
   down a line with it. */
.composer-foot > :last-child {
  margin-left: auto;
  flex-shrink: 0;
}

@media (max-width: 599px) {
  /* The hint is the first thing to go on a phone: it explains a keyboard the reader does not have. */
  .composer-hint {
    display: none;
  }
}

/*
 * A break between boards.
 *
 * The capacity board answers "what is happening now" and the panels under it answer two other
 * questions entirely. Without a heading they read as more of the same page scrolled further, which
 * is how a forecast gets mistaken for a measurement.
 */
/* A section heading divides a board into parts. It is scanned on the way to a panel, so it takes the
   same face as the panel titles under it — a serif heading over sans titles inverted the hierarchy. */
.board-section {
  margin: var(--space-8) 0 var(--space-4);
  font-family: var(--font-sans);
  letter-spacing: -0.01em;
  font-size: var(--text-xl);
  font-weight: 600;
  line-height: var(--leading-heading);
}
.board-section:first-of-type {
  margin-top: var(--space-6);
}

/* ---- cluster utilization: the traffic-flow canvas -------------------------------------------- */

/*
 * An instrument panel, not a document panel.
 *
 * What this replaced was two progress bars each over its own mini chart, each chart plotting usage
 * and capacity in the resource's own unit on one shared linear axis. On a live cluster that is 5741m
 * against 84 cores, so the series anybody opened the panel for was a flat line welded to the floor —
 * a cluster at 7% and a collector that had stopped reporting drew the same picture. Both tracks are
 * percent-of-capacity now, which is what lets them share one canvas and lets capacity be a single
 * dashed rule at 100 instead of a second flat curve per resource.
 *
 * Four rules here are load-bearing rather than decorative:
 *
 *   - **Dark in both themes.** `--flow-*` below are local properties on `.flow-canvas`, not theme
 *     tokens, and every colour inside the canvas is derived from them rather than from the page. The
 *     wave, its glow and its fade are built for a dark ground; on the light theme's near-white the
 *     same drawing washes out to a grey smear, which is precisely the state this panel was rebuilt
 *     out of. Same argument the bastion terminal makes for staying dark on a light page.
 *   - **No gradient in this file.** The wash behind the plot and the fade under each wave are SVG
 *     `<linearGradient>`s built in `console/flow.tsx`; blacklist 6 allows a gradient in exactly the
 *     one place and `tokens.test.ts` enforces it by rejecting `gradient(` anywhere in this
 *     stylesheet. The `stop-color` rules below are how a token reaches an SVG stop without one.
 *   - **No shadow.** Blacklist 2: separation on a data surface is the hairline's job. The depth here
 *     is a translucent ground plus `backdrop-filter` on the chips; `drop-shadow` appears only as a
 *     stroke glow *inside* the SVG, which is part of the drawing rather than elevation under a card.
 *   - **Hue is identity, load is the exception.** `.is-lane-a` / `.is-lane-b` are the two hues the
 *     palette already holds for a two-series chart; `.is-loaded` / `.is-saturated` override them on
 *     the same thresholds `utilisationTone` uses, so this panel and the badge beside it cannot
 *     disagree about one cluster. The first cut encoded load in the hue *instead* of identity, which
 *     made the state a healthy cluster is in essentially always — two identical grey lines — the
 *     least legible reading on the page.
 */

.flow {
  display: grid;
  gap: var(--space-2);
  min-width: 0;
}

.flow-canvas {
  /* The instrument's own ground and inks. Named outside the themed prefixes on purpose: these are
     local to this surface and must NOT follow the page theme, which is the whole point of them. */
  --flow-ground: #0B0D12;
  --flow-ink: #E8EAF0;
  --flow-ink-dim: #7C8496;
  --flow-hairline: rgba(255, 255, 255, 0.1);
  --flow-lane-a: var(--chart-5);
  --flow-lane-b: var(--chart-6);

  position: relative;
  border: 1px solid var(--flow-hairline);
  border-radius: var(--radius-md);
  background: var(--flow-ground);
  overflow: hidden;
  min-width: 0;
  color: var(--flow-ink);
}

/*
 * Stretched horizontally only.
 *
 * `preserveAspectRatio="none"` with `non-scaling-stroke` on every stroke, so the wave fills a
 * resizable dashboard tile at any width without the line weight changing with it. Vertically one
 * viewBox unit stays one CSS pixel, which is what lets the drawing reason about the room the chips
 * and the status strip take out of the top and bottom.
 */
.flow-svg {
  display: block;
  width: 100%;
  height: 240px;
}

/* The wash: the reference's teal-to-violet ground, at an opacity that is felt rather than seen.
   Painted, never stroked — a stroked full-bleed rect draws a hairline over the canvas' own border. */
.flow-wash {
  stroke: none;
}
.flow-wash-a {
  stop-color: var(--flow-lane-a);
  stop-opacity: 0.14;
}
.flow-wash-mid {
  stop-color: var(--flow-ground);
  stop-opacity: 0;
}
.flow-wash-b {
  stop-color: var(--flow-lane-b);
  stop-opacity: 0.16;
}

.flow-grid {
  stroke: var(--flow-hairline);
  stroke-width: 1;
  opacity: 0.5;
}

/* Razor-thin and dashed: capacity is context for the waves, not a fourth thing competing with them. */
.flow-baseline {
  stroke: var(--flow-ink-dim);
  stroke-width: 1;
  stroke-dasharray: 3 6;
  opacity: 0.65;
}

.flow-fill {
  /* Painted, never stroked: a stroked fill draws a second, offset copy of the wave along the closing
     edge that runs across the floor. */
  stroke: none;
}

.flow-line {
  fill: none;
  stroke: currentColor;
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  /* The glow. Inside the SVG and on the stroke itself, so it is part of the drawing — not elevation
     under a panel, which is what blacklist 2 refuses. */
  filter: drop-shadow(0 0 6px currentColor);
}

/* A reading, not a decoration: every marker sits on a sample that was actually reported. */
.flow-dot {
  fill: var(--flow-ground);
  stroke: currentColor;
  stroke-width: 2.5;
}
.flow-head {
  fill: currentColor;
  stroke: var(--flow-ground);
  stroke-width: 2;
  filter: drop-shadow(0 0 6px currentColor);
}

/* --- the two identity hues, and the two overrides ------------------------------------------- */

.flow-track.is-lane-a {
  color: var(--flow-lane-a);
}
.flow-track.is-lane-b {
  color: var(--flow-lane-b);
}
.flow-track.is-loaded {
  color: var(--warn);
}
.flow-track.is-saturated {
  color: var(--critical);
}

.flow-stop-head.is-lane-a {
  stop-color: var(--chart-5);
  stop-opacity: 0.34;
}
.flow-stop-head.is-lane-b {
  stop-color: var(--chart-6);
  stop-opacity: 0.34;
}
.flow-stop-head.is-loaded {
  stop-color: var(--warn);
  stop-opacity: 0.36;
}
.flow-stop-head.is-saturated {
  stop-color: var(--critical);
  stop-opacity: 0.36;
}
/*
 * The tail fades to the instrument's own ground at zero alpha rather than to `transparent`:
 * interpolating to the keyword goes through transparent *black* in some engines, which draws a haze
 * of the wrong colour over the wash behind it.
 */
.flow-stop-tail {
  stop-color: var(--flow-ground);
  stop-opacity: 0;
}

/* --- the chips, over the canvas -------------------------------------------------------------- */

.flow-chips {
  position: absolute;
  top: var(--space-3);
  left: var(--space-3);
  right: var(--space-3);
  z-index: 1;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  min-width: 0;
  pointer-events: none;
}

.flow-chip {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-full);
  border: 1px solid var(--flow-hairline);
  background: rgba(255, 255, 255, 0.05);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  line-height: 1.5;
  white-space: nowrap;
  color: currentColor;
}
.flow-chip-key {
  color: var(--flow-ink-dim);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
/* Tabular, because these two numbers exist to be compared with each other and with themselves a
   minute later — proportional digits change width as they change value. */
.flow-chip-value {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.flow-chip.is-lane-a {
  color: var(--flow-lane-a);
}
.flow-chip.is-lane-b {
  color: var(--flow-lane-b);
}
.flow-chip.is-loaded {
  color: var(--warn-text);
}
.flow-chip.is-saturated {
  color: var(--critical-text);
}

/*
 * The window picker, on the instrument.
 *
 * `pointer-events` is restored here alone: `.flow-chips` is an inert overlay so a pointer crossing
 * the canvas is not swallowed by a strip that only prints numbers, and the one thing in it that is
 * a control has to opt back in.
 */
.flow-windows {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 2px;
  border-radius: var(--radius-full);
  border: 1px solid var(--flow-hairline);
  background: rgba(255, 255, 255, 0.05);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  pointer-events: auto;
}

.flow-window {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  border: none;
  background: transparent;
  color: var(--flow-ink-dim);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  padding: 3px var(--space-2);
  border-radius: var(--radius-full);
  cursor: pointer;
  transition: color var(--motion-fast) var(--ease-out), background var(--motion-fast) var(--ease-out);
}
.flow-window:hover {
  color: var(--flow-ink);
}
/* The selected one is a filled pill, not merely brighter text: on an instrument ground a colour
   difference alone is the weakest signal available, and this is the control that says what span the
   drawing beside it covers. */
.flow-window.is-on {
  background: rgba(255, 255, 255, 0.1);
  color: var(--flow-ink);
}
.flow-window:focus-visible {
  outline: 2px solid var(--flow-lane-a);
  outline-offset: 1px;
}
@media (prefers-reduced-motion: reduce) {
  .flow-window {
    transition: none;
  }
}

/*
 * Also auto-margined, so the row reads left / centre-right / right rather than as one right-hand
 * clump — and, when the tile is narrow enough that the row wraps, the verdict lands at the right of
 * its own line instead of under the CPU chip.
 */
.flow-chip-verdict {
  margin-left: auto;
  letter-spacing: 0.1em;
  color: var(--flow-ink-dim);
}
.flow-chip-verdict.is-loaded {
  color: var(--warn-text);
  border-color: var(--warn-text);
}
.flow-chip-verdict.is-saturated {
  color: var(--critical-text);
  border-color: var(--critical-text);
}

/* --- the status strip ------------------------------------------------------------------------ */

.flow-status {
  position: absolute;
  left: var(--space-4);
  right: var(--space-4);
  bottom: var(--space-3);
  z-index: 1;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
  flex-wrap: wrap;
  min-width: 0;
  pointer-events: none;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
}
.flow-fact {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-1);
  min-width: 0;
}
.flow-fact-label {
  color: var(--flow-ink-dim);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.flow-fact-value {
  color: var(--flow-ink);
  font-variant-numeric: tabular-nums;
}

.flow-nodata {
  margin: 0;
  padding: var(--space-12) var(--space-4);
  text-align: center;
  color: var(--flow-ink-dim);
  font-size: var(--text-sm);
}

/* Outside the instrument, in the page's own voice: this is the window the panel was given, which is
   a fact about the query rather than a reading off the cluster. */
.flow-axis {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  font-size: var(--text-xs);
  color: var(--text-muted);
  min-width: 0;
}
.flow-axis-ceiling {
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/*
 * Narrow tile: the canvas keeps most of its height, because a wave squeezed under ~120px stops being
 * a shape and becomes a texture. The status strip is what goes — it qualifies the drawing, and a
 * drawing nobody can read is not worth qualifying.
 */
@media (max-width: 720px) {
  .flow-svg {
    height: 200px;
  }
  .flow-status {
    display: none;
  }
}

/* ---- numeric table cells --------------------------------------------------------------------- */

/*
 * A measured figure in a table.
 *
 * Mono and tabular so a column of them lines up digit for digit — the only way a column of
 * percentages can be compared without reading each one. `Td numeric` handles the alignment; this
 * handles the face, and it is a class because it had become three copies of the same inline style.
 */
.num-cell {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* The scheduler's commitment sits between usage and the ceiling in importance as well as in value. */
.num-cell-muted {
  color: var(--text-secondary);
}
.num-cell-over {
  color: var(--severity-warning-fg);
  font-weight: 600;
}

/* ---- dashboard switcher ---------------------------------------------------------------------- */

/*
 * The monitoring boards, in the header of each of them.
 *
 * Not the sidebar: these are all one part of the product, and putting six of them beside Users and
 * Audit would say they are the same kind of destination. A segmented row in the page header is what
 * the reference tools use a dashboard folder for, with one fewer click.
 *
 * One line that scrolls sideways when it does not fit, never a wrap — see `monitoring/links.tsx`.
 * `max-width: 100%` and `min-width: 0` let it shrink to the header row instead of widening it;
 * `position: relative` makes it the offset parent the current board is centred against. The
 * scrollbar is hidden because the fade says the same thing in less space, and a trackpad, shift +
 * wheel and Tab all still scroll it.
 */
.dash-links {
  position: relative;
  display: flex;
  flex-wrap: nowrap;
  gap: 2px;
  padding: 2px;
  max-width: 100%;
  min-width: 0;
  overflow-x: auto;
  overflow-y: hidden;
  /* Reaching either end must not hand the wheel to the page and slide the layout sideways. */
  overscroll-behavior-x: contain;
  border-radius: var(--radius-md);
  background: var(--bg-sunken);
  border: 1px solid var(--border-hairline);
}
/* The WebKit/Blink half of hiding the bar; Firefox's `scrollbar-width: none` is in the one
   `@supports not selector(::-webkit-scrollbar)` block near the top of this file. */
.dash-links::-webkit-scrollbar {
  display: none;
}
/* An edge with more beyond it fades out, so a cut-off board reads as "more this way". */
.dash-links[data-more="end"] {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 48px), transparent);
          mask-image: linear-gradient(to right, #000 calc(100% - 48px), transparent);
}
.dash-links[data-more="start"] {
  -webkit-mask-image: linear-gradient(to left, #000 calc(100% - 48px), transparent);
          mask-image: linear-gradient(to left, #000 calc(100% - 48px), transparent);
}
.dash-links[data-more="both"] {
  -webkit-mask-image: linear-gradient(to right, transparent, #000 48px, #000 calc(100% - 48px), transparent);
          mask-image: linear-gradient(to right, transparent, #000 48px, #000 calc(100% - 48px), transparent);
}

.dash-link {
  padding: 4px var(--space-3);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--text-secondary);
  text-decoration: none;
  white-space: nowrap;
}
.dash-link:hover {
  color: var(--text-primary);
  background: var(--bg-hover);
}
/* The current board, marked by weight and ground as well as colour — `aria-current` carries it for
   anyone not reading either. */
.dash-link-on {
  background: var(--bg-surface);
  color: var(--text-primary);
}
.dash-link:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: -1px;
}

/* ---- daily bars ------------------------------------------------------------------------------ */

/*
 * Alerts per day.
 *
 * Bars rather than a line: these are counts of discrete events, and a line between two daily totals
 * implies values in between that no day had. Bars share the row equally so a thirty-day and a
 * ninety-day window are the same width, and each is at least a pixel wide — a day that fired nothing
 * still occupies its slot, because a sparse series drawn without its empty days puts two alerts a
 * week apart side by side and reads as two consecutive days.
 *
 * **The 140px belongs to the bars, not to the box.** It was the box's height, and the scale is the
 * box's last item, wrapped onto a line of its own — so the two lines shared 140px between them, the
 * scale overflowed the box by its own height, the baseline rule was drawn through the middle of the
 * dates, and the card's `overflow: hidden` sheared off their lower half. The box now sizes to its
 * content, the bars are 140px each, and the baseline is the scale's top border: it sits on the foot
 * of every bar because nothing else is between them (`row-gap: 0`).
 */
.daybars {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  -moz-column-gap: 2px;
       column-gap: 2px;
  row-gap: 0;
}

.daybar {
  flex: 1 1 0;
  min-width: 2px;
  height: 140px;
  display: flex;
  align-items: flex-end;
}

/*
 * No track fill.
 *
 * A sunken column behind each day is the full height of the chart, and on a window where most days
 * are quiet it reads as thirty full-height bars — the exact opposite of what the data says. The
 * baseline rule under the row is enough to show where the days are.
 */
.daybar-stack {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  width: 100%;
  height: 100%;
  border-radius: 2px 2px 0 0;
  overflow: hidden;
}

.daybar-seg {
  display: block;
  width: 100%;
  /* A day with one alert out of a peak of two hundred rounds to nothing; a floor keeps it visible,
     because "one critical alert fired" is not the same fact as "none did". */
  min-height: 0;
}
.daybar-seg[style*="height: 0%"] {
  min-height: 0;
}
.daybar-critical {
  background: var(--severity-critical-fill);
}
.daybar-warning {
  background: var(--severity-warning-fill);
}
.daybar-info {
  background: var(--severity-info-fill);
}

.daybars-scale {
  flex: 1 0 100%;
  display: flex;
  justify-content: space-between;
  padding-top: var(--space-2);
  border-top: 1px solid var(--border-hairline);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

/*
 * A wrapping set of badges inside a table cell.
 *
 * `Td` does not constrain its content, but a flex row of eight badges in a column sized by its
 * neighbours renders with the last one cut in half — which reads as a badge whose text was
 * truncated rather than as a cell that ran out of room. Wrapping with a floor on the cell keeps
 * every badge whole.
 */
.badge-wrap {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  min-width: 14rem;
}

/* ---- the certificate vault --------------------------------------------------------------------
 *
 * Four shapes, all of which exist elsewhere in the product in some form and none of which had a
 * class that fitted: a definition list of certificate facts, a stack of delivery options one of
 * which is a refusal, a form laid out in rows, and a panel of rendered YAML.
 */

/*
 * A certificate's facts, as a two-column definition list.
 *
 * A table would be wrong: these are labelled values rather than rows of a set, and a <dl> is what
 * a screen reader announces as such. The label column is fixed so the values line up down the
 * panel — a certificate page is read by scanning the values, not the labels.
 */
.fact-list {
  display: grid;
  grid-template-columns: minmax(7rem, max-content) 1fr;
  gap: var(--space-2) var(--space-3);
  margin: 0;
  font-size: var(--text-sm);
}
.fact-list dt {
  color: var(--text-secondary);
}
.fact-list dd {
  margin: 0;
  min-width: 0;
  /* Fingerprints and DNs are long, unbroken and must not push the panel wider than the board. */
  overflow-wrap: anywhere;
}

/* One delivery method, including the one that is listed in order to be refused. */
.option-row {
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  padding: var(--space-3);
}
.option-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-1);
}
.option-body {
  margin: 0;
  font-size: var(--text-sm);
  line-height: var(--leading-relaxed);
  color: var(--text-secondary);
}
/*
 * The reason a method is not offered.
 *
 * A left rule rather than a warning ground: this is an explanation, not an alarm, and tinting the
 * whole card would make the method someone cannot pick the loudest thing on the panel.
 */
.option-refusal {
  margin-top: var(--space-2);
  padding-left: var(--space-3);
  border-left: 2px solid var(--border-strong);
}

.stack-3 {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* Fields side by side, collapsing to one column where they would be too narrow to type into. */
.form-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: var(--space-3);
  align-items: start;
}
.form-actions {
  display: flex;
  gap: var(--space-2);
  align-items: center;
}

/*
 * The rendered manifests for one mount.
 *
 * A nested surface, so no shadow — the card around it already carries one, and a second inside it
 * reads as a floating panel rather than a section of the card.
 */
.manifest-panel {
  margin-top: var(--space-3);
  padding: var(--space-3);
  background: var(--bg-sunken);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
}
.manifest-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  font-size: var(--text-sm);
}

/* A second line inside a table cell: the qualifier under the value it qualifies. */
.cell-sub {
  margin-top: 2px;
  font-size: var(--text-xs);
  color: var(--text-secondary);
  overflow-wrap: anywhere;
}

.text-muted {
  color: var(--text-secondary);
}

/*
 * An inline link inside prose or a table.
 *
 * Underlined, not colour alone — WCAG 1.4.1, and the accent against body text in a dense table does
 * not carry on its own for anyone with a colour vision deficiency.
 */
.link {
  color: var(--color-primary-solid);
  text-decoration: underline;
  text-underline-offset: 2px;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}
.link:hover {
  color: var(--hover-accent-text);
  text-decoration-thickness: 2px;
  transition: color var(--motion-fast) var(--ease-out);
}

/* ---- what the AI is watching -----------------------------------------------------------------
 *
 * The selection rules, set as a list rather than a paragraph. Each one is a bound on what leaves the
 * customer's boundary, and a reader checking whether their data is being sent somewhere needs to be
 * able to find the specific bound they care about — which is a scan, not a read.
 */
.watch-rules {
  margin: 0 0 var(--space-4);
  padding-left: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  font-size: var(--text-base);
  line-height: var(--leading-body);
  color: var(--color-text-muted);
}

.watch-rules strong {
  color: var(--color-text);
  font-weight: 500;
}

.watch-budget {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding: var(--space-3);
  background: var(--bg-sunken);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/*
 * Who wrote a narration, under the narration.
 *
 * Present on every model-written paragraph in the product for the same reason the trend dialog
 * names its provider: a reader has to be able to tell which sentences a model composed and which
 * this product is asserting, and the answer must not depend on noticing a typeface.
 */
.watch-attrib {
  margin-top: var(--space-2);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  font-family: var(--font-mono);
}

/* ============================================================================================
 * Foresight — the Observe → Understand → Predict → Simulate → Act board.
 *
 * Every value below resolves to an existing token. No hex is written here: `tokens.test.ts` reads
 * this file and a hand-tuned colour is a test failure, correctly — the palette is generated from
 * `@platform/config/tokens` and this board is not an exception to that.
 *
 * ## On motion
 *
 * The service map animates, and there is no `animation` property anywhere in this section. The
 * particles are positioned per frame from `requestAnimationFrame` in `flow-map.tsx`, because their
 * speed is a *measurement* — it changes on every poll, and a CSS animation whose duration is
 * rewritten restarts, jumping every particle back to the start of its path at exactly the moment
 * traffic changed. So the file's "nothing auto-animates in a loop" rule is untouched: this board
 * adds no looping declaration of its own.
 *
 * That sentence is deliberately phrased without the CSS keyword. Naming it in prose would make this
 * comment match a source scan for looping animations — the same hole trap 13 describes, where an
 * assertion passes on the strength of a comment that mentions the thing it is looking for.
 * ========================================================================================== */

/* ---------------------------------------------------------------------------- stage rhythm */

.foresight-stage { margin-top: var(--space-12); }
.foresight-stage:first-of-type { margin-top: var(--space-6); }

.foresight-stage-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--color-border);
}

/*
 * The numbering encodes a real sequence — you cannot act before you have predicted — so it is
 * information rather than ornament, which is the only reason it is allowed to be here.
 */
.foresight-stage-num {
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.14em;
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
  color: var(--color-primary-text);
  background: var(--color-primary-soft);
  border: 1px solid var(--color-primary-soft);
}

.foresight-stage-title {
  margin: 0;
  font-size: var(--text-xl);
  font-weight: 600;
  letter-spacing: -0.01em;
}

.foresight-stage-what {
  margin-left: auto;
  text-align: right;
  color: var(--color-text-subtle);
  font-size: var(--text-sm);
}

/* ---------------------------------------------------------------------------- tiles */

.foresight-tiles {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}

/* A card is a shadow AND a hairline, together — the rule this file opens with. */
.foresight-tile {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
}

.foresight-tile-label {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-text-subtle);
}

.foresight-tile-value {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-2xl);
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.01em;
}

.foresight-tile-unit {
  font-size: var(--text-sm);
  font-weight: 400;
  color: var(--color-text-subtle);
}

.foresight-tile-foot { font-size: var(--text-sm); color: var(--color-text-muted); }

/* Tone colours the figure only. The label stays ink, so the tile is readable before it is scanned. */
.foresight-tile.tone-ok .foresight-tile-value { color: var(--color-text); }
.foresight-tile.tone-info .foresight-tile-value { color: var(--color-text); }
.foresight-tile.tone-warn .foresight-tile-value { color: var(--warn-text); }
.foresight-tile.tone-critical .foresight-tile-value { color: var(--critical-text); }

/* ---------------------------------------------------------------------------- observe */

.foresight-observe {
  display: grid;
  grid-template-columns: minmax(0, 2.1fr) minmax(0, 1fr);
  gap: var(--space-4);
  align-items: start;
}

.foresight-facts { display: grid; gap: var(--space-2); margin: 0; }
/*
 * The Diagnose action under the selected service's facts.
 *
 * Full width and separated from the list above it: it is the one thing on this panel that leaves
 * the page, and a control that navigates has to be distinguishable at a glance from the readouts
 * it sits beneath.
 */
.foresight-diagnose {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: var(--space-3);
  width: 100%;
}

.foresight-fact {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}
.foresight-fact dt { color: var(--color-text-muted); font-size: var(--text-sm); }
.foresight-fact dd { margin: 0; font-size: var(--text-base); color: var(--color-text); }

/* ---------------------------------------------------------------------------- service map */

.flow-map { position: relative; }

/* Nested inside a card, so sunken and no lift. Stacking shadows is how a card grid turns to mud. */
.flow-map-svg {
  display: block;
  width: 100%;
  height: auto;
  background: var(--bg-sunken);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}

.flow-edge {
  fill: none;
  stroke: var(--color-border-strong);
  stroke-width: 1.25;
  transition: stroke-width var(--motion-fast) var(--ease-out);
}
.flow-edge:hover { stroke-width: 2.5; }
.flow-edge.sev-warn { stroke: var(--warn); }
.flow-edge.sev-critical { stroke: var(--critical); }
/* Busier than its own baseline. Weight, not colour — colour already means severity here. */
.flow-edge.is-busier { stroke-width: 2.25; }

.flow-particle { pointer-events: none; }
.flow-particle.sev-ok { fill: var(--color-primary-text); }
.flow-particle.sev-warn { fill: var(--warn-text); }
.flow-particle.sev-critical { fill: var(--critical-text); }

.flow-edge-rate {
  font-family: var(--font-mono);
  font-size: 9px;
  fill: var(--color-text-subtle);
}

.flow-node { cursor: pointer; }
.flow-node:focus-visible { outline: none; }
.flow-node:focus-visible .flow-node-box {
  stroke: var(--color-primary-text);
  stroke-width: 2;
}

.flow-node-box {
  fill: var(--color-surface);
  stroke: var(--color-border);
  stroke-width: 1;
  transition: fill var(--motion-fast) var(--ease-out), stroke var(--motion-fast) var(--ease-out);
}
.flow-node:hover .flow-node-box { fill: var(--color-hover); stroke: var(--color-border-strong); }
.flow-node-box.sev-warn { stroke: var(--warn); }
.flow-node-box.sev-critical { stroke: var(--critical); }
.flow-node.is-selected .flow-node-box { stroke: var(--color-primary-text); stroke-width: 1.5; }

.flow-node-name { font-family: var(--font-mono); font-size: 11px; fill: var(--color-text); pointer-events: none; }
.flow-node-meta { font-family: var(--font-mono); font-size: 9.5px; fill: var(--color-text-subtle); pointer-events: none; }
.flow-node-dot.sev-warn { fill: var(--warn); }
.flow-node-dot.sev-critical { fill: var(--critical); }

.flow-readout {
  position: absolute;
  left: var(--space-3);
  bottom: var(--space-3);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: center;
  padding: var(--space-2) var(--space-3);
  background: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  box-shadow: var(--elev-dropdown);
  font-size: var(--text-sm);
}
.flow-readout-path { font-weight: 500; }
.flow-readout-up { color: var(--warn-text); }
.flow-readout-muted { color: var(--color-text-subtle); }

.flow-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
  margin-top: var(--space-3);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.flow-legend-item { display: inline-flex; align-items: center; gap: var(--space-2); }
.flow-legend-spacer { flex: 1 1 auto; }
.flow-legend-note { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-text-subtle); }

.flow-swatch { width: 10px; height: 10px; border-radius: 50%; display: inline-block; }
.flow-swatch.sev-ok { background: var(--ok); }
.flow-swatch.sev-warn { background: var(--warn); }
.flow-swatch.sev-critical { background: var(--critical); }
/*
 * `unknown` needs its own swatch, and it must not be a shade of green.
 *
 * The application flow draws a hop nobody measured every time a Service's Endpoints were not
 * collected. Borrowing the healthy colour for it would put a green dot on the single hop this
 * platform cannot speak for — which is the one place a reader most needs the picture to hesitate.
 */
.flow-swatch.sev-unknown { background: var(--color-border-strong); }

/* ---------------------------------------------------------------- application flow */

/*
 * The flow scrolls inside its own box.
 *
 * A namespace with six Services is wider than any column this sits in, and letting the page body
 * scroll sideways instead takes the namespace picker off the right-hand edge — the one control
 * needed to get back to a narrower namespace.
 */
.appflow-canvas {
  overflow-x: auto;
  /*
   * Contained on x, per the rule in scroll.test.ts: nothing on this surface intends to move
   * sideways, so a flow scrolled to its right-hand edge must not hand the remaining wheel to the
   * page. Without this the reader reaches the last column and the whole board slides instead.
   */
  overscroll-behavior-x: contain;
  margin-top: var(--space-3);
  padding-bottom: var(--space-2);
}
/*
 * Fills the card, then centres — the sizing rule lives here, the ceiling is inline.
 *
 * `max-width: none` with a pixel `width`/`height` on the element was the old pair, and it meant the
 * drawing rendered at exactly its natural size however much room the card had. Four columns come to
 * about 1,060 units in a card well over 1,500px wide, so a third of the card was blank to the right
 * of the picture and the whole thing hugged the left edge.
 *
 * `width: 100%` grows it; the inline `max-width` (natural width x MAX_SCALE) stops it becoming a
 * poster; `margin-inline: auto` centres whatever slack is left. When the flow is *wider* than the
 * card all three are inert — auto margins collapse to zero and `.appflow-canvas` scrolls, which is
 * the behaviour that box was built for.
 */
.appflow-svg {
  display: block;
  width: 100%;
  height: auto;
}

/*
 * Traffic in motion, at the rate the hop is actually receiving.
 *
 * Same contract as `.flow-particle` on the service map: an edge with no measurement gets no particle
 * at all, so stillness means "nothing measured here" and never "nothing happening". The colour is
 * the accent rather than a severity, because a particle reports throughput and severity is already
 * carried by the stroke it runs along.
 */
.appflow-particle {
  fill: var(--accent);
  pointer-events: none;
}

/* A carrying edge reads slightly stronger than a declared-but-unmeasured one, so the picture still
   distinguishes them with motion switched off. */
.appflow-edge.is-live {
  stroke-width: 1.75;
}

/*
 * Direction, which an undirected curve simply did not carry.
 *
 * The picture is called a flow and every line in it was a plain grey arc — readable as "these two
 * things are related", not as "a request goes this way". On a diagram whose entire subject is the
 * path a request takes, that is the one thing the lines had to say.
 *
 * A marker per severity, filled explicitly. `context-stroke` would let one marker inherit each
 * path's colour and is still not safe to depend on; where it is unsupported the arrow falls back to
 * black, which puts a black head on a red line and reads as a third state.
 */
.appflow-arrowhead { fill: var(--color-border-strong); }
/* `sev-ok` was absent from all three of arrowhead, edge and box, so a healthy hop fell through to the
   neutral base and the legend's green "serving" dot described a colour the map could not draw. See
   the note on .appflow-hop-box.sev-ok below. */
.appflow-arrowhead.sev-ok { fill: var(--ok); }
.appflow-arrowhead.sev-warn { fill: var(--warn); }
.appflow-arrowhead.sev-critical { fill: var(--critical); }
.appflow-arrowhead.sev-unknown { fill: var(--color-border-strong); }

/*
 * The hand-off that does not complete.
 *
 * Dashed *and* marked, on the edge leading into the break point. The dash alone would collide with
 * `sev-unknown`, which is already dashed and means something entirely different — "nobody measured
 * this" against "the request stops here" — so the disc is what separates them, and it is the part
 * that survives the colour channel being removed.
 */
.appflow-edge.is-severed {
  stroke: var(--critical);
  stroke-width: 2;
  stroke-dasharray: 7 5;
}
.appflow-sever-disc { fill: var(--critical); }
.appflow-sever-bar { fill: var(--color-surface); }

/*
 * A path that carries nothing, and the bar where it ends.
 *
 * The third dash pattern on this board, and the three have to stay apart because they say different
 * things: `sev-unknown` (3 3) is "nobody measured this", `is-severed` (7 5, with the disc) is "this
 * is the hop the banner diagnoses", and this one is "nothing is delivered along here". The arrowhead
 * is dropped in the markup rather than hidden here — a head asserts arrival, and the assertion is
 * what is false.
 *
 * One edge is usually both — the hop the banner diagnoses is generally the one with no ready
 * backend — and there the sever has to win, because that edge is the board's answer and this is only
 * a property of it. Stated as its own rule rather than left to source order: these two are equal
 * specificity, so whichever is written second would win, and "written second" is not a reason.
 */
.appflow-edge.is-stopped {
  stroke-dasharray: 1.5 4;
  stroke-linecap: round;
  opacity: 0.5;
}
.appflow-edge.is-severed.is-stopped {
  stroke-dasharray: 7 5;
  opacity: 1;
}
/* Neutral, and the same grey the arrowheads fall back to: this mark means "ends", not "is bad". The
   hop's own box is already carrying how bad it is. */
.appflow-stop-bar { fill: var(--color-border-strong); }

/*
 * Column headings and hop kinds in their own case (2026-10-22, the Request map redesign). Spaced
 * capitals turned "EndpointSlice" into ENDPOINTSLICE — a Kubernetes kind is a proper noun with its
 * own capitals, and the reader types it that way into `oc get`. The headings are numbered in the
 * component instead, because the columns are a sequence and that is what the tracking was standing
 * in for.
 */
.appflow-column-label {
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.01em;
  fill: var(--color-text-subtle);
}

.appflow-edge {
  stroke: var(--color-border-strong);
  stroke-width: 1.25;
  fill: none;
}
.appflow-edge.sev-ok { stroke: var(--ok); }
.appflow-edge.sev-warn { stroke: var(--warn); }
.appflow-edge.sev-critical { stroke: var(--critical); }
/* Dashed, not faint: an unchecked link should read as a different kind of line, not a quieter one. */
.appflow-edge.sev-unknown { stroke-dasharray: 3 3; }

/*
 * The plate's edge — the depth on this board, and the only depth on it.
 *
 * Drawn rather than lit: this is a second rect three units below the face, showing only the sliver
 * the face does not cover, which is how a keycap reads as raised. It is emphatically **not** an
 * elevation. The spec bans shadows for in-page separation, `--elev-rest` is `none` in both themes,
 * and `tokens.test.ts` holds both — so there is no `box-shadow` here and nothing here separates one
 * panel from another. What it does is give the objects in a diagram a surface to sit on, which is a
 * property of the picture and not of the page furniture around it.
 *
 * `color-mix` toward the page ground rather than a fixed dark: in the light theme the edge has to
 * darken and in the dark theme it has to stay under the face without going to black, and mixing
 * each severity with `--bg-page` lands both from one declaration.
 */
.appflow-hop-riser {
  fill: color-mix(in srgb, var(--appflow-edge-tone, var(--color-border-strong)) 34%, var(--bg-page));
  stroke: none;
}
.appflow-hop-riser.sev-ok { --appflow-edge-tone: var(--ok); }
.appflow-hop-riser.sev-warn { --appflow-edge-tone: var(--warn); }
.appflow-hop-riser.sev-critical { --appflow-edge-tone: var(--critical); }
.appflow-hop-riser.sev-unknown { --appflow-edge-tone: var(--color-border-strong); }

.appflow-hop-box {
  fill: var(--color-surface);
  stroke: var(--color-border);
  stroke-width: 1;
}
/*
 * The state the legend names first and the stylesheet never defined.
 *
 * `sev-ok`, `sev-warn`, `sev-critical` and `sev-unknown` are all rendered onto `.appflow-hop-box`,
 * and only three of them had a rule. A healthy hop therefore took the base `--color-border` — the
 * same neutral grey a box with no state at all would take — while the legend beside it showed a
 * green dot captioned "serving". Twenty healthy services drew in the colour of "nothing to say".
 *
 * That is this product's own failure mode in a stylesheet: the state that means *fine* and the state
 * that means *we have no measurement* were the same colour, and the only thing separating them was
 * the dash pattern on `sev-unknown`. `classnames.test.ts` could not catch it either — `sev-ok` does
 * exist, on `.flow-swatch`, so a scan for "is this class defined anywhere" passes while the specific
 * pairing that renders the map is unstyled.
 */
.appflow-hop-box.sev-ok { stroke: var(--ok); }
.appflow-hop-box.sev-warn { stroke: var(--warn); }
.appflow-hop-box.sev-critical { stroke: var(--critical); }
.appflow-hop-box.sev-unknown { stroke: var(--color-border-strong); stroke-dasharray: 3 3; }

.appflow-hop-kind {
  font-family: var(--font-mono);
  font-size: 9.5px;
  fill: var(--color-text-subtle);
}
.appflow-hop-name { font-family: var(--font-mono); font-size: 11.5px; fill: var(--color-text); }
.appflow-hop-meta { font-family: var(--font-mono); font-size: 9.5px; fill: var(--color-text-subtle); }

/*
 * A hop is a control, so it has to look and behave like one.
 *
 * `app-flow.tsx` gives every hop `role="button"`, `tabIndex={0}` and a key handler, and nothing here
 * answered any of that: no pointer, no hover, and — the one that matters — no visible focus on an
 * element a keyboard can land on. A focusable control with no focus state is not a styling gap, it
 * is a control that cannot be operated without a mouse, which is the whole reason the role and the
 * tabindex were added.
 *
 * The hover moves the *fill*, never the stroke. The stroke is the hop's severity and it is the one
 * thing on this picture a reader is asked to trust; a pointer that recoloured it would say the
 * cluster changed. `outline` for focus for the same reason — it sits outside the box and takes
 * nothing from it.
 */
.appflow-hop { cursor: pointer; }
.appflow-hop:focus { outline: none; }
.appflow-hop:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.appflow-hop:hover .appflow-hop-box { fill: var(--color-hover); }

/*
 * The break marker. Heavier than a severity outline on purpose — several hops are unhealthy when an
 * application is down and exactly one of them is the answer, so the two states have to be
 * distinguishable at a glance rather than by comparing shades.
 */
.appflow-hop.is-break .appflow-hop-box { stroke: var(--critical); stroke-width: 2.5; }
.appflow-hop-dot { fill: var(--critical); }

/*
 * The two pickers in the card's action slot.
 *
 * A cluster and a namespace, and the pair only appears together on an account with more than one
 * cluster — so this has to hold one control as tidily as two. `flex-end` keeps them against the
 * card's right edge where a single `Select` used to sit on its own, and wrapping is what stops the
 * longer of two cluster names pushing the namespace picker off the card on a narrow window.
 */
/* ══════════════════════════════════════════════════════════════════════════
   Application flow — the console treatment

   The board was a column of outlined boxes on the card's own surface: correct,
   legible, and reading as a form rather than as an instrument. This gives it
   the visual language of the 3D topology console — a lit ground, plates rather
   than outlines, luminous paths, and colour spent where something is wrong.

   ## It draws on its own ground in both themes

   The same commitment `.depmap-canvas` already makes, and for the same reason:
   a request path is a picture of a running system, and a picture reads on a
   field, not on a sheet of paper. Committing means every fill and every text
   colour below is stated against *this* ground rather than inherited from the
   theme — which is why they are literals here and not tokens. A half-committed
   version, where the ground is dark and the ink still follows the theme, is the
   one that renders black-on-black for a light-theme reader.

   ## What is NOT overridden, and why that matters

   `.appflow-edge` and `.appflow-hop-box` already carry `.sev-*` rules at the
   same specificity as anything scoped here. Setting `stroke` on either from
   this block would land later in the cascade and win — flattening ok, warn,
   critical and unknown into one colour, which is the entire signal the board
   exists to carry. So this sets `fill` and never `stroke`, and every severity
   rule above keeps working untouched.
   ══════════════════════════════════════════════════════════════════════════ */

.appflow-canvas {
  --flow-ground: #060C18;
  --flow-crown: #12233D;
  --flow-plate: #0D1A2E;
  --flow-ink: #DCE7F8;
  --flow-dim: #8298B8;
  --flow-grid: rgba(120, 165, 240, .05);
  --flow-hair: rgba(126, 166, 232, .16);

  border: 1px solid var(--flow-hair);
  border-radius: var(--radius-md);
  padding: var(--space-4) var(--space-4) var(--space-3);
  /* Grid, then the crown of light behind the entry point. Backgrounds rather
     than pseudo-elements because this box scrolls sideways: a positioned
     ::before would either travel with the drawing or need pinning, and a
     background on a scroll container stays put for free. */
  background:
    /* The 72px reference grid stays — a repeating 1px line IS how a grid is drawn, and the lanes
       are positioned against it. The radial crown that sat under it is gone: that one was weather. */
    linear-gradient(to right, var(--flow-grid) 1px, transparent 1px) 0 0 / 72px 72px,
    linear-gradient(to bottom, var(--flow-grid) 1px, transparent 1px) 0 0 / 72px 72px,
    var(--flow-ground);
}

/* Plates, not outlines. The riser under each box is this file's own extrusion
   trick and it keeps doing the work — it is just mixed into the dark ground now
   instead of the page, so it reads as a machined edge rather than a seam. */
.appflow-canvas .appflow-hop-box { fill: var(--flow-plate); }
.appflow-canvas .appflow-hop-riser {
  fill: color-mix(in srgb, var(--appflow-edge-tone, var(--flow-hair)) 30%, #04080F);
}

.appflow-canvas .appflow-hop-name { fill: var(--flow-ink); }
.appflow-canvas .appflow-hop-kind,
.appflow-canvas .appflow-hop-meta,
.appflow-canvas .appflow-column-label { fill: var(--flow-dim); }
/* The column headings read a step brighter than the captions inside the boxes: they are the map's
   key, and they sit alone on the ground with nothing to frame them. */
.appflow-canvas .appflow-column-label { fill: var(--flow-ink); opacity: 0.72; }

/* Traffic reads as light on the wire. */
.appflow-canvas .appflow-particle { fill: #7FD8F2; }

/*
 * Hover, and the marks drawn over the paths — restated against this ground.
 *
 * These are the three paints inside the canvas that still came from the theme,
 * and the first one was a real defect a light-mode reader met immediately:
 * `.appflow-hop:hover .appflow-hop-box` is (0,3,0), because a pseudo-class
 * outranks the scoping class, so `--color-hover` won over the plate fill. In
 * light mode that is near-white — and the ink on it is `--flow-ink`, also near
 * white. The box under the pointer was the only box on the board that could not
 * be read, which is the worst possible one to lose.
 *
 * The lesson generalises past this fix: scoping a surface by an ancestor class
 * does not protect it from any rule that adds a pseudo-class or a second class.
 * Every state a scoped surface can enter has to be restated inside the scope.
 */
.appflow-canvas .appflow-hop:hover .appflow-hop-box,
.appflow-canvas .appflow-hop:focus-visible .appflow-hop-box {
  /* Hover on a lit ground is *more light*, never a different colour. */
  fill: #16273F;
}

/* The bar drawn across a severed path, which exists to make the gap read as a
   cut. It has to be the ground it interrupts — the theme's surface is white in
   light mode, so the break was being marked with a white domino. */
.appflow-canvas .appflow-sever-bar { fill: var(--flow-ground); }

/* Stop bars default to the theme's strong border: a mid grey that reads as a
   smudge on this ground rather than as a deliberate mark. */
.appflow-canvas .appflow-stop-bar { fill: rgba(126, 166, 232, .5); }

/*
 * The one glow on the board, and it is on the one hop the flow stops at.
 *
 * The riser comment above notes the spec bans shadows for **in-page
 * separation** — panels lifted off other panels — and that ban is intact: not
 * one plate here has a shadow. This is state, not separation, and it is spent
 * on a single element out of thirty. A reader's eye should land on the break
 * before they have read a word; that is the whole promise of the board.
 */
.appflow-canvas .appflow-hop.is-break .appflow-hop-box {
  filter: drop-shadow(0 0 9px color-mix(in srgb, var(--critical) 62%, transparent));
}
.appflow-canvas .appflow-hop.is-break .appflow-hop-dot {
  animation: appflow-break-beat 2.6s ease-in-out infinite;
}
@keyframes appflow-break-beat {
  0%, 100% { opacity: 1; }
  50% { opacity: .3; }
}
@media (prefers-reduced-motion: reduce) {
  /* Removed, not paused — a paused blink can freeze on the transparent frame
     and take the marker away entirely. Same rule the dependency map follows. */
  .appflow-canvas .appflow-hop.is-break .appflow-hop-dot { animation: none; }
}

.appflow-pickers {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
}

.appflow-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
  margin-top: var(--space-3);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.appflow-break {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}
.appflow-break > div { flex: 1 1 22rem; }

/*
 * The diagnosis, directly under the break it is about.
 *
 * One block so the banner and the answer read as one finding rather than as two panels that
 * happened to land next to each other. The answer is inset from the banner's left edge, which is
 * what says "this is about that" without a second heading repeating the hop's name.
 */
.appflow-break-block {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}
.appflow-break-note {
  margin: 0;
  padding-left: var(--space-4);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.appflow-break-answer {
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--critical);
  border-radius: var(--radius-md);
  background: var(--color-sunken);
}

/*
 * The conversation, under the answer, about the same hop.
 *
 * `.sre` fills whatever box it is given and pins its composer to the bottom of it — so the box has
 * to have a bottom. Without a height the thread grows the card forever and the flow picture is
 * pushed off the top of the screen by the reader's own third question, which is the one thing this
 * panel exists to keep on screen.
 *
 * `min-height` as well as `max-height`: an empty console is one opening card, and a box that starts
 * at the height of that card and jumps to 32rem on the first answer moves the composer out from
 * under the cursor mid-sentence.
 */
/*
 * The wait, which has to look like something is happening.
 *
 * A static line of grey text is indistinguishable from a page that has stopped, and this particular
 * wait ran for five minutes. The spinner says the request is alive, the clock says how long it has
 * been, and Stop is the exit that is otherwise a page reload.
 */
.appflow-waiting {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-left: var(--space-4);
}
.appflow-waiting-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.appflow-waiting-clock {
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}
.appflow-waiting-spin {
  flex: none;
  width: 14px;
  height: 14px;
  border: 2px solid var(--color-border-strong);
  border-top-color: var(--accent);
  border-radius: var(--radius-full);
  animation: appflow-waiting-spin 900ms linear infinite;
}
@keyframes appflow-waiting-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  /* The clock still ticks, so the wait is still visibly alive without the rotation. */
  .appflow-waiting-spin { animation: none; }
}
.appflow-waiting-slow {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.appflow-waiting-slow code {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
}

/*
 * The answer and the shell, side by side, inside the flow card.
 *
 * `.advice-split` lays its panes out at the height of its container and owns no scrolling itself, so
 * the container has to have a definite height — inside the dialog that comes from `fill`, and here
 * it has to be stated. Without it the terminal stretches to the height of the answer and the whole
 * card scrolls as one, which is the layout docking exists to avoid.
 */
.appflow-break-workspace {
  height: min(72vh, 760px);
  min-height: 26rem;
}

/*
 * The pane beside the answer: a tab strip, then whichever tool is showing.
 *
 * Both tools stay mounted and are hidden with `hidden` rather than unmounted — a terminal that
 * unmounted on a tab change would drop its session. So the panes are stacked in one grid cell and
 * only one is visible; `[hidden]` needs `display: none` restated because the flex rule below would
 * otherwise override the attribute's default.
 */
.appflow-tools {
  display: flex;
  flex-direction: column;
  min-height: 0;
  gap: var(--space-2);
  height: 100%;
}
/*
 * The strip and, where the pane can be put away, the way to do it.
 *
 * The tabs stay a pill group of their own; the close control sits beside it rather than inside it,
 * because a third button in a two-tab pill reads as a third tool. The head never flexes — the
 * space belongs to whichever pane is showing.
 */
.appflow-tools-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: none;
}
.appflow-tools-head > .appflow-tools-tabs { flex: 1 1 auto; }
.appflow-tools-tabs {
  display: flex;
  flex: none;
  gap: var(--space-1);
  padding: 2px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-sunken);
}
.appflow-tools-tab {
  flex: 1 1 0;
  padding: var(--space-2) var(--space-3);
  border: 0;
  border-radius: calc(var(--radius-md) - 2px);
  background: transparent;
  color: var(--color-text-muted);
  font: inherit;
  font-size: var(--text-sm);
  cursor: pointer;
}
.appflow-tools-tab:hover { color: var(--text-primary); }
.appflow-tools-tab:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }
.appflow-tools-tab.is-active {
  background: var(--color-surface);
  color: var(--text-primary);
  box-shadow: var(--elev-rest);
}
.appflow-tools-pane {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}
/* The attribute must win over the flex rule above, or a hidden pane still takes its space. */
.appflow-tools-pane[hidden] { display: none; }

.appflow-chat {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.appflow-chat-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
}
.appflow-chat-note {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.appflow-chat-box {
  display: flex;
  flex-direction: column;
  min-height: 22rem;
  max-height: 32rem;
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}

/* ---------------------------------------------------------------- what needs you */

/*
 * The to-do list at the top of the overview.
 *
 * Deliberately roomier than the dense operational register the rest of the portal uses. This is the
 * one surface read by somebody who does not know the product, and the thing that makes a list
 * scannable is space between its rows — the same content at table density reads as another report.
 */
.needsyou-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.needsyou-item {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-2);
  border-bottom: 1px solid var(--color-border);
  /* `relative` is what the headline's stretched `::after` resolves against — it is what makes the
     whole row the link's hit area, not decoration. */
  position: relative;
  transition: background var(--motion-fast) var(--ease-out);
}
.needsyou-item:last-child { border-bottom: none; }

/*
 * A dot, not a tinted row background.
 *
 * Three critical items with filled backgrounds turn the whole card red and the severity stops
 * carrying information — everything urgent means nothing is. The dot and the badge encode it twice
 * without colouring the surface the text sits on, which also keeps the text contrast fixed.
 */
.needsyou-dot {
  flex: 0 0 auto;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  margin-top: 0.45rem;
}
.needsyou-dot.sev-critical { background: var(--critical); }
.needsyou-dot.sev-warning { background: var(--warn); }
.needsyou-dot.sev-info { background: var(--color-border-strong); }

.needsyou-body { flex: 1 1 20rem; min-width: 0; }

.needsyou-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}
.needsyou-head strong { font-size: var(--text-md); }

.needsyou-detail {
  margin: var(--space-1) 0 0;
  color: var(--color-text-muted);
  /* Kept near 65 characters of running text so the sentence reads rather than scans. */
  max-width: 68ch;
}

.needsyou-actions {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* --------------------------------------------------------------- the disclosure and the row link */

.needsyou-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

/* A chevron drawn from a border, so it needs no asset and inherits the button's colour. */
.needsyou-chevron {
  width: 0.45rem;
  height: 0.45rem;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg);
  transition: transform 120ms ease;
}
.needsyou-chevron.is-open { transform: rotate(45deg); }

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

/*
 * The collapsed state: counts by severity, still coloured, still a control.
 *
 * A button rather than a div because it re-opens the panel — the whole summary is the target, so
 * somebody who collapsed it by accident gets it back without hunting for the chevron.
 */
.needsyou-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-3) 0;
  background: none;
  border: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.needsyou-summary-hint {
  color: var(--color-text-subtle);
  font-size: var(--text-sm);
}
.needsyou-summary:hover .needsyou-summary-hint { color: var(--color-text); }

/*
 * The whole row is the link.
 *
 * `position: relative` on the row plus a stretched pseudo-element on the headline: one anchor, full
 * hit area, and no interactive element nested inside another — which is invalid markup and which
 * breaks keyboard order in exactly the way a screen reader reports as two overlapping controls.
 */
.needsyou-headline {
  color: inherit;
  text-decoration: none;
}
.needsyou-headline::after {
  content: "";
  position: absolute;
  inset: 0;
}
.needsyou-headline:hover strong { text-decoration: none; }
.needsyou-headline:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 3px;
}

/* Above the stretched link, so the buttons at the end of the row keep their own destinations. */
.needsyou-actions { position: relative; z-index: 1; flex: 0 0 auto; }

/*
 * The chevron that says the row goes somewhere.
 *
 * It replaces a "Show me" button that pointed at the same href as the row itself. A duplicate
 * control is worse than no control: it gives a pointer two targets for one result and gives a
 * screen reader two names for one link, the weaker of which ("Show me") is the one it reads second.
 *
 * `pointer-events: none` is the load-bearing line. `.needsyou-actions` sits above the stretched
 * link on `z-index` so "Explain it" keeps its own destination — if this shared that stacking it
 * would swallow clicks near the right edge of the row and navigate nowhere, which is the specific
 * bug a stretched link invites.
 */
.needsyou-go {
  flex: 0 0 auto;
  align-self: center;
  width: 0.5rem;
  height: 0.5rem;
  margin-left: var(--space-1);
  border-top: 1.5px solid var(--color-text-subtle);
  border-right: 1.5px solid var(--color-text-subtle);
  transform: rotate(45deg);
  pointer-events: none;
  transition: border-color var(--motion-fast) var(--ease-out), transform var(--motion-fast) var(--ease-out);
}
.needsyou-item:hover .needsyou-go {
  border-color: var(--accent);
  transform: rotate(45deg) translate(1px, -1px);
}

@media (prefers-reduced-motion: reduce) {
  .needsyou-go { transition: none; }
  .needsyou-item:hover .needsyou-go { transform: rotate(45deg); }
}

/* The row lifts a little under the pointer, so a full-width click target announces itself. */
.needsyou-item:hover { background: var(--bg-hover); }

.needsyou-clear {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-2);
}
.needsyou-clear-icon { flex: 0 0 auto; color: var(--ok); margin-top: 0.15rem; }
.needsyou-clear-lead { margin: 0; color: var(--color-text); }
/*
 * The checks that ran, in the quiet register.
 *
 * Present because "nothing needs you" and "this failed to load" are the same blank space, and on
 * this product that difference has cost eight hours of blind collection. Quiet, because it is
 * reassurance rather than instruction — but never absent.
 */
.needsyou-clear-checked {
  margin: var(--space-1) 0 0;
  font-size: var(--text-sm);
  color: var(--color-text-subtle);
  max-width: 68ch;
}

/* ------------------------------------------------------------------- the AI pipeline lanes
 *
 * A fixed three-column pipeline — what asked, the runtime, what came out — rather than the measured
 * graph `.flow-map-svg` draws. It carries its own palette scope for the same reason the dependency
 * map does (see "The dependency map's canvas" in claude/28-design.md): severity here is a *second*
 * colour channel over a dark instrument ground, and mixing it with the page's single accent hue
 * would leave the reader unable to tell an accent from a verdict.
 */
.flowlane-canvas {
  --lane-ink: var(--color-text);
  --lane-ink-dim: var(--color-text-subtle);
  --lane-line: var(--color-border-strong);

  display: block;
  width: 100%;
  height: auto;
  /* Capped, not fixed: the pipeline is at most a handful of lanes tall, and a fixed height would
     strand a two-lane account in the middle of an empty instrument. */
  max-height: clamp(240px, 46vh, 520px);
  background: var(--bg-sunken);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}

.flowlane-edge {
  fill: none;
  stroke: var(--lane-line);
  stroke-width: 1.25;
}
.flowlane-edge.sev-ok { stroke: var(--ok); }
.flowlane-edge.sev-warn { stroke: var(--warn); }
.flowlane-edge.sev-critical { stroke: var(--critical); }
/* A lane that carried nothing is drawn, dimmed, rather than removed: the path exists in the
   product whether or not anything used it this window, and the figure beside it says zero. */
.flowlane-edge.sev-unknown { stroke: var(--lane-line); stroke-dasharray: 3 5; }
.flowlane-edge-live { stroke-width: 2; }

.flowlane-particles { pointer-events: none; }
.flowlane-dot.sev-ok { fill: var(--ok); }
.flowlane-dot.sev-warn { fill: var(--warn); }
.flowlane-dot.sev-critical { fill: var(--critical); }
.flowlane-dot.sev-unknown { fill: var(--lane-ink-dim); }

.flowlane-box {
  fill: var(--color-surface);
  stroke: var(--color-border);
  stroke-width: 1;
}
.flowlane-node.sev-warn .flowlane-box { stroke: var(--warn); }
.flowlane-node.sev-critical .flowlane-box { stroke: var(--critical); }

/*
 * Ordinary CSS-pixel sizes, because this canvas renders at roughly 1:1.
 *
 * The dependency map's does not — its viewBox is far wider than the card, so it downscales and its
 * type is written at double size to survive. This one is three columns and its viewBox is about the
 * width of the card it sits in, so the same trick would render every label half again too large:
 * which is exactly what the first draft did, and the details ran out of their boxes.
 */
.flowlane-name {
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 600;
  fill: var(--lane-ink);
}
.flowlane-detail {
  font-family: var(--font-mono);
  font-size: 11px;
  fill: var(--lane-ink-dim);
}

.flowlane-core-halo {
  fill: none;
  stroke: var(--lane-line);
  stroke-width: 1;
  opacity: 0.55;
}
.flowlane-core-body { fill: var(--color-surface); stroke: var(--lane-line); stroke-width: 1.5; }
.flowlane-core.sev-warn .flowlane-core-body,
.flowlane-core.sev-warn .flowlane-core-halo { stroke: var(--warn); }
.flowlane-core.sev-critical .flowlane-core-body,
.flowlane-core.sev-critical .flowlane-core-halo { stroke: var(--critical); }
.flowlane-core.sev-ok .flowlane-core-halo { stroke: var(--ok); }

.flowlane-core-eyebrow {
  font-family: var(--font-sans);
  font-size: 9px;
  letter-spacing: 0.12em;
  text-anchor: middle;
  /* The bright core is the ground here, so the dim ink disappears on it. Full ink at low opacity
     keeps the eyebrow readable without competing with the name under it. */
  fill: var(--lane-ink);
  opacity: 0.72;
}
.flowlane-core-name {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 600;
  text-anchor: middle;
  fill: var(--lane-ink);
}
.flowlane-core-detail {
  font-family: var(--font-mono);
  font-size: 11px;
  text-anchor: middle;
  fill: var(--lane-ink-dim);
}
/* The one place on this canvas that says data left the customer's network. Critical, not warn:
   it is not a degradation, it is the fact their security review asked about. */
.flowlane-core-badge {
  font-family: var(--font-sans);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-anchor: middle;
  fill: var(--warn-text);
}

/* The board's own chrome: the window picker and the coverage note under the diagram. */
.flowlane-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}
.flowlane-note {
  margin: var(--space-3) 0 0;
  font-size: var(--text-sm);
  color: var(--color-text-subtle);
}

/* ---------------------------------------------------------------------------- understand */

.foresight-understand { display: grid; gap: var(--space-4); }
.foresight-alert-head { display: flex; align-items: center; gap: var(--space-3); margin-bottom: var(--space-3); }
.foresight-explanation { margin: 0; font-size: var(--text-base); line-height: var(--leading-relaxed); }
.foresight-muted { color: var(--color-text-subtle); font-size: var(--text-sm); }

/* ---------------------------------------------------------------------------- predict */

/* Contains on x, or reaching the end of the wide table hands the wheel to the page and the whole
   layout slides — the "why is my dashboard drifting" bug scroll.test.ts exists for. */
.foresight-scroll { overflow-x: auto; overscroll-behavior-x: contain; }

.foresight-table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.foresight-table thead th {
  text-align: left;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-weight: 400;
  color: var(--color-text-subtle);
  padding: 0 var(--space-3) var(--space-2);
  border-bottom: 1px solid var(--color-border);
  white-space: nowrap;
}
.foresight-table tbody td { padding: var(--space-3); border-bottom: 1px solid var(--color-border); }
.foresight-table tbody tr:last-child td { border-bottom: none; }
.foresight-table .r { text-align: right; }

/*
 * The class accuracy figure, reading down rather than across.
 *
 * It was `.foresight-meter` — an `inline-flex` row holding a percentage, a fixed 54px track and a
 * sample count — inside a `dd` in a three-column grid on a 300px card. That is roughly 140px of
 * unshrinkable content in a ~85px column: `inline-flex` does not wrap and its children were `flex:
 * none`, so it did not clip or reflow, it simply ran out past the card's own border and printed
 * "133 scored" over whatever was to the right of it. `min-width: 0` on the figure could not help,
 * because nothing inside was shrinkable in the first place.
 *
 * A column fits any width, so this is a column.
 */
.foresight-accuracy {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  min-width: 0;
}
/* The count under the bar is prose, and prose wraps. Without this it is one long unbreakable line
   and the overflow comes straight back. */
.foresight-accuracy small {
  font-size: var(--text-xs);
  font-weight: 400;
  line-height: var(--leading-body);
  color: var(--color-text-muted);
  overflow-wrap: anywhere;
}
/*
 * `width: 100%` was right when every card was 300px and wrong the moment the
 * cards holding predictions became full-width rows: an 84% accuracy bar ran the
 * entire width of the board, which reads as a progress bar for the card rather
 * than as one figure among three. A cap keeps it a figure.
 */
.foresight-accuracy .foresight-meter-track { width: 100%; max-width: 140px; }

.foresight-meter-track {
  width: 54px;
  height: var(--size-bar);
  border-radius: var(--radius-full);
  background: var(--color-sunken);
  overflow: hidden;
  flex: none;
}
.foresight-meter-fill { display: block; height: 100%; border-radius: var(--radius-full); background: var(--ok); }
.foresight-meter-fill.is-weak { background: var(--warn); }
.is-weak { color: var(--warn-text); }

/* ---------------------------------------------------------------------------- predict: segments */

/*
 * One card per detector, and every card is always on the page.
 *
 * The board used to be one table, and an empty table said "nothing predicted" about four different
 * situations. The cards carry the figures that tell them apart — subjects watched, found on the
 * last pass, the error raised — so an empty segment is either the good case or a named outage,
 * never a blank. A card holding open predictions takes the whole row: its table needs the width,
 * and a forecast squeezed into a third of the board is one the eye skips.
 */
.foresight-engine {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.foresight-engine strong { color: var(--text-primary); font-weight: 600; }

.foresight-segments {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: var(--space-4);
}
.foresight-segment {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}
/*
 * The board in two groups: what needs you, then the evidence everything else was looked at.
 *
 * This replaces `.foresight-segment.has-open { grid-column: 1 / -1 }`. That rule made a card that
 * had found something span the whole row while the quiet cards beside it stayed at their 300px
 * minimum, so the page alternated full-width block and narrow card down its length — a shape
 * produced by the data rather than chosen, which reads as a layout that failed rather than as a
 * board. The cards that carry a table of predictions are now in their own single-column stack and
 * the quiet ones keep the grid they were designed for.
 */
.foresight-group { margin-top: var(--space-6); }
.foresight-group:first-of-type { margin-top: var(--space-4); }

.foresight-group-title {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0 0 var(--space-3);
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--color-text);
}
/* The count is the qualifier, so it is never the same weight as the word it qualifies. */
.foresight-group-count {
  font-size: var(--text-sm);
  font-weight: 400;
  color: var(--color-text-muted);
}

/* One per row. A card here holds a table of predictions and their fix panels; two side by side
   would give each a column too narrow for either. */
.foresight-found {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-4);
}
/* The border carries the state; the alarm colour is spent only on a detector that failed. */
.foresight-segment.is-predicted { border-color: color-mix(in srgb, var(--warn) 45%, var(--color-border)); }
.foresight-segment.is-failed { border-color: color-mix(in srgb, var(--critical-text) 55%, var(--color-border)); }

.foresight-segment-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.foresight-segment-title { margin: 0; font-size: var(--text-base); font-weight: 600; }
.foresight-segment-watches { margin: 0; font-size: var(--text-sm); color: var(--color-text-muted); }

/*
 * Three columns where there is room for three, two where there is not.
 *
 * Fixed at `repeat(3, 1fr)` the labels broke mid-phrase in the narrow cards —
 * "FOUND LAST / PASS" over two lines, "201 containers / with a memory / limit"
 * over three — so a card that had found nothing was the hardest one on the
 * board to read. `auto-fit` lets the quiet grid drop to two columns and keeps
 * three in the full-width rows, from one declaration.
 */
.foresight-segment-figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(118px, 1fr));
  gap: var(--space-3) var(--space-4);
  margin: 0;
}
.foresight-segment-figure { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.foresight-segment-figure dt {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-subtle);
}
.foresight-segment-figure dd { margin: 0; font-size: var(--text-lg); font-weight: 600; }
.foresight-segment-figure dd small { font-size: var(--text-xs); font-weight: 400; color: var(--color-text-muted); }
.foresight-segment-soon { margin: 0; font-size: var(--text-sm); }

/*
 * The prediction rows, and why the sentence is not a column.
 *
 * A prediction's message is the detector's whole finding — two hundred characters naming the
 * subject, the count, the last occurrence and what happens next. As a fifth `<td>` it took most of
 * the table's width and squeezed Subject, Namespace, Horizon and Confidence into what was left,
 * which is how a five-column table ends up unreadable at any window size. It is a full-width row
 * underneath now: the sentence gets a line length, the figures get their columns back.
 */
.foresight-predictions { table-layout: auto; }

/* A pod name is one unbreakable token — `rook-ceph-rgw-ocs-storagecluster-cephobjectstore-a-…` is
   62 characters with nowhere to break. `anywhere` is what stops it setting the column's width and
   pushing everything else off the side; the mono face is so a reader can tell a name from prose. */
.foresight-subject {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  overflow-wrap: anywhere;
}

/*
 * A prediction is two rows that read as one: the figures, then the sentence under them.
 *
 * So the figures row loses its bottom padding and the separating rule, and the detail row carries
 * both — otherwise the pair is drawn as two table rows with a line between them, which is exactly
 * the reading the split was made to avoid.
 */
.foresight-prediction > td { padding-bottom: var(--space-1); }
.foresight-predictions tbody td { border-bottom: none; }
.foresight-prediction-detail > td {
  padding-top: 0;
  border-bottom: 1px solid var(--color-border);
}
.foresight-predictions tbody tr:last-child td { border-bottom: none; }

.foresight-prediction-message {
  margin: 0;
  max-width: 90ch;
  font-size: var(--text-sm);
  line-height: var(--leading-body);
  color: var(--color-text-muted);
}

/* What to do when there is no catalogued change, in the engine's own words. Amber rather than red:
   "we cannot fix this from here" is a limit, not a failure. */
.foresight-prediction-instead {
  margin: var(--space-2) 0 0;
  max-width: 90ch;
  font-size: var(--text-sm);
  line-height: var(--leading-body);
  color: var(--warn-text);
}

.foresight-prediction-because {
  margin: 0 0 var(--space-3);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.foresight-prediction-fix > td { padding-top: 0; }
.foresight-action-fix { margin-top: var(--space-3); }

/*
 * The Fix panel takes the row's width and gives the table none of its own.
 *
 * Table columns are sized from their cells' content, and a `<pre>` holding an `oc patch` has a
 * min-content width of the whole command. `min-width: 0` on the scroller cannot stop that — it caps
 * a flex item's floor, not a table cell's intrinsic width — so one long remedy widened the whole
 * table past its card: Open, Explain and every Copy button scrolled out of view, and the figures
 * were spread across a table wider than the page. Inline-size containment makes the panel's
 * intrinsic width zero, so it fills the cell the other columns sized and its commands scroll
 * inside their own frames. On the wrapper, because size containment does not apply to a table cell.
 */
.foresight-prediction-fix-body { contain: inline-size; }

/*
 * The controls on a row. `flex-end` because the column is right-aligned like the two figures beside
 * it, and wrapping because three links do not fit a narrow window and must stack rather than force
 * the table to scroll.
 */
.foresight-row-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
}
/*
 * Row-sized, not form-sized. `.btn` is the 34px control at body size — right in a toolbar, and the
 * largest thing in a table whose rows are set at 11–12px, so three of them made every prediction
 * row a toolbar with a sentence attached. Compact is the size of every other control on this
 * panel: Request this change, Copy and Check whether this worked.
 */
.foresight-row-actions .btn {
  height: var(--size-control-sm);
  padding: 0 var(--space-3);
  font-size: var(--text-sm);
}
/* The toggle reads as on while its panel is open, so the open panel has a visible owner. */
.foresight-row-actions .btn[aria-expanded="true"] {
  border-color: var(--accent);
  background: var(--accent-soft);
}
.foresight-action .foresight-row-actions {
  justify-content: flex-start;
  margin-top: var(--space-2);
}

/* The countdowns the engine does not own — error budgets, certificates — beside the segments. */
.foresight-countdowns {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: var(--space-4);
  margin-top: var(--space-4);
}
.foresight-countdown-list { display: grid; gap: var(--space-2); margin: 0; padding: 0; list-style: none; }
.foresight-countdown {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
  font-size: var(--text-sm);
}
.foresight-countdown-when { margin-left: auto; white-space: nowrap; }

/* ══════════════════════════════════════════════════════════════════════════
   Foresight + Dependencies — the depth layer

   Scoped, not global, and that is the whole design.

   `--elev-rest` and `--elev-hover` are `none` product-wide: a deliberately flat
   system where the 1px border is the only separation mechanism between adjacent
   regions. Nothing here changes that. It defines a second, richer depth
   vocabulary that exists only inside these two surfaces — which is exactly what
   `.depmap-canvas` already does for colour, declaring its own palette locally
   rather than pushing map hues into the product's tokens.

   Why only these two: they are the boards dense enough to need layering. A
   forecast board carries a dozen detector cards, a table of predictions and a
   fix panel inside one column; the dependency map carries fifty chips at four
   radii. Everywhere else in the product a card is a card, and a shadow on it
   would be decoration.

   Grounds stay graphite. Nothing below introduces a hue — `tokens.ts` is
   explicit that a page tinted toward blue puts the accent's own hue behind
   every chart, and these boards are mostly chart. What is added is elevation,
   a field for the eye to centre on, and glow spent on exactly two states.
   ══════════════════════════════════════════════════════════════════════════ */

.foresight-stage,
.depmap-canvas {
  /* Three steps, and the ramp is the point: a resting panel, a panel carrying
     something open, and the thing the reader is looking at. One shadow reused
     at three strengths reads as a system; three unrelated shadows read as three
     components from three places. */
  --depth-1: 0 1px 2px rgba(0, 0, 0, .24), 0 3px 10px -5px rgba(0, 0, 0, .34);
  --depth-2: 0 2px 5px rgba(0, 0, 0, .28), 0 14px 30px -14px rgba(0, 0, 0, .50);
  --depth-3: 0 3px 8px rgba(0, 0, 0, .34), 0 26px 56px -22px rgba(0, 0, 0, .64);
  /* The machined edge — one hairline along the top, where a light would catch.
     It is what separates a panel that has depth from a panel that merely has a
     shadow under it.

     White rather than the theme's ink, and that is the theme-safe choice rather
     than a dark-mode assumption: on the graphite ground it is the highlight the
     shadow implies, and on the light theme it lands white-on-near-white and
     disappears. Following `--color-text` instead would draw a dark rule across
     the top of every card in light mode — a second border, in the one place a
     border already exists. */
  --lift: inset 0 1px 0 rgba(255, 255, 255, .06);
}

/*
 * The field the board sits in.
 *
 * A stage this dense reads better in a field than on a flat sheet: the eye is
 * given a centre before it is given rows. Built from the theme's own two
 * grounds through `color-mix`, so it follows light and dark and carries no hue
 * of its own — and behind the content on its own stacking context, so nothing
 * on the stage has to know it is there.
 */
.foresight-stage { position: relative; isolation: isolate; }
.foresight-stage::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: calc(var(--space-6) * -1) calc(var(--space-5) * -1) calc(var(--space-5) * -1);
  border-radius: var(--radius-lg);
  /*
   * A flat wash, and it was a radial one.
   *
   * `tokens.test.ts` refuses a gradient on a data surface — a gradient behind a measurement is
   * decoration competing with a number — and this sits behind a board of open predictions, each
   * carrying a figure and a confidence. The stage still separates itself from the page; it just
   * does it with one tone instead of a bloom.
   */
  background: color-mix(in srgb, var(--bg-surface) 45%, transparent);
  pointer-events: none;
}

/* Panels lift. One step at rest; two for a board carrying open predictions. */
.foresight-stage .card,
.foresight-segment,
.foresight-countdowns .card {
  box-shadow: var(--depth-1), var(--lift);
  transition: box-shadow .22s var(--ease-out), transform .22s var(--ease-out);
}
.foresight-found > .foresight-segment { box-shadow: var(--depth-2), var(--lift); }
.foresight-segment:hover { box-shadow: var(--depth-3), var(--lift); transform: translateY(-1px); }

/*
 * Glow is spent on two states and no others: a detector that failed, and a
 * detector holding an open forecast. A board where every panel glows is a board
 * where the glow means nothing — the same rule the alert rail follows when it
 * refuses to spend its alarm colour on a list of warnings.
 *
 * The ring is `color-mix` over the semantic token rather than a new colour, so
 * these stay the same red and amber as every badge, chip and dot in the product.
 */
.foresight-segment.is-failed {
  box-shadow:
    var(--depth-2), var(--lift),
    0 0 0 1px color-mix(in srgb, var(--critical) 34%, transparent),
    0 0 30px -12px color-mix(in srgb, var(--critical) 70%, transparent);
}
.foresight-segment.is-predicted {
  box-shadow:
    var(--depth-2), var(--lift),
    0 0 0 1px color-mix(in srgb, var(--warn) 26%, transparent);
}

/*
 * Denser data.
 *
 * Every measurement on these boards is already mono; what it did not have was
 * columns that line up. Tabular figures make a column of numbers scannable as a
 * column, and pulling the tracking in very slightly stops long mono strings —
 * a 62-character pod name, a 13,213-sample count — reading as loose.
 */
.foresight-stage .tnum,
.foresight-segment-figure dd,
.foresight-accuracy,
.foresight-countdown-when,
.depmap-chip {
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.005em;
}

/*
 * The dependency map's own depth.
 *
 * Chips are SVG, so this is the same drop-shadow treatment rather than the box
 * ramp above — but read against the map's own near-black ground, which is why
 * the values are darker than the panel ramp rather than reused from it.
 */
.depmap-chip { filter: drop-shadow(0 1px 3px rgba(0, 0, 0, .55)); }
.depmap-orb  { filter: drop-shadow(0 3px 14px rgba(0, 0, 0, .7)); }

@media (prefers-reduced-motion: reduce) {
  /* The lift stays, the movement goes. Removing the shadow change too would
     take away the state, not the motion. */
  .foresight-segment:hover { transform: none; }
}

/* ---------------------------------------------------------------------------- simulate */

.foresight-sim {
  display: grid;
  grid-template-columns: minmax(0, 340px) minmax(0, 1fr);
  gap: var(--space-4);
  align-items: start;
}

.foresight-field { display: grid; gap: var(--space-2); padding-bottom: var(--space-4); }
.foresight-field-label { font-size: var(--text-base); font-weight: 500; }
.foresight-select {
  height: var(--size-control);
  padding: 0 var(--space-3);
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text);
  font-family: inherit;
  font-size: var(--text-base);
}

.foresight-control {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--color-border);
}
.foresight-control:last-of-type { border-bottom: none; }
.foresight-control-top { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); }
.foresight-control label { font-size: var(--text-base); font-weight: 500; }
.foresight-control-readout { font-family: var(--font-mono); font-size: var(--text-md); color: var(--color-primary-text); }
.foresight-control-hint { font-size: var(--text-xs); color: var(--color-text-subtle); }

.foresight-range {
  -webkit-appearance: none;
  -moz-appearance: none;
       appearance: none;
  width: 100%;
  height: 22px;
  background: transparent;
  cursor: pointer;
  margin: 0;
}
.foresight-range::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: var(--radius-full);
  background: var(--color-sunken);
}
.foresight-range::-moz-range-track {
  height: 4px;
  border-radius: var(--radius-full);
  background: var(--color-sunken);
}
.foresight-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 15px;
  height: 15px;
  margin-top: -5.5px;
  border-radius: 50%;
  background: var(--color-primary-solid);
  border: 2px solid var(--color-surface);
  box-shadow: 0 0 0 1px var(--color-primary);
}
.foresight-range::-moz-range-thumb {
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: var(--color-primary-solid);
  border: 2px solid var(--color-surface);
  box-shadow: 0 0 0 1px var(--color-primary);
}
.foresight-range:focus-visible { outline: none; }
.foresight-range:focus-visible::-webkit-slider-thumb {
  box-shadow: 0 0 0 1px var(--color-primary), 0 0 0 4px var(--color-focus-ring);
}

.foresight-reset {
  margin-top: var(--space-4);
  height: var(--size-control-sm);
  padding: 0 var(--space-3);
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border);
  background: var(--color-hover);
  color: var(--color-text);
  font-family: inherit;
  font-size: var(--text-sm);
  cursor: pointer;
}
.foresight-reset:disabled { opacity: 0.5; cursor: default; }
.foresight-reset:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; }

/* Nested in a card: sunken, no shadow. */
.foresight-verdict {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-4);
  border-radius: var(--radius-md);
  background: var(--bg-sunken);
  border: 1px solid var(--color-border);
  justify-items: start;
}
.foresight-verdict.is-safe { border-color: var(--ok); }
.foresight-verdict.is-risk { border-color: var(--critical); }
.foresight-verdict-big {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-2xl);
  line-height: 1.1;
}
.foresight-verdict.is-safe .foresight-verdict-big { color: var(--ok-text); }
.foresight-verdict.is-risk .foresight-verdict-big { color: var(--critical-text); }
.foresight-verdict-delta { font-family: var(--font-mono); font-size: var(--text-base); }
.foresight-verdict-delta.is-better { color: var(--ok-text); }
.foresight-verdict-delta.is-worse { color: var(--critical-text); }
.foresight-verdict-why { margin: 0; font-size: var(--text-sm); color: var(--color-text-muted); line-height: var(--leading-relaxed); }

.foresight-sim-facts { margin-top: var(--space-4); }
.foresight-sim-note {
  margin: var(--space-4) 0 0;
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
  border-left: 2px solid var(--color-border-strong);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* ---------------------------------------------------------------------------- act */

.foresight-actions { display: grid; gap: var(--space-3); margin: 0; padding: 0; list-style: none; }
.foresight-action {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-4);
  align-items: start;
  padding: var(--space-4);
  border-radius: var(--radius-md);
  background: var(--bg-sunken);
  border: 1px solid var(--color-border);
}
.foresight-action-rank {
  font-family: var(--font-mono);
  font-size: var(--text-lg);
  color: var(--color-text-subtle);
  width: 22px;
  text-align: center;
}
.foresight-action-title { font-size: var(--text-md); font-weight: 500; }
.foresight-action-why { font-size: var(--text-sm); color: var(--color-text-muted); margin-top: 2px; }
.foresight-action-evidence {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-text-subtle);
  margin-top: var(--space-2);
}

/* ---------------------------------------------------------------------------- responsive */

@media (max-width: 1100px) {
  .foresight-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .foresight-observe,
  .foresight-sim { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 620px) {
  .foresight-tiles { grid-template-columns: minmax(0, 1fr); }
  .foresight-stage-what { display: none; }
}

/* ---- the hero zone (design spec v3) -----------------------------------------------------------
 *
 * One status, one sentence, one quiet row. The page states its conclusion before its figures —
 * the inversion of the eight-tile strip this replaces, where the reader assembled the conclusion
 * from eight equally-weighted numbers.
 *
 * Separation here is whitespace and one hairline, never a shadow and never a card: the hero is not
 * a box among boxes, it is the top of the page. Sized in the sans the titles already use — the
 * verdict is scanned, not read.
 */
/*
 * The verdict block that opens most pages.
 *
 * Its vertical rhythm was measured on a 1000px window and came to 219px — with the page header above
 * it, 302px of chrome before the first card, very nearly a third of the screen spent before anything
 * a reader came for. The type was never the problem: 48px of it was padding, 24px was a bottom
 * margin, and another 36px sat in the gaps between three short lines.
 *
 * So the whitespace is where the cut was taken. The hierarchy is unchanged — the verdict is still by
 * far the largest thing on the page — but the block now leaves room for the content it introduces.
 */
.hero {
  padding: var(--space-3) 0 var(--space-4);
  border-bottom: 1px solid var(--border-hairline);
  margin-bottom: var(--space-4);
}
.hero-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-2);
}
.hero-eyebrow {
  font-size: var(--text-sm);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}
/* The age of everything below, phrased as a status: word + dot, no pill. */
.hero-freshness {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-sm);
  color: var(--text-secondary);
}
.hero-freshness-warning { color: var(--warn-text); }
.hero-freshness-critical { color: var(--critical); }

/* The confirmation is subordinate to the age, and takes the neutral colour rather than inheriting
   the severity one. The severity belongs to the alert; "checked 20s ago" is good news whatever the
   alert's severity is, and painting it red would say the opposite of what it means. */
.hero-freshness-confirmed {
  color: var(--text-secondary);
  font-weight: 400;
}
.hero-dot {
  width: 6px;
  height: 6px;
  border-radius: var(--radius-full);
  background: currentColor;
}

/*
 * The verdict: tight, alone on its line, and the one thing on the screen set at this size — the
 * whole point fails if anything else on the page competes with it.
 *
 * Sized against the window rather than fixed at 38px. A single size cannot be right for both a
 * 1000px laptop, where 38px was a third of the chrome above the fold, and a wide desktop where it is
 * the anchor of the page. The floor is 26px and the ceiling 32px, so it stays roughly twice the
 * largest other text on the page — the next size down is 15px — and the hierarchy the fixed value
 * was protecting is intact at both ends.
 *
 * The word carries the status colour only when the status is not "healthy". Green on every visit
 * teaches the reader that colour here is decoration; reserving it for trouble keeps it a signal.
 */
.hero-verdict {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  font-family: var(--font-sans);
  font-size: clamp(19px, 1.5vw, 23px);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.015em;
  color: var(--text-primary);
}
.hero-verdict-dot {
  width: 8px;
  height: 8px;
  border-radius: var(--radius-full);
  background: var(--ok);
  flex: none;
}
.hero-verdict-degraded { color: var(--warn-text); }
.hero-verdict-degraded .hero-verdict-dot { background: var(--warn); }
.hero-verdict-critical { color: var(--critical); }
.hero-verdict-critical .hero-verdict-dot { background: var(--critical); }
/* Unknown is an absence, not a middle severity: muted, and the dot is an outline — a place where a
   reading would be, holding no reading. */
.hero-verdict-unknown { color: var(--text-secondary); }
.hero-verdict-unknown .hero-verdict-dot {
  background: transparent;
  border: 2px solid var(--text-muted);
}

/* The evidence: one sentence, body size, measured. Carries the figures the verdict came from. */
.hero-detail {
  margin: var(--space-1) 0 0;
  max-width: 62ch;
  font-size: var(--text-sm);
  line-height: var(--leading-body);
  color: var(--text-secondary);
}

/*
 * The demoted tier. Every count the old strip carried, one type size, no borders between them —
 * the gap is the separator. `dt` and `dd` sit on one line per figure so the row reads as
 * "label value  label value", the way a caption reads, not as a rank of small cards.
 */
/* The alert line sits between the verdict's sentence and the figures it was derived from, with the
   figures' own top margin doing the separating below it. */
.hero-alerts {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

.hero-figures {
  display: flex;
  flex-wrap: wrap;
  -moz-column-gap: var(--space-8);
       column-gap: var(--space-8);
  row-gap: var(--space-2);
  margin: var(--space-3) 0 0;
}
.hero-figure {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
}
.hero-figure dt {
  font-size: var(--text-sm);
  color: var(--text-muted);
}
.hero-figure dd {
  margin: 0;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-sm);
  color: var(--text-secondary);
}
.hero-figure dd a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid var(--border-strong);
}
.hero-figure dd a:hover {
  color: var(--text-primary);
  border-bottom-color: var(--text-primary);
}
/* An inline trend beside a figure — self-centred so the svg does not ride the text baseline. */
.hero-figure-spark {
  display: inline-flex;
  align-self: center;
  margin-left: var(--space-1);
}

@media (max-width: 620px) {
  .hero-verdict { font-size: 20px; }
  .hero-figures { -moz-column-gap: var(--space-5); column-gap: var(--space-5); }
}

/* ------------------------------------------------------------------ the folded opener
 *
 * `PageHero compact`: the verdict alone on a line, with the rest of the register behind it.
 *
 * On the dependency map the five stacked rows of the full opener were ninety-odd pixels of a window
 * the picture underneath has to fit inside — so the introduction to the diagram was drawn on top of
 * the diagram. What is left is the one row that is a judgement, and that row is also the button:
 * the target is the sentence itself, not a control sitting next to it.
 */
.hero-bar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--border-hairline);
  margin-bottom: var(--space-3);
}
.hero-bar-open {
  display: flex;
  flex: 1;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  /* Pulled back by its own inset so the verdict starts on the page's left edge — exactly where the
     full register's <h2> starts. A heading indented by a button's padding reads as misaligned. */
  margin-left: calc(var(--space-2) * -1);
  padding: var(--space-1) var(--space-2);
  border: 0;
  border-radius: var(--radius-md);
  background: none;
  font-family: var(--font-sans);
  font-size: var(--text-lg);
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.35;
  text-align: left;
  cursor: pointer;
}
/*
 * Zero specificity, deliberately.
 *
 * The button also carries the `hero-verdict-*` modifier, and that rule is one class too — so
 * declaring `color` inside `.hero-bar-open` above ties with it and wins on source order, printing a
 * degraded verdict in primary ink. `:where()` makes this the floor rather than a competitor, so the
 * modifier decides wherever either rule ends up in this file.
 */
:where(.hero-bar-open) {
  color: var(--text-primary);
}
.hero-bar-open:hover {
  background: var(--bg-hover);
}
/* One line at any width. The sentence is repeated in full as the dialog's title, so the ellipsis
   hides nothing that pressing it does not immediately give back. */
.hero-bar-text {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
/*
 * The counts, on the bar.
 *
 * Same `figures` the dialog lists, printed inline between the verdict and the `ⓘ`: the label muted,
 * the value in the mono face with tabular figures so a row of counts does not jitter by a pixel
 * every time one of them changes. Small type and normal weight — the sentence beside them is the
 * judgement and has to stay the loudest thing on the row.
 *
 * Explicit colours rather than inherited ones, because the button carries the `hero-verdict-*`
 * modifier: inheriting would print the counts in the verdict's amber or red and turn a caption into
 * five more alarms.
 */
.hero-bar-figs {
  display: flex;
  align-items: baseline;
  gap: var(--space-4);
  /*
   * Never shrinks, and the verdict beside it does.
   *
   * The first version had this the other way round — the counts giving up width first, under
   * `overflow: hidden` — on the reasoning that the sentence is what the row exists to carry. At
   * 1100px that clipped `52/78` to `52/7`, which is not a truncated number but a different one, in
   * the one row a reader takes the page's word from. The verdict's own ellipsis is a mark a reader
   * can see, and the sentence is repeated in full as the dialog's title; a half-drawn count says
   * nothing about having been cut. So the sentence gives way, visibly, and the counts stay whole.
   */
  flex: none;
  margin-left: auto;
  white-space: nowrap;
}
/*
 * `.hero-bar-more` claims the free space with a `margin-left: auto` of its own, and with the figures
 * doing the same the two split it and the counts settle in the middle of the row. Two classes beat
 * one, so this is the rule that decides when both are present.
 */
.hero-bar-figs + .hero-bar-more {
  margin-left: var(--space-4);
}
.hero-bar-fig {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-2);
  font-size: var(--text-sm);
  font-weight: 400;
  letter-spacing: 0;
}
.hero-bar-fig-k {
  color: var(--text-muted);
}
.hero-bar-fig-v {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
}
/* Below this the verdict needs the whole row to itself. Nothing is lost by hiding them: the press
   gives back every count, with the sentence that qualifies them. */
@media (max-width: 900px) {
  .hero-bar-figs { display: none; }
}

.hero-bar-more {
  display: inline-flex;
  flex: none;
  margin-left: auto;
  color: var(--text-muted);
}
.hero-bar-open:hover .hero-bar-more {
  color: var(--text-secondary);
}

/*
 * The register inside the dialog: the same rows beside the same graphic, laid out the way
 * `hero--split` lays them out on the page.
 *
 * Not `.hero` — that class carries the page block's padding, hairline rule and bottom margin, all
 * of which the dialog body supplies itself. The rows below it (`hero-top`, `hero-detail`,
 * `hero-figures`) are top-level selectors and do not need a `.hero` ancestor to style.
 */
.hero-dialog {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-6);
}
.hero-dialog-main {
  flex: 1;
  min-width: 0;
}
/*
 * The figures row is in a narrow container here, so it takes the narrow container's gap — the same
 * `--space-5` the compact breakpoint gives it on the page, for the same reason. At the page's
 * `--space-8` the fifth count drops to a line of its own beside three-quarters of a row of
 * whitespace, which reads as a layout fault rather than as the quiet caption it is.
 */
.hero-dialog .hero-figures {
  -moz-column-gap: var(--space-5);
       column-gap: var(--space-5);
}
@media (max-width: 620px) {
  .hero-dialog {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-4);
  }
}

/* ================================================================== the signed-out surface
 *
 * Login is the one product page whose job is marketing's job — a first impression with no data
 * density to protect — so it carries the marketing site's ground: the page colour with two dim
 * accent orbs. Tokens, not hex, except the orb tints: they are the accent at alphas that read as
 * a glow on the dark ground and as a faint wash on the light one, and no token encodes "the same
 * hue at two decorative alphas".
 */
.auth-backdrop {
  background-color: var(--bg-page);
  background-image:
    radial-gradient(38rem 26rem at 28% 30%, rgba(91, 141, 239, 0.16), transparent 65%),
    radial-gradient(30rem 22rem at 72% 68%, rgba(143, 179, 245, 0.10), transparent 60%);
}

/*
 * The sign-in fields.
 *
 * These lived in an inline `<style>` inside `apps/portal/app/login/page.tsx` until 2026-08-26 and
 * moved here for a concrete reason: a `<style>` element's text content inside the page's Suspense
 * boundary hydrated differently from the server's copy, and React tore the whole boundary down to
 * re-render on the client ("Text content did not match" -> "Switched to client rendering"). The
 * sign-in form is the first thing anyone sees; it should not be the one route that hydrates twice.
 *
 * The literal colours are deliberate and match the page's own comment: this screen is a white card
 * on black regardless of the viewer's theme, so it must not read theme tokens.
 */

/* Autofill paints its own background over the field and washes the text out with it (Chrome renders
   pale grey-on-grey). `-webkit-text-fill-color` plus a field-sized inset shadow are the only way to
   override that paint — neither is reachable from a plain `color`/`background`. */
.auth-input:-webkit-autofill,
.auth-input:-webkit-autofill:hover,
.auth-input:-webkit-autofill:focus {
  -webkit-text-fill-color: #111;
  box-shadow: 0 0 0 1000px #fff inset;
  caret-color: #111;
}

.auth-input:focus-visible {
  outline: none;
  border-color: #111;
  box-shadow: 0 0 0 3px rgba(17, 17, 17, 0.15);
}

/* Both shadows, or the autofill rule above wins and the focus ring vanishes on a filled field. */
.auth-input:-webkit-autofill:focus {
  box-shadow: 0 0 0 3px rgba(17, 17, 17, 0.15), 0 0 0 1000px #fff inset;
}

.auth-wordmark {
  font-size: 32px;
  font-weight: 650;
  letter-spacing: -0.03em;
  line-height: 1.1;
}

/* The audience line, set like the marketing site's eyebrow: microscopic, spaced, quiet. */
.auth-audience {
  margin-top: var(--space-2);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* The scene layer inside .auth-backdrop: behind the card, never catching a click. */
.auth-scene {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/*
 * The entrance — framer-motion's staggered rise, in CSS. Each child of the signed-out column
 * fades up once on load; `both` keeps them invisible until their delay arrives. This is a
 * one-shot, not a loop, so it does not join the shimmer/spinner/live-dot register.
 */
@keyframes auth-rise {
  from {
    opacity: 0;
    transform: translateY(16px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
.auth-rise > * {
  animation: auth-rise 0.7s cubic-bezier(0.32, 0.72, 0, 1) both;
}
.auth-rise > *:nth-child(2) { animation-delay: 0.08s; }
.auth-rise > *:nth-child(3) { animation-delay: 0.16s; }
.auth-rise > *:nth-child(4) { animation-delay: 0.24s; }

@media (prefers-reduced-motion: reduce) {
  .auth-rise > * {
    animation-duration: 0.2s;
    animation-delay: 0s;
    transform: none;
  }
}


/* ------------------------------------------------------------------ overview infrastructure radar */

/*
 * The Overview's graphical opener. Theme-native: dark mode is the reference NOC look, light mode
 * the same radar in the light palette. The band paints its own ground explicitly all the same — a
 * surface that borrows whatever sits behind it is one refactor away from vanishing.
 */
.ov-hero {
  background: var(--bg-page);
  color: var(--color-text);
  /*
   * No border, and that is the single largest change on this page.
   *
   * This band held a section label, eight tiles, a prose panel and a card — every one of which
   * already has an edge of its own — inside one more rounded rectangle, which sat inside the
   * region's rounded rectangle. Three nested containers to introduce content that was already
   * grouped by its own layout. The band still paints its own ground and still keeps its inset, so
   * its content shares the left and right rules of the cards above it; what it no longer does is
   * draw a box to say so.
   */
  border-radius: var(--radius-card);
  /*
   * No inset either, and this is the other half of dropping the border.
   *
   * While the band was a surface, 20px of padding is what put its content on the same left and
   * right rules as the content of the cards above it. With no border the band is not a surface any
   * more — the tiles, the watch panel and the estate card are — so the thing that has to line up is
   * their *edges* against the cards' edges, and an inset put them 20px inside. Measured at 1600px:
   * the estate card's right edge stopped 20px short of the greeting card's, which is the kind of
   * misalignment that reads as a page that is out of true without anyone being able to say why.
   */
  padding: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: var(--space-5);
  margin-bottom: var(--space-4);
}
@media (max-width: 1100px) {
  .ov-hero {
    grid-template-columns: minmax(0, 1fr);
  }
}

.ov-board-col {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  min-width: 0;
}
.ov-map-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
}
/*
 * A section label, not a sentence.
 *
 * Uppercase and tracked at caption size, which is the one treatment that reads as a heading without
 * competing for size with the eight figures underneath it — the figures are what this section is
 * for, and a 16px title above them was a second thing asking to be read first.
 */
.ov-map-title {
  font-size: var(--text-sm);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.085em;
  color: var(--color-text);
}
.ov-map-sub {
  margin-top: 3px;
  font-size: var(--text-xs);
  color: var(--color-text-subtle);
}
/* The estate's own name, which is the one part of that line worth picking out. */
.ov-map-source { color: var(--text-secondary); font-weight: 600; }
.ov-health {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--text-xs);
  font-weight: 500;
  white-space: nowrap;
}
.ov-health--ok { color: var(--severity-ok-fg); }
.ov-health--warning { color: var(--severity-warning-fg); }
.ov-health--critical { color: var(--severity-critical-fg); }
.ov-health--info { color: var(--color-text-muted); }

/*
 * The two verdicts in the panel's corner.
 *
 * Kept side by side and never merged. `.ov-health` is arithmetic over the counts the radar is
 * already drawing; `.ov-watch-chip` is the state of a model. A single blended word would be
 * traceable to neither, which is what an "AI health score" is.
 *
 * `flex-wrap` rather than `nowrap`: at the narrow breakpoint the two together are wider than the
 * title beside them, and a nowrap row would push the panel's own header out of the card.
 */
.ov-verdicts {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: var(--space-2);
  min-width: 0;
}

/*
 * The AI chip: a pill, not bare coloured text.
 *
 * It sits directly beside `.ov-health`, which *is* bare coloured text, and the difference is the
 * point — one is a reading of the numbers on screen and the other is a claim about a background
 * process the reader cannot see. The ground is what separates them at a glance.
 */
.ov-watch-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 9px;
  border-radius: var(--radius-full);
  font-size: var(--text-xs);
  font-weight: 500;
  white-space: nowrap;
  border: 1px solid transparent;
}
.ov-watch-chip--ok {
  background: var(--severity-ok-bg);
  color: var(--severity-ok-fg);
}
.ov-watch-chip--info {
  background: var(--severity-info-bg);
  color: var(--severity-info-fg);
}
.ov-watch-chip--warning {
  background: var(--severity-warning-bg);
  color: var(--severity-warning-fg);
}
.ov-watch-chip--critical {
  background: var(--severity-critical-bg);
  color: var(--severity-critical-fg);
  border-color: var(--severity-critical-fill);
}
/* The age of the last look, subordinate to the state word it qualifies. */
.ov-watch-chip-when {
  font-weight: 400;
  opacity: 0.8;
}

/*
 * The band under the radar: what the model has actually said about this cluster.
 *
 * A left accent rather than a full tint. The paragraph inside is prose the reader has to actually
 * read, and a coloured ground behind body text costs contrast for a signal the accent already
 * carries — `tokens.test.ts` holds every tier to a floor on every ground, and this is the surface
 * where that floor matters most.
 */
/*
 * The band takes whatever the hero row is taller than the board column, and its footer sits at the
 * foot of it.
 *
 * `.ov-hero` is a grid, so both columns are the height of the taller one, and which one that is
 * depends on the reader's data. The rail already had a mechanism for being the shorter one - its
 * cards grow, further down - and the board column had none, so when the rail won (601px of gauges
 * and facts against 489px of tiles) 152px of empty ground sat below this band and the column read
 * as having stopped early.
 *
 * The slack lands here rather than in the tiles because this is the one elastic thing in the
 * column: an instrument tile given 90px of it has a hole between its caption and its bar and
 * nothing to put in it, whereas a prose panel with its footer on the floor is a panel with room.
 */
.ov-watch {
  flex: 1 1 auto;
  display: flex;
  min-width: 0;
}
.ov-watch-body {
  border: 1px solid color-mix(in srgb, var(--border-hairline) 68%, transparent);
  /* The accent is the whole of the colour on this panel. A tinted ground behind a paragraph the
     reader has to actually read costs contrast for a signal three pixels already carry. */
  border-left-width: 3px;
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  padding: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
  /* `.ov-watch` is a flex row with one child, so without this the panel is only as wide as its
     longest line — and only as tall as its text, which is the height the band exists to spend. */
  flex: 1 1 auto;
}
.ov-watch-body--ok { border-left-color: var(--severity-ok-fill); }
.ov-watch-body--info { border-left-color: var(--severity-info-fill); }
.ov-watch-body--warning { border-left-color: var(--severity-warning-fill); }
.ov-watch-body--critical { border-left-color: var(--severity-critical-fill); }

.ov-watch-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  min-width: 0;
}
.ov-watch-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: var(--radius-full);
  flex: none;
}
.ov-watch-mark--ok {
  background: var(--severity-ok-bg);
  color: var(--severity-ok-fg);
}
.ov-watch-mark--info {
  background: var(--severity-info-bg);
  color: var(--severity-info-fg);
}
.ov-watch-mark--warning {
  background: var(--severity-warning-bg);
  color: var(--severity-warning-fg);
}
.ov-watch-mark--critical {
  background: var(--severity-critical-bg);
  color: var(--severity-critical-fg);
}
.ov-watch-word {
  font-size: var(--text-sm);
  font-weight: 600;
}
/* Which runtime. A fact, so it is not styled as a heading. */
.ov-watch-model {
  font-size: var(--text-xs);
  color: var(--color-text-subtle);
  min-width: 0;
  overflow-wrap: anywhere;
}
/*
 * Whether this cluster's telemetry leaves the customer's network.
 *
 * Its own mark rather than the fourth clause of the runtime caption — see the note at the call
 * site. `in` is deliberately not green: staying inside the network is the normal case and the
 * default configuration, and a green badge on every screen for the whole of normal operation is a
 * badge nobody reads by the second day. `out` is amber, which is what it is — a decision that was
 * made and acknowledged, not a fault.
 */
.ov-watch-net {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  border-radius: var(--radius-full);
  font-size: var(--text-xs);
  white-space: nowrap;
  border: 1px solid transparent;
}
.ov-watch-net--in {
  background: var(--bg-sunken);
  color: var(--color-text-muted);
  border-color: color-mix(in srgb, var(--border-hairline) 70%, transparent);
}
.ov-watch-net--out {
  background: var(--severity-warning-bg);
  color: var(--severity-warning-fg);
}
.ov-watch-more {
  margin-left: auto;
  font-size: var(--text-xs);
  white-space: nowrap;
}

/*
 * The reason a watch is not watching.
 *
 * Body colour, not muted. It is the most important sentence this panel can print and muting it
 * would file it as a caption — which is how a stalled watch reads as a decorative subtitle.
 */
.ov-watch-reason {
  margin: 0;
  font-size: var(--text-sm);
  line-height: 1.55;
  color: var(--color-text);
}
/*
 * Clamped to three lines while the detail is folded away, and never hidden.
 *
 * The distinction matters and is the reason this is a clamp rather than a disclosure: the sentence
 * that says the watch has stopped is the most important thing this panel can print, and the whole
 * reason the `stalled` state exists is that a dead worker is otherwise invisible. So it always
 * renders, always in body colour, and always from its first word — what folding buys is that a
 * five-line paragraph does not set the height of the band above eight instruments. Opening the
 * details releases it.
 */
.ov-watch-body:not(.is-open) .ov-watch-reason {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.ov-watch-review {
  margin: 0;
  font-size: var(--text-sm);
  line-height: 1.6;
  color: var(--color-text);
}
.ov-watch-attrib {
  margin: 0;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

.ov-watch-findings {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.ov-watch-finding {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  min-width: 0;
}
/* The dot is absolutely positioned for its badge use; inline here, beside a line of text. */
.ov-watch-finding .ov-dot {
  position: static;
  border: 0;
  width: 7px;
  height: 7px;
  margin-top: 6px;
  flex: none;
}
.ov-watch-finding-body {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}
.ov-watch-finding-what {
  font-size: var(--text-xs);
  color: var(--color-text);
  overflow-wrap: anywhere;
}
.ov-watch-finding-note {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  line-height: 1.5;
}

/*
 * The footer, which is the honesty rather than a legend.
 *
 * It names what detects (arithmetic), what the model does (narrate), how many passes run without a
 * model at all, and how much of today's budget is spent. Every figure in it comes from the payload,
 * so none of it can become a stale claim about a build that has changed underneath it.
 */
/* On the floor of the band, which is what makes the height above read as room rather than as a
   gap. It is the panel's footnote in the first place - the sentence that stops the words above it
   being read as "the AI monitors your cluster". */
.ov-watch-foot {
  /* `auto` on top, zero on the rest — as one shorthand, because a `margin-top: auto` written above
     the `margin: 0` that used to be here is silently reset by it. */
  margin: auto 0 0;
  padding-top: var(--space-2);
  border-top: 1px solid var(--border-hairline);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  line-height: 1.5;
}

/*
 * The status board: one tile per instrument, on a grid.
 *
 * ## Why this is not a radar any more
 *
 * It was a circle of stations around a glowing core, and it had three problems that were properties
 * of the shape rather than of the drawing. A ring is a fixed square in a panel that is not square,
 * so the wider the screen the more of this band was empty ground either side of it — the alignment
 * complaint that started this. A circle has no reading order, so eight figures had no first one. And
 * the depth that made it look like an instrument came from a fixed `rgba(0, 0, 0, 0.45)` vignette,
 * which is the one thing in this file that could not follow the theme: over the light ground it was
 * a grey wash across the whole panel.
 *
 * A grid answers all three. It fills whatever width it is given, it reads left to right, and it is
 * painted in tokens, so the light theme gets a light board rather than a dark one behind glass.
 *
 * ## The tile is a link and says what it is
 *
 * Figure, label, one line of context, and a bar carrying the share of that instrument's population
 * that needs attention — one meaning in one place across all eight. A tile with nothing to divide by
 * draws a dashed rule instead of an empty track: nought per cent and "never counted" must not look
 * the same, which is this surface's whole rule applied to four pixels.
 */
.ov-board {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-3);
}
/*
 * Four columns for as long as the rail is beside the board, then two, then one.
 *
 * There used to be a step at 1400 that dropped the board to two columns while the rail was still
 * beside it, and it produced the worst layout on the page. Two tiles in an 880px column is a 430px
 * tile carrying a 32px number, and eight of them is four rows — so the board column stood ~640px
 * tall against a rail whose content is ~400px, and the rail's own mechanism for being the shorter
 * column (its card grows, its facts spread) then pushed four facts 90px apart. Measured at 1280px.
 *
 * The step existed to keep tiles from getting too narrow, and at four columns they do not: the
 * board column bottoms out at ~700px just before the rail stacks, which is 175px a tile against a
 * 100px figure and a 118px head. So the step is gone rather than tuned, and the two heights stay
 * comparable at every width where they sit side by side.
 */
@media (max-width: 1100px) {
  .ov-board { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@media (max-width: 900px) {
  .ov-board { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 520px) {
  .ov-board { grid-template-columns: minmax(0, 1fr); }
}

.ov-tile {
  position: relative;
  /*
   * A column with the meter pinned to its foot, so eight tiles put their eight bars on one line.
   *
   * It was a grid with `align-content: start`, which stacks each child under the one above it and
   * lands the bar wherever the label before it happened to end. Every label is one line *today*, so
   * the row looked level - but one wrapping label drops that tile's bar 17px below its neighbours',
   * and a tile that grows (the board now takes the hero's spare height, below) leaves its bar
   * floating in the middle of the box. Pinning the foot is what makes the row survive both.
   */
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-4);
  /*
   * A weaker edge than the hairline the rest of the product uses.
   *
   * Eight tiles in a grid draw eight full rectangles, and at full hairline strength the grid reads
   * as a table of boxes rather than as eight figures — the lines become the pattern and the numbers
   * become what is inside it. At ~70% the edge still separates two adjacent tiles and stops being
   * the first thing the eye lands on. Derived from the token rather than written as an alpha, so it
   * cannot drift from whatever the hairline becomes.
   */
  border: 1px solid color-mix(in srgb, var(--border-hairline) 68%, transparent);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  color: var(--color-text);
  text-decoration: none;
  min-width: 0;
  transition:
    background var(--motion-fast) var(--ease-out),
    border-color var(--motion-fast) var(--ease-out),
    transform var(--motion-fast) var(--ease-out);
}
.ov-tile:hover {
  background: var(--bg-surface);
  border-color: var(--border-strong);
  /*
   * The global `a:hover` underlines, and a tile is an anchor.
   *
   * So pointing at Alerts underlined the label, the 142 and the caption all at once — a whole panel
   * dressed as a hyperlink, and a 32px figure with a rule through its descenders. The tile says it
   * is a target with its border, its lift and its bar; it does not need to say it a fourth time in
   * a way that damages the one thing on it worth reading.
   */
  text-decoration: none;
  /* A pixel, not a shadow. `tokens.test.ts` disallows `box-shadow` outside the `--elev-*` tiers,
     and a tile that rises by one pixel under the pointer is the whole of the affordance anyway. */
  transform: translateY(-1px);
}
/* The instrument's own mark comes forward with the pointer, so the foot of the tile answers the
   hover rather than only its border. */
.ov-tile:hover .ov-tile-meter,
.ov-tile:focus-visible .ov-tile-meter,
.ov-tile:hover .ov-tile-level,
.ov-tile:focus-visible .ov-tile-level {
  background: color-mix(in srgb, var(--color-text) 12%, transparent);
}
.ov-tile:hover .ov-tile-spread-bar,
.ov-tile:focus-visible .ov-tile-spread-bar {
  background: color-mix(in srgb, var(--color-text) 42%, transparent);
}
.ov-tile:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

.ov-tile-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}

/* The badge carries the instrument's tone as a wash of its own colour rather than as a fill: eight
   saturated squares on one board is a set of warnings, and most of these are only counts. */
.ov-tile-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: var(--radius-md);
  color: var(--ov-tone);
  background: color-mix(in srgb, var(--ov-tone) 12%, transparent);
}
.ov-tile-badge--ok { --ov-tone: var(--severity-ok-fg); }
.ov-tile-badge--info { --ov-tone: var(--severity-info-fg); }
.ov-tile-badge--warning { --ov-tone: var(--severity-warning-fg); }
.ov-tile-badge--critical { --ov-tone: var(--severity-critical-fg); }

.ov-tile-label {
  font-size: var(--text-base);
  font-weight: 500;
  color: var(--color-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Inline at the end of the head row rather than pinned to a badge corner. */
.ov-tile-head .ov-dot {
  position: static;
  border: 0;
  width: 7px;
  height: 7px;
  margin-left: auto;
  flex: none;
}

.ov-tile-value {
  /* The one number on the tile, and the only thing on this board set above body size. */
  font-size: var(--text-3xl);
  font-weight: 600;
  line-height: var(--leading-hero);
  letter-spacing: -0.03em;
  margin-top: var(--space-3);
}
.ov-tile-sub {
  font-size: var(--text-xs);
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  /* Was `margin-top` on the bar below. Moved here so the bar can claim `margin-top: auto` and still
     keep its distance from the caption when the tile has no spare height to give it. */
  margin-bottom: var(--space-2);
}

.ov-tile-meter {
  position: relative;
  height: 5px;
  margin-top: auto;
  border-radius: var(--radius-full);
  /* Derived from the text colour rather than `--bg-sunken`: on a light card the sunken grey and the
     card are four points apart, so the unfilled part of the track was invisible and a 3% share read
     as a floating sliver with nothing to be a share *of*. */
  background: color-mix(in srgb, var(--color-text) 9%, transparent);
  overflow: hidden;
  transition: background var(--motion-fast) var(--ease-out);
}
.ov-tile-fill {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  border-radius: inherit;
  background: var(--ov-tone);
  transition: width var(--motion-base) var(--ease-out);
}
.ov-tile-fill--ok { --ov-tone: var(--severity-ok-fill); }
.ov-tile-fill--info { --ov-tone: var(--accent); }
.ov-tile-fill--warning { --ov-tone: var(--severity-warning-fill); }
.ov-tile-fill--critical { --ov-tone: var(--severity-critical-fill); }

/* Nothing to divide by. A dashed rule reads as "not measured"; an empty track would read as none. */
.ov-tile-none {
  display: block;
  height: 5px;
  margin-top: auto;
  /* Border-box, so the unmeasured slot is the same height as a meter and the dashed rule lands on
     the same line as the bars beside it. It was `4px + a 1px border` offset by a compensating
     `calc(var(--space-2) + 1px)` margin, which put it 1px below every bar in its row. */
  box-sizing: border-box;
  border-top: 1px dashed var(--border-hairline);
}

/* ---- the instrument shapes -------------------------------------------------------------------
 *
 * Three quantities the share bar cannot carry, each drawn so it cannot be mistaken for the bar or
 * for the other two. See the `Viz` type in apps/portal/app/overview-hero.tsx for why a second
 * quantity gets a different shape rather than a second bar.
 *
 * All three are pinned to the foot with `margin-top: auto`, exactly as the meter and the dashed
 * slot are, so the eight feet sit on one line whatever each tile put above it.
 */

/* A level: how full, against an installed capacity. Taller than the share bar and fully rounded,
   because it is read as a quantity of something rather than as a proportion that is wrong. */
/*
 * The level is ruled into quarters, and that is what stops it being mistaken for a share bar.
 *
 * Drawn as a plain rounded track it was the same object as the meter beside it in a different
 * colour — 11% claimed-and-used next to 11% unhealthy, two incomparable quantities that a reader
 * would have every reason to compare. The doctrine this board was built on says two quantities in
 * one position must not look alike; a ticked track reads as a scale being filled, which is what
 * fullness is, and a smooth one reads as a proportion, which is what a share is.
 */
.ov-tile-level {
  position: relative;
  display: block;
  height: 6px;
  margin-top: auto;
  border-radius: 2px;
  background: color-mix(in srgb, var(--color-text) 9%, transparent);
  overflow: hidden;
  transition: background var(--motion-fast) var(--ease-out);
}
/*
 * The quarter marks, as three elements rather than a repeating gradient.
 *
 * The gradient was the obvious way to rule a track and it is refused by `tokens.test.ts`, which
 * bans gradients on data surfaces — rightly, and it does not have a carve-out for "but mine is the
 * mark rather than the decoration" because every gradient added to this stylesheet would arrive
 * with one. Three spans cost three DOM nodes and say plainly what they are.
 */
.ov-tile-tick {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: color-mix(in srgb, var(--color-text) 20%, transparent);
}
.ov-tile-level-fill {
  position: absolute;
  inset: 0 auto 0 0;
  border-radius: 2px 0 0 2px;
  background: var(--ov-tone);
  transition: width var(--motion-base) var(--ease-out);
}
/* The level's own tone: a store at 94% is a warning whatever the claim count above it says. */
.ov-tile-level-fill--ok { --ov-tone: var(--severity-info-fill); }
.ov-tile-level-fill--info { --ov-tone: var(--severity-info-fill); }
.ov-tile-level-fill--warning { --ov-tone: var(--severity-warning-fill); }
.ov-tile-level-fill--critical { --ov-tone: var(--severity-critical-fill); }

/*
 * Pips: one mark per node, while there are few enough to count.
 *
 * `1fr` columns rather than fixed widths, so six marks and eleven marks both span the tile and the
 * row of feet stays level. The unlit ones carry the tile's tone — a dark pip among five lit ones is
 * the machine that is out, which is the reading a 17%-wide bar cannot give.
 */
.ov-tile-pips {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 3px;
  height: 5px;
  margin-top: auto;
}
.ov-tile-pip {
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--ov-tone) 34%, transparent);
  transition: background var(--motion-fast) var(--ease-out);
}
.ov-tile-pip.is-on {
  background: var(--severity-ok-fill);
}
.ov-tile-pips--ok { --ov-tone: var(--severity-ok-fg); }
.ov-tile-pips--info { --ov-tone: var(--severity-info-fg); }
.ov-tile-pips--warning { --ov-tone: var(--severity-warning-fill); }
.ov-tile-pips--critical { --ov-tone: var(--severity-critical-fill); }

/*
 * A spread: the distribution of a population, tallest first.
 *
 * Columns rather than a bar, because the quantity is a *shape* — whether seventy-eight namespaces
 * are comparable tenants or three real ones and seventy-five empty. It is taller than the other
 * feet for the same reason, and `align-items: flex-end` is what puts every column on the same floor
 * as the bars beside it in the other tiles.
 */
.ov-tile-spread {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  /*
   * Ten pixels, and the ceiling is what keeps the row of feet level rather than what keeps the
   * shape subtle. Every foot is bottom-aligned by `margin-top: auto`, so a taller one does not
   * break the line — it makes its own tile taller, and the grid then gives that height to all four
   * tiles in the row. At 10px the difference is invisible; at the 18px this started as, the other
   * three tiles carried a visible hole between their caption and their bar.
   */
  height: 10px;
  margin-top: auto;
}
.ov-tile-spread-bar {
  flex: 1 1 0;
  min-width: 2px;
  border-radius: 1px;
  background: color-mix(in srgb, var(--color-text) 34%, transparent);
  transition: background var(--motion-fast) var(--ease-out);
}

/*
 * The sentence behind the figure, on hover or focus.
 *
 * Out of flow, so nothing on the board moves when it appears, and below the tile because the row
 * above it is the panel's own header. `.ov-hero` does not clip, so the bottom row's tip has room.
 */
.ov-tile-tip {
  position: absolute;
  top: calc(100% + 6px);
  left: var(--space-2);
  right: var(--space-2);
  z-index: var(--z-raised);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  line-height: var(--leading-body);
  box-shadow: var(--elev-overlay);
  opacity: 0;
  visibility: hidden;
  transition:
    opacity var(--motion-fast) var(--ease-out),
    visibility var(--motion-fast) var(--ease-out);
}
.ov-tile:hover .ov-tile-tip,
.ov-tile:focus-visible .ov-tile-tip {
  opacity: 1;
  visibility: visible;
}
/* The tip's z-index only orders it inside its own tile. The hovered tile lifts (`transform`), which
   makes it a stacking context at the same level as its neighbours, so the tile after it in the grid
   painted over the tip's lower half — hidden under an opaque tile, and text-over-text under the glass
   theme's translucent one, which is where it was noticed. The tile being read comes forward. */
.ov-tile:hover,
.ov-tile:focus-visible {
  z-index: var(--z-raised);
}

/* Status dots. The pulse is an opacity breath — a painted ring that reads on the dark ground is
   invisible on the light one, while the dot's own severity fill works in both. */
.ov-dot {
  position: absolute;
  top: -1px;
  right: -1px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  border: 2px solid var(--bg-sunken);
}
.ov-dot--ok { background: var(--severity-ok-fill); }
.ov-dot--warning {
  background: var(--severity-warning-fill);
  animation: ov-pulse 2s ease-in-out infinite;
}
.ov-dot--critical {
  background: var(--severity-critical-fill);
  animation: ov-pulse 1.2s ease-in-out infinite;
}
/*
 * Neutral, and it was `var(--accent)`.
 *
 * In the light theme that token is #09090B, so the three tiles with nothing to report — Workloads,
 * Storage, Projects — carried a near-black dot in the corner where the other five carry green,
 * amber and red. On a status board the darkest mark reads as the worst one, so the three counts
 * that have no unwell state at all were the loudest things on it. It survived this long because in
 * the dark theme the same token is #FAFAFA, where a pale dot reads as "quiet" rather than as
 * "urgent", and the board was designed dark.
 *
 * Grey rather than the informational blue: these instruments have no state to report, and blue is
 * this product's word for a signal that *is* informational — a finding below its baseline, a change
 * waiting on somebody. "Nothing to say" and "something mild to say" are different, and the board has
 * three of the first and none of the second.
 */
.ov-dot--info { background: var(--border-strong); }
@keyframes ov-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.4; }
}
/* Inline beside a word rather than pinned to a badge corner. */
.ov-health .ov-dot {
  position: static;
  border: 0;
  width: 7px;
  height: 7px;
}

/* Right column: stacked summary cards, each a link with one small graphic. */
.ov-side {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
}
/*
 * The rail's cards divide the surplus height rather than leaving it under the last one.
 *
 * `.ov-hero` is a grid, so this column is already as tall as the radar beside it - but its two
 * cards are content-height, so ~180px of dead ground sat below Platform and the rail read as
 * having stopped early against a panel that had not. Growing both lands the rail's bottom edge on
 * the radar column's, and the rules below spread each card's own contents, because a taller box
 * whose content still clings to the top just moves the gap inside the card.
 *
 * `flex-basis: auto`, not 0: equal bases would force two halves of one height and squash the
 * gauges into the shorter of them. This keeps each card's natural height and divides only what is
 * left over.
 *
 * All of it is inert below 1100px, where `.ov-hero` collapses to one column and there is no
 * surplus to divide.
 */
.ov-side .card {
  margin: 0;
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
}
.ov-side .card-body {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
}
/*
 * Four gauge rows, centred in whatever height the card ends up with.
 *
 * They were a 2x2 grid of ring-over-label, which needs the width of the ring and is given half the
 * width of a 290px rail - so every caption wrapped: "433 of 1500 schedulable" set on two lines
 * under a 76px ring, four times over, and the wrapping was most of why the rail out-grew the board
 * beside it. As rows the same caption has the rail's full width and fits on one line, the four
 * labels share a left rule and the four readings share a right one, so the card is read down a
 * column instead of hunted around a square.
 */
.ov-side .ov-rail-gauges {
  flex: 1 1 auto;
  align-content: center;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-2);
}
.ov-side .ov-gauge {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 0 var(--space-3);
  text-align: left;
  /* A body for the hover highlight, and the negative inset keeps the ring on the card's own left
     rule so the four rows still read down one column. */
  padding: 4px var(--space-2);
  margin: 0 calc(var(--space-2) * -1);
  border-radius: var(--radius-sm);
  transition: background var(--motion-fast) var(--ease-out);
}
.ov-side .ov-gauge:hover {
  background: var(--bg-hover);
}
.ov-side .ov-gauge svg {
  width: 60px;
  height: 60px;
}
/*
 * A thinner ring, set in CSS over the component's own attribute.
 *
 * `stroke-width` is an SVG presentation attribute, so a stylesheet rule outranks the `5` the
 * component writes — which is what lets this rail thin its rings without changing the gauge
 * everywhere else it is used. Five units at 0.6 scale is a 3px band around a 46px circle, and at
 * that weight four of them stacked read as four doughnuts; the reading is the number in the middle.
 */
.ov-side .ov-gauge svg circle {
  stroke-width: 3.5;
}
/*
 * The viewBox is 76 wide and the ring is drawn at 52, so anything set here is scaled by 0.68 — set
 * against that, not against the 15px the full-size gauge uses, or it renders at 10px.
 *
 * 26 units is 17.7px on screen, which makes the percentage the largest thing in the row and the
 * ring around it the qualifier. That is the right way round: "37%" is the reading, and the arc is
 * how much of a circle 37% is.
 */
.ov-side .ov-gauge-num {
  /*
   * 20 units, and the ceiling is the string "100%".
   *
   * The ring's inner diameter is 56.5 viewBox units, and "100%" was **measured** at 62.5 units when
   * this was 24 — it did not fit, and neither did the 23 that was here before the refinement pass,
   * so the one reading that is four characters long (a store that is completely full, which is the
   * most important number this rail can print) has been colliding with its own ring all along. It
   * is the kind of defect that never appears in a screenshot, because no gauge on a healthy estate
   * is ever at 100%.
   *
   * 20 measures ~52 and clears it with room for a wider fallback face. The ring grew to 60px in the
   * same change so that a smaller unit size is still a larger number on screen: 20 × 60/76 ≈ 15.8px
   * against a 13px label and a hairline arc, which is what makes the percentage the row's anchor.
   */
  font-size: 20px;
  font-weight: 600;
}
/* Three items, three columns, one line: the ring, the resource on the card's left rule, and what
   it is measured against on its right one. Four rows then read down two rules instead of being
   hunted around a square. */
.ov-side .ov-gauge-detail {
  text-align: right;
  color: var(--color-text-subtle);
}
/* The resource is the row's name, so it is set at body weight rather than as a caption under a
   ring — there is no ring above it any more to be a caption for. */
.ov-side .ov-gauge-label {
  font-size: var(--text-base);
  font-weight: 500;
  color: var(--color-text);
}
/*
 * Four facts as four equal bands, divided by the hairline this product separates anything with.
 *
 * `justify-content: space-between` was the obvious way to fill the card and the wrong one: four
 * 17px rows over ~190px of body puts 40px of nothing between each, and rows floating that far
 * apart read as the alignment problem rather than the fix. Equal bands with a rule between them
 * are the same list at the same density the tables use, and the last band's baseline lands on the
 * card's padding rather than wherever the spare space happened to end.
 */
.ov-side .ov-facts {
  flex: 1 1 auto;
  gap: 0;
  /* The one division the merged rail needs. A second card heading would put back exactly the chrome
     the merge removed; a hairline says "measurements above, facts below" for the price of a pixel. */
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border-hairline);
}
.ov-side .ov-facts > div {
  flex: 1 1 auto;
  align-items: center;
  padding: var(--space-2);
  margin: 0 calc(var(--space-2) * -1);
  border-radius: var(--radius-sm);
  transition: background var(--motion-fast) var(--ease-out);
}
.ov-side .ov-facts > div:hover {
  background: var(--bg-hover);
}
/* A hairline at ~55%: four of them inside a card that already has an edge is four more lines than
   a list of four facts needs at full strength. */
.ov-side .ov-facts > div + div {
  border-top: 1px solid color-mix(in srgb, var(--border-hairline) 55%, transparent);
}
.ov-side .ov-facts dt {
  font-size: var(--text-base);
  color: var(--color-text-muted);
}
.ov-side .ov-facts dd {
  font-size: var(--text-base);
  font-weight: 500;
}
.ov-sum {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-card);
  background: var(--bg-surface);
  color: var(--color-text);
  text-decoration: none;
  transition: border-color 140ms var(--ease-out);
}
.ov-sum:hover,
.ov-sum:focus-visible {
  border-color: var(--border-strong);
}
.ov-sum:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}
.ov-sum-body {
  min-width: 0;
}
.ov-sum-label {
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}
.ov-sum-row {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin-top: 2px;
}
.ov-sum-value {
  font-size: var(--text-2xl);
  font-weight: 600;
  line-height: 1.1;
}
.ov-sum-status {
  font-size: var(--text-xs);
  font-weight: 500;
}
.ov-sum-status--ok { color: var(--severity-ok-fg); }
.ov-sum-status--warning { color: var(--severity-warning-fg); }
.ov-sum-status--critical { color: var(--severity-critical-fg); }
.ov-sum-status--info { color: var(--color-text-muted); }
.ov-sum-detail {
  margin-top: 2px;
  font-size: 11px;
  color: var(--color-text-subtle);
  overflow: hidden;
  text-overflow: ellipsis;
}
.ov-sum-figure {
  flex: none;
}
.ov-statusring {
  position: relative;
  display: inline-flex;
}
.ov-statusring-glyph {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-text-muted);
}

/* Capacity rings in the side column. */
.ov-rings {
  display: flex;
  gap: var(--space-4);
}
.ov-ring {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  text-align: center;
}
.ov-ring-num {
  fill: var(--color-text);
  font-size: 13px;
  font-weight: 600;
}
.ov-ring-label {
  font-size: var(--text-xs);
  font-weight: 500;
}
.ov-ring-detail {
  font-size: 11px;
  color: var(--color-text-subtle);
}

/* The Overview row that pairs the fleet chart with the event feed. */
.ov-row {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(280px, 1fr);
  gap: var(--space-4);
  align-items: start;
}
@media (max-width: 1100px) {
  .ov-row {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Recent events: a quiet list, one row per event. */
.ov-event-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--text-xs);
}
.ov-event-filter {
  height: var(--size-control-sm);
  padding: 0 var(--space-2);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  cursor: pointer;
}
.ov-event-filter[aria-pressed="true"] {
  border-color: var(--border-strong);
  color: var(--color-text);
}
.ov-events {
  list-style: none;
  margin: 0;
  padding: var(--space-1) 0;
}
.ov-event {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-4);
  border-top: 1px solid var(--border-hairline);
}
.ov-event:first-child {
  border-top: 0;
}
.ov-event-icon {
  display: inline-flex;
  margin-top: 2px;
}
.ov-event-icon--ok { color: var(--severity-ok-fg); }
.ov-event-icon--warning { color: var(--severity-warning-fg); }
.ov-event-body {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
  flex: 1;
}
.ov-event-title {
  font-size: var(--text-sm);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ov-event-name {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}
.ov-event-sub {
  font-size: var(--text-xs);
  color: var(--color-text-subtle);
}
.ov-event-when {
  font-size: var(--text-xs);
  color: var(--color-text-subtle);
  white-space: nowrap;
  margin-top: 2px;
}

/*
 * Nothing on this board is conveyed only by movement, so all of it can stop.
 *
 * The dots keep their colour and the bars keep their width — the pulse and the sweep of a bar into
 * place are ambience. What went with the radar was a rotating beam; what is left is smaller and
 * still has to answer this, because a status board that breathes at somebody who asked it not to is
 * the same failure at a lower amplitude.
 */
@media (prefers-reduced-motion: reduce) {
  .ov-dot--warning,
  .ov-dot--critical {
    animation: none;
  }
  .ov-tile,
  .ov-tile-fill,
  .ov-tile-tip,
  .ov-tile-meter,
  .ov-tile-level,
  .ov-tile-level-fill,
  .ov-tile-pip,
  .ov-tile-spread-bar,
  .ov-refresh-btn,
  .needsyou-bar,
  .needsyou-pill,
  .ov-side .ov-gauge,
  .ov-side .ov-facts > div {
    transition: none;
  }
  .ov-tile:hover {
    transform: none;
  }
  /*
   * The spinner stops and the control dims instead.
   *
   * Not `animation: none` alone: the rotation is the only thing that says a refresh is in flight,
   * and removing it without a replacement leaves a reader who asked for stillness with a button
   * that looks idle while it is working. A change of opacity is a state, not motion.
   */
  .ov-refresh-icon.is-busy {
    animation: none;
    opacity: 0.45;
  }
}

/* ------------------------------------------------------------------ overview instrument panels */

/* Two panels of equal weight; three where the row carries thirds. Both collapse to one column
   before the panels get too narrow to read rather than at a device breakpoint. */
.ov-row--even {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.ov-row--thirds {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
@media (max-width: 1250px) {
  .ov-row--thirds {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (max-width: 900px) {
  .ov-row--even,
  .ov-row--thirds {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Radial gauges with their trend beneath. */
.ov-gauges {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: var(--space-4);
}
.ov-gauge {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  text-align: center;
  min-width: 0;
}
.ov-gauge-num {
  fill: var(--color-text);
  font-size: 15px;
  font-weight: 600;
}
.ov-gauge-num--warning { fill: var(--severity-warning-fg); }
.ov-gauge-num--critical { fill: var(--severity-critical-fg); }
.ov-gauge-label {
  font-size: var(--text-sm);
  font-weight: 500;
}
.ov-gauge-detail {
  font-size: 11px;
  color: var(--color-text-subtle);
}
.ov-gauge-trend {
  margin-top: var(--space-1);
  min-height: 26px;
}

/* Allocation bars: label, figures, percentage, then the track. */
.ov-allocs {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
/* The row wrapper. It had no rule at all — the spacing between rows comes from `.ov-allocs`'s own
   `gap`, so nothing looked obviously wrong and the missing declaration below did not announce
   itself. `min-width: 0` is what lets `.ov-alloc-label` (flex: 1) actually ellipsis: without it the
   label's automatic minimum is its content width, so a long resource name widens the row and pushes
   the figures and percentage off the right edge of the card instead of truncating. */
.ov-alloc {
  min-width: 0;
}
.ov-alloc-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin-bottom: 6px;
}
.ov-alloc-label {
  font-size: var(--text-sm);
  flex: 1;
  min-width: 0;
}
.ov-alloc-figures {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.ov-alloc-pct {
  font-size: var(--text-sm);
  font-weight: 600;
  min-width: 40px;
  text-align: right;
}
.ov-alloc-track {
  height: 6px;
  border-radius: 3px;
  background: var(--border-hairline);
  overflow: hidden;
}
.ov-alloc-fill {
  height: 100%;
  border-radius: 3px;
}
.ov-alloc-note {
  margin-top: 4px;
  font-size: 11px;
  color: var(--color-text-subtle);
}

/* Top projects: a four-column grid, not a table — the bars are the point. */
.ov-proj {
  display: flex;
  flex-direction: column;
}
.ov-proj-head,
.ov-proj-row {
  display: grid;
  grid-template-columns: minmax(80px, 1.4fr) repeat(3, minmax(64px, 1fr));
  gap: var(--space-3);
  align-items: center;
}
.ov-proj-head {
  padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--border-hairline);
  font-size: 11px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}
.ov-proj-row {
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border-hairline);
  color: var(--color-text);
  text-decoration: none;
}
.ov-proj-row:last-child {
  border-bottom: 0;
}
.ov-proj-row:hover,
.ov-proj-row:focus-visible {
  background: var(--color-hover);
}
.ov-proj-row:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: -2px;
}
.ov-proj-name {
  font-size: var(--text-xs);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ov-share {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}
.ov-share-value {
  font-size: 11px;
  color: var(--color-text-muted);
}
.ov-share-track {
  height: 4px;
  border-radius: 2px;
  background: var(--border-hairline);
  overflow: hidden;
}
.ov-share-fill {
  display: block;
  height: 100%;
  border-radius: 2px;
}

/* Platform health: one row per component, state in words as well as colour. */
.ov-health-list,
.ov-rec-list {
  list-style: none;
  margin: 0;
  padding: var(--space-1) 0;
}
.ov-health-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-4);
  border-top: 1px solid var(--border-hairline);
}
.ov-health-row:first-child {
  border-top: 0;
}
.ov-health-icon {
  display: inline-flex;
  flex: none;
}
.ov-health-icon--ok { color: var(--severity-ok-fg); }
.ov-health-icon--warning { color: var(--severity-warning-fg); }
.ov-health-icon--critical { color: var(--severity-critical-fg); }
.ov-health-icon--info { color: var(--color-text-muted); }
.ov-health-body {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1;
}
.ov-health-name {
  font-size: var(--text-sm);
}
.ov-health-detail {
  font-size: 11px;
  color: var(--color-text-subtle);
}
.ov-health-state {
  font-size: var(--text-xs);
  font-weight: 500;
  white-space: nowrap;
}
.ov-health-state--ok { color: var(--severity-ok-fg); }
.ov-health-state--warning { color: var(--severity-warning-fg); }
.ov-health-state--critical { color: var(--severity-critical-fg); }
.ov-health-state--info { color: var(--color-text-muted); }

/* Recommended actions: severity, what to do, and the subject it was reached from. */
.ov-rec {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--border-hairline);
}
.ov-rec:first-child {
  border-top: 0;
}
.ov-rec-icon {
  display: inline-flex;
  margin-top: 2px;
  flex: none;
}
.ov-rec-icon--critical { color: var(--severity-critical-fg); }
.ov-rec-icon--warning { color: var(--severity-warning-fg); }
.ov-rec-icon--info { color: var(--color-text-muted); }
.ov-rec-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.ov-rec-title {
  font-size: var(--text-sm);
}
.ov-rec-detail {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.ov-rec-subject {
  font-size: 11px;
  color: var(--color-text-subtle);
}

/* ------------------------------------------------------------------ the Overview's own header
 *
 * The one page in the product allowed a title larger than the chrome ceiling. See `OverviewHeader`
 * in apps/portal/app/overview-panels.tsx for why that exemption is this page's and not a relaxation
 * of the rule: summary surfaces have their own type register, operational pages do not.
 *
 * `align-items: flex-end` puts the utility strip on the subtitle's baseline rather than the title's.
 * The strip is a caption about the page — when it was measured, which cluster — so it belongs with
 * the other caption, and hanging it off a 28px title left it floating above a 13px line with 9px of
 * nothing under it.
 */
.ov-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-3) var(--space-5);
}
.ov-head-main {
  min-width: 0;
  /* Wide enough that the tools drop to their own line before the title starts wrapping: a title
     broken across two lines under a strip that still fits is the wrong thing to have given up. */
  flex: 1 1 26rem;
}
.ov-head-title {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--text-2xl);
  font-weight: 600;
  line-height: var(--leading-hero);
  letter-spacing: -0.022em;
  color: var(--color-text);
}
.ov-head-sub {
  margin: var(--space-1) 0 0;
  font-size: var(--text-base);
  line-height: var(--leading-body);
  color: var(--color-text-subtle);
  max-width: 64ch;
}
.ov-head-tools {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-2);
}

/* The page's freshness line and manual refresh, in the header's utility strip. */
.ov-refresh {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.ov-refresh-age {
  font-size: var(--text-xs);
  color: var(--color-text-subtle);
  white-space: nowrap;
  /* Tabular, so "Updated 4s ago" does not jog sideways once a second as the digits change. */
  font-variant-numeric: tabular-nums;
}
/*
 * A control, not an outline.
 *
 * It was a bordered pill the same size as a primary action, which put the page's least consequential
 * button at the top of the page at the weight of its most. This is a quiet chip: no border at rest,
 * a raised ground on hover, and the icon carrying the identity so the word can be small.
 */
.ov-refresh-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: var(--size-control-sm);
  padding: 0 10px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-text-subtle);
  font: inherit;
  font-size: var(--text-xs);
  cursor: pointer;
  transition:
    background var(--motion-fast) var(--ease-out),
    border-color var(--motion-fast) var(--ease-out),
    color var(--motion-fast) var(--ease-out);
}
.ov-refresh-btn:hover:not(:disabled) {
  background: var(--bg-hover);
  border-color: var(--border-hairline);
  color: var(--color-text);
}
.ov-refresh-btn:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}
.ov-refresh-btn:disabled {
  cursor: default;
  color: var(--color-text-subtle);
}
/* The word beside the glyph. It never changes, which is what lets the control keep one width. */
.ov-refresh-word {
  line-height: 1;
}
.ov-refresh-icon {
  display: inline-flex;
  /* The glyph is the only part of the control that moves. A word that changes to "Refreshing…"
     resizes the button under the pointer that is still on it. */
  transform-origin: 50% 50%;
}
.ov-refresh-icon.is-busy {
  animation: ov-spin 900ms linear infinite;
}
@keyframes ov-spin {
  to { transform: rotate(360deg); }
}

/* ------------------------------------------------------------------ page-opener figures */

/*
 * The graphic a `PageHero` may carry in its aside: an arc, its figure, and a caption.
 *
 * One graphic, never a strip of them. The opener's job is the answer at the top of the page, and a
 * row of competing rings there is the eight-tile layout the register was introduced to replace.
 */
/*
 * The split opener: text left, one graphic right, vertically centred against the whole block.
 * Opt-in per call site (`figure`), so `ConsoleHero` and every prose-only opener keep the original
 * block layout — making `.hero` itself a flex row would lay their headings out side by side.
 */
.hero--split {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-6);
}
.hero-main {
  flex: 1;
  min-width: 0;
}
.hero-figure-col {
  flex: none;
}
@media (max-width: 700px) {
  .hero--split {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-4);
  }
}
.hero-figure-ring {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}
.hero-ring-num {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text);
}
.hero-ring-caption {
  font-size: 11px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

/*
 * The ratio bars on a cluster card. Three at most, and only where a denominator genuinely exists —
 * see the card for why a capacity bar is not among them.
 */
.cc-bars {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--space-4);
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-hairline);
}
.cc-bar-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: 4px;
}
.cc-bar-label {
  font-size: 11px;
  color: var(--color-text-muted);
}
.cc-bar-figures {
  font-size: 11px;
  color: var(--color-text-subtle);
}
.cc-bar-track {
  height: 4px;
  border-radius: 2px;
  background: var(--border-hairline);
  overflow: hidden;
}
.cc-bar-fill {
  height: 100%;
  border-radius: 2px;
}

/* The hero rail's gauges: two up, so four fill the rail's height beside the radar. */
.ov-rail-gauges {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3);
}

/* A quiet label/value list for the rail's second card. */
.ov-facts {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0;
}
.ov-facts > div {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
}
.ov-facts dt {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.ov-facts dd {
  margin: 0;
  font-size: var(--text-xs);
  text-align: right;
}
.ov-fact-state--ok { color: var(--severity-ok-fg); }
.ov-fact-state--warning { color: var(--severity-warning-fg); }
.ov-fact-state--critical { color: var(--severity-critical-fg); }
.ov-fact-state--info { color: var(--color-text-muted); }

/* ------------------------------------------------------------------ overview metric strip */

/*
 * Six figures across, each a link. `auto-fit` with a 190px floor rather than a fixed six columns:
 * at 1200px six tiles would be 180px each and the sublines would wrap mid-phrase, so the strip
 * drops to three-up and then two-up instead of squeezing.
 */
.kpi-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}
.kpi {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-3) var(--space-4) var(--space-2);
  background: var(--bg-surface);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-card);
  color: var(--color-text);
  text-decoration: none;
  transition: border-color 140ms var(--ease-out), background 140ms var(--ease-out);
}
.kpi:hover,
.kpi:focus-visible {
  border-color: var(--border-strong);
  background: var(--bg-hover);
}
.kpi:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}
.kpi-label {
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}
.kpi-figure {
  display: flex;
  align-items: baseline;
  gap: 2px;
}
.kpi-value {
  font-size: var(--text-2xl);
  font-weight: 600;
  line-height: 1.15;
}
.kpi-value--warning { color: var(--severity-warning-fg); }
.kpi-value--critical { color: var(--severity-critical-fg); }
.kpi-unit {
  font-size: var(--text-base);
  font-weight: 500;
  color: var(--color-text-muted);
}
.kpi-sub {
  font-size: 11px;
  color: var(--color-text-subtle);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Reserved whether or not a graphic is drawn, so a tile with no trend does not sit shorter than
   the five beside it — a ragged strip bottom reads as a rendering fault. */
.kpi-graphic {
  display: flex;
  align-items: flex-end;
  min-height: 28px;
  margin-top: var(--space-1);
}
.kpi-track {
  width: 100%;
  height: 4px;
  align-self: center;
  border-radius: 2px;
  background: var(--border-hairline);
  overflow: hidden;
}
.kpi-fill {
  display: block;
  height: 100%;
  border-radius: 2px;
}

/* ---------------------------------------------------------------- must-gather

   The upload surface and the session list.

   Everything here is scoped under `.mg-` because it is the only page in the product that accepts a
   file, and a drop target is a shape nothing else needs. Tokens only — no literal colours — so the
   whole block follows the theme like the rest of the sheet. */

/* The drop target. Dashed rather than solid: a dashed border is the one border convention that
   already means "put something here", and it distinguishes the region from every Card on the page
   without needing a colour that would read as a status. */
.mg-drop {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-6) var(--space-5);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-card);
  background: var(--bg-sunken);
  text-align: center;
  transition: border-color 120ms ease, background 120ms ease;
}
.mg-drop.is-over {
  border-color: var(--accent);
  background: var(--accent-soft);
}
.mg-drop.is-busy {
  opacity: 0.6;
}
.mg-drop svg {
  width: 28px;
  height: 28px;
  color: var(--text-muted);
}
.mg-drop-title {
  margin: 0;
  font-size: 15px;
  font-weight: 600;
  color: var(--text-primary);
}
/* The constraints belong next to the gesture, not behind a validation error. Someone about to drag a
   4 GiB file across a slow link should learn the limit before they wait for the upload to fail. */
.mg-drop-hint {
  margin: 0 0 var(--space-2);
  max-width: 46ch;
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-secondary);
}

/* Upload progress. A determinate bar whenever the browser gives us a total, and a moving
   indeterminate one when it does not — a bar frozen at 0% reads as broken, which for a
   multi-gigabyte upload is the difference between waiting and refreshing. */
.mg-progress {
  height: 6px;
  border-radius: var(--radius-full);
  background: var(--border-hairline);
  overflow: hidden;
}
.mg-progress-bar {
  height: 100%;
  border-radius: var(--radius-full);
  background: var(--accent);
  transition: width 200ms ease;
}
.mg-progress-bar.is-indeterminate {
  width: 33%;
  animation: mg-slide 1.1s ease-in-out infinite;
}
@keyframes mg-slide {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(300%); }
}
/* Respect a reduced-motion preference: the bar still says "indeterminate" by sitting at a third
   width, it simply stops travelling. */
@media (prefers-reduced-motion: reduce) {
  .mg-progress-bar.is-indeterminate {
    animation: none;
  }
}

/* The confirmation panel. */
.mg-confirm {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.mg-confirm-verdict {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  font-size: 15px;
  color: var(--text-primary);
}
.mg-confirm-controls {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
}
.mg-confirm-controls label {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 260px;
  flex: 1 1 260px;
  font-size: 13px;
  color: var(--text-secondary);
}
.mg-confirm-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* What classification matched on. Shown as the raw paths rather than a summary: "high confidence" is
   an assertion, `cluster-scoped-resources/ocs.openshift.io/` is evidence, and the person holding the
   archive is usually the only one who can tell which is right. */
.mg-matched {
  font-size: 13px;
  color: var(--text-secondary);
}
.mg-matched-label {
  display: block;
  margin-bottom: var(--space-1);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 11px;
  color: var(--text-muted);
}
.mg-matched ul {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}
.mg-matched code {
  padding: 2px 6px;
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
  border: 1px solid var(--border-hairline);
  font-size: 12px;
  color: var(--text-primary);
}

/* Session rows. */
.mg-name {
  font-weight: 600;
  color: var(--text-primary);
  word-break: break-all;
}
.mg-sub {
  margin-top: 2px;
  font-size: 12px;
  color: var(--text-secondary);
}
/*
 * `--warn-text`, not `--status-warn-fg`. The latter has never been declared anywhere, so this rule
 * has always resolved to its fallback — plain secondary text — and a must-gather warning has been
 * rendering in the same colour as the line above it since the page shipped. The name it wanted is
 * `--severity-warn-fg`; the token behind that is this one.
 */
.mg-warn {
  color: var(--warn-text);
}
/* A fact the archive did not carry, said rather than left blank. An empty cell reads as a value that
   failed to load; "not in this archive" is the honest answer for an audit or plugin gather that
   legitimately has no cluster version. */
.mg-absent {
  color: var(--text-muted);
  font-style: italic;
}
/* Two lines of the parse failure, in full on hover. The reason is the entire value of a failed row,
   so it is never replaced by a generic sentence — only visually clamped. */
.mg-error {
  margin-top: var(--space-1);
  max-width: 32ch;
  font-size: 12px;
  line-height: 1.4;
  color: var(--critical-text);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.mg-row-actions {
  display: flex;
  gap: var(--space-1);
  justify-content: flex-end;
}

/* Must-gather: the per-archive analysis page. */

/* Tab strip. Buttons rather than links because the archive is one page with sections, not seven
   routes — a parsed archive is immutable, so there is no per-section state worth a URL yet. */
.mg-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  border-bottom: 1px solid var(--border-hairline);
}
.mg-tab {
  position: relative;
  padding: var(--space-2) var(--space-3);
  border: 0;
  background: transparent;
  color: var(--text-secondary);
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}
.mg-tab:hover { color: var(--text-primary); }
.mg-tab.is-active {
  color: var(--text-primary);
  border-bottom-color: var(--accent);
}
/* A dot, never a zero. A "0" beside a section the archive did not collect would assert the count the
   tab body is careful not to claim. */
.mg-tab-absent {
  margin-left: 4px;
  color: var(--text-muted);
}

.mg-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}
.mg-toolbar label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 13px;
  color: var(--text-secondary);
}

/* Cluster conditions, quoted verbatim. */
.mg-conditions {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.mg-condition-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 13px;
  color: var(--text-primary);
}
/* Not clamped. An Upgradeable=False message names the removed APIs and carries the knowledge-base
   link that is the entire actionable content — truncating it would hide the only next step. */
.mg-condition-msg {
  margin: var(--space-1) 0 0;
  font-size: 13px;
  line-height: 1.55;
  color: var(--text-secondary);
}

.mg-warnings {
  margin: 0;
  padding-left: var(--space-4);
  font-size: 13px;
  line-height: 1.6;
  color: var(--text-secondary);
}

/* Event messages: two lines, full text on hover. Long enough to identify the event, short enough
   that 300 rows stay scannable. */
.mg-message {
  max-width: 52ch;
  font-size: 12px;
  line-height: 1.4;
  color: var(--text-secondary);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Must-gather: the wider tab panels. */
.mg-panel { margin-bottom: var(--space-5); }
.mg-panel:last-child { margin-bottom: 0; }
.mg-panel-title {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin: 0 0 var(--space-2);
  font-size: 14px;
  font-weight: 600;
  color: var(--text-primary);
}
.mg-panel-count {
  font-size: 12px;
  font-weight: 400;
  color: var(--text-muted);
}

/* Raw container log. Monospace, scrolls in its own box — a 512 KiB log must never be the page's
   scroll. `white-space: pre-wrap` so a 4,000-character line wraps instead of forcing the page wide. */
.mg-log {
  max-height: 60vh;
  overflow: auto;
  /* The wheel stops at this box's edges. Without it, reaching the end of a wide log line hands the
     scroll to the page and the layout slides sideways — the drift scroll.test.ts exists to prevent. */
  overscroll-behavior: contain;
  margin: 0;
  padding: var(--space-3);
  border-radius: var(--radius-md);
  background: var(--bg-sunken);
  border: 1px solid var(--border-hairline);
  font-size: 12px;
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--text-secondary);
}

/* A table cell that acts as a link. A real <button> for keyboards, styled as the link it reads as. */
.mg-linklike {
  border: 0;
  padding: 0;
  background: transparent;
  color: var(--accent-text);
  font-size: inherit;
  cursor: pointer;
}
.mg-linklike:hover { text-decoration: none; }

/* Archive search. Results drop below the box, in the page flow rather than a popover — this page is
   a workspace, and search results people scroll are content, not a menu. */
.mg-search { margin-bottom: var(--space-2); }
.mg-search-results { margin-top: var(--space-2); font-size: 13px; }
.mg-search-results ul {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  color: var(--text-secondary);
}

/* Must-gather: the oc terminal and the Ask panel. */
.mg-terminal {
  max-height: 55vh;
  overflow: auto;
  overscroll-behavior: contain;
  padding: var(--space-3);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-hairline);
  background: var(--bg-sunken);
  margin-bottom: var(--space-3);
}
.mg-terminal-entry { margin-bottom: var(--space-3); }
.mg-terminal-cmd {
  font-family: ui-monospace, monospace;
  font-size: 13px;
  color: var(--text-primary);
}
.mg-terminal-prompt { color: var(--accent-text); margin-right: 4px; }
.mg-terminal-out {
  margin: var(--space-1) 0 0;
  font-size: 12px;
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--text-secondary);
}
/* A non-zero exit reads as one at a glance — oc's own convention is stderr, ours is tone. */
.mg-terminal-out.is-error { color: var(--critical-text); }
.mg-terminal-input {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.mg-terminal-input input { font-family: ui-monospace, monospace; }

.mg-ask { display: flex; gap: var(--space-2); }
.mg-ask input { flex: 1; }
.mg-exchange { margin-bottom: var(--space-4); }
.mg-exchange-q {
  margin: 0 0 var(--space-2);
  font-weight: 600;
  color: var(--text-primary);
}
/* The model's prose. pre-wrap keeps its own line breaks without letting a long line push the page
   wide; it is prose, so it wraps rather than scrolls. */
.mg-exchange-a {
  margin: 0;
  padding: var(--space-3);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-hairline);
  background: var(--bg-sunken);
  font-family: inherit;
  font-size: 13px;
  line-height: 1.6;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--text-secondary);
}

/* ---- the SOP library --------------------------------------------------------------------------
 *
 * A list of documents, and a reader for one of them.
 *
 * The rows are buttons rather than links, and that is not laziness about routing: opening one is a
 * slide-over over the list, so there is no URL to link to. If a document ever gets its own page this
 * becomes an anchor, and the styling is written so that swap changes nothing visual.
 */
.sop-list {
  display: grid;
}
.sop-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  width: 100%;
  padding: var(--space-3) var(--space-5);
  border: none;
  border-bottom: 1px solid var(--border-hairline);
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.sop-row:last-child {
  border-bottom: none;
}
.sop-row:hover {
  background: var(--bg-hover);
}
.sop-row:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: -2px;
}
.sop-row-main {
  display: grid;
  gap: 2px;
  min-width: 0;
}
.sop-row-title {
  font-size: var(--text-md);
  font-weight: 600;
  overflow-wrap: anywhere;
}
.sop-row-sub {
  font-size: var(--text-xs);
  color: var(--text-muted);
  overflow-wrap: anywhere;
}
.sop-row-tags {
  display: flex;
  flex: none;
  gap: var(--space-2);
  align-items: center;
  flex-wrap: wrap;
}

/*
 * The document itself.
 *
 * One column that may not grow, for the reason `.advice` carries the same rule: a procedure is full
 * of unbreakable tokens — an `oc patch` with an inline JSON body, a 63-character pod name — and an
 * implicit `auto` track sizes itself to the longest of them, laying every paragraph out to that
 * width. The symptom is not a wide code block; it is a document whose every line runs off the edge.
 */
.sop-doc {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-3);
  min-width: 0;
}
/* Headings carry the structure a reader scans by: "Steps", "How to tell it worked". Sans, like every
   other label in the product, and sized by depth rather than by weight alone — three levels that
   differ only in boldness are three levels nobody can tell apart at a glance. */
.sop-h {
  font-family: var(--font-sans);
  font-weight: 600;
  letter-spacing: -0.01em;
  overflow-wrap: anywhere;
}
.sop-h1 {
  font-size: var(--text-xl);
  line-height: var(--leading-heading);
}
.sop-h2 {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-hairline);
  font-size: var(--text-lg);
}
.sop-h3 {
  font-size: var(--text-md);
  color: var(--text-secondary);
}
/* The first heading opens the document; a rule above it would draw a line under the dialog's own
   title. */
.sop-doc > .sop-h:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}

/* ================================================================== display type & scroll motion
 *
 * Additive, hand-authored — NOT part of the generated block at the top of this file, so
 * `tokens.test.ts` does not need to know about it. Backs `motion.tsx` (`Reveal`, `RevealGroup`,
 * `Parallax`, `DisplayHeading`). Scoped to the small set of opener surfaces (hero bands, empty
 * states, docs covers) that `28-design.md` v3 never covered, because the console's own views
 * (tables, dashboards, forms) should keep using `--text-xl` and below — see the doc comment at the
 * top of motion.tsx for why.
 *
 * Timing reference, so any future addition here matches instead of drifting:
 *   - Reveal-on-scroll: 600ms, `--ease-out-display` (a slightly softer curve than the product's
 *     150–250ms UI transitions, because a one-shot entrance reads fine slower than a hover does).
 *   - Stagger step: 80ms between siblings, same figure `.auth-rise` already uses.
 *   - Hover lift: 150ms, the existing `--motion-fast`/`--ease-out` pair — unchanged, reused.
 *   - Parallax: no duration; driven by scroll position via rAF, transform-only.
 */
:root {
  --text-display: 40px;
  --text-hero: 56px;
  --leading-display: 1.05;
  --ease-out-display: cubic-bezier(0.16, 1, 0.3, 1);
  --motion-reveal: 600ms;
  --motion-reveal-stagger: 80ms;
}

.text-display,
.text-hero {
  margin: 0;
  font-family: var(--font-sans);
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--text-primary);
}
.text-display {
  font-size: var(--text-display);
  line-height: var(--leading-display);
}
.text-hero {
  font-size: var(--text-hero);
  line-height: var(--leading-display);
}
@media (max-width: 640px) {
  .text-hero {
    font-size: var(--text-display);
  }
  .text-display {
    font-size: 32px;
  }
}

/*
 * `.reveal` — the IntersectionObserver-driven form of `.auth-rise`. Starts hidden/offset,
 * transitions to settled once `motion.tsx` adds `.is-in-view`. `both` isn't needed here (unlike
 * the keyframe version) because the *starting* state is the base class itself, not a 0% keyframe.
 */
.reveal {
  opacity: 0;
  transition: opacity var(--motion-reveal) var(--ease-out-display), transform var(--motion-reveal) var(--ease-out-display);
  transition-delay: var(--reveal-delay, 0ms);
}
.reveal--up {
  transform: translateY(24px);
}
.reveal--fade {
  transform: none;
}
.reveal.is-in-view {
  opacity: 1;
  transform: none;
}
.reveal-group {
  display: contents;
}
@media (prefers-reduced-motion: reduce) {
  .reveal {
    transition-duration: 1ms;
    transform: none;
  }
}

/* `.parallax-layer` — transform-only by construction (see `Parallax` in motion.tsx); this rule
   just keeps the initial paint from jumping before the first scroll handler runs. */
.parallax-layer {
  will-change: transform;
}
@media (prefers-reduced-motion: reduce) {
  .parallax-layer {
    transform: none !important;
  }
}

/*
 * Hover-lift for plain content links (article/report copy, card titles used as links) that sit
 * outside `.btn`. Same non-colour cue as the button hover pass — a felt 1px shift, not just an
 * underline — but restrained to `--motion-fast`/`--ease-out`, the existing UI-transition pair,
 * because a link inside body copy is not a one-shot entrance.
 */
.link-hover {
  color: var(--accent-text);
  text-decoration: none;
  transition: color var(--motion-fast) var(--ease-out), transform var(--motion-fast) var(--ease-out);
  display: inline-block;
}
.link-hover:hover {
  color: var(--hover-accent-text, var(--accent-text));
  transform: translateY(-1px);
}
.link-hover:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  .link-hover:hover {
    transform: none;
  }
}

/* ================================================================== vibrant red identity layer
 *
 * Requested: lean harder into red, richer saturation, gradient/glow depth, more "alive" —
 * without losing the console read or the plan the file already commits to. Two constraints from
 * above stay load-bearing here:
 *
 *   1. **No hex is hand-tuned.** Every colour below is `color-mix()`/gradient composition of the
 *      existing, contrast-verified `--brand`/`--critical`/`--hover-accent` tokens — never a new
 *      literal hex — so `tokens.test.ts` and this section can never disagree.
 *   2. **No new `box-shadow` outside `.elev-*`.** Every glow here is built from `filter:
 *      drop-shadow()`, `text-shadow`, or a blurred gradient pseudo-element — visually a bloom, but
 *      a different CSS property, so the shadow ban (enforced structurally, not by discipline) stays
 *      intact. See the note above `.btn:hover` for why that rule exists.
 *   3. **Red still never becomes an action.** `index.tsx`'s own comment on the wordmark is the
 *      reason: "an interface where the button you press most often and the button that destroys a
 *      cluster are the same red has taught its users to ignore the warning." `--accent`/
 *      `--color-primary-solid` (what `<Button variant="primary">` fills with) are untouched here —
 *      still blue. What gets louder is everywhere red *already* meant something before this pass:
 *      brand identity, the hover-red cue, live/attention state, and destructive outline — turned
 *      up, not spread onto new surfaces.
 *
 * Timing note: the two new animations below (`boot-glow`, `flicker-in`) are explicitly one-shot
 * (`animation-fill-mode: both`, no `infinite`) — the "nothing auto-animates in a loop" rule from
 * the top of this file is unchanged. `.pill-live`/`.status-dot-live` is the only thing here that
 * loops, and it already did; this only re-skins its colour.
 */
:root {
  --brand-bright: color-mix(in srgb, var(--brand) 82%, white);
  --brand-gradient: linear-gradient(135deg, var(--brand-bright) 0%, var(--brand) 55%, var(--brand-deep) 100%);
  --danger-gradient: linear-gradient(135deg, var(--critical-text) 0%, var(--critical) 100%);
  --glow-brand-sm: color-mix(in srgb, var(--brand) 55%, transparent);
  --glow-brand-lg: color-mix(in srgb, var(--brand) 30%, transparent);
  /* Deliberately derived from --critical, not --brand: the danger-button hover glow below has to
     stay red in every theme regardless of what brand identity does, or a destructive action's
     hover cue could end up matching whatever the ambient accent colour happens to be that theme
     (see the "no new hex" comment on the light-theme override block further down for why this
     matters more once brand starts differing by theme). */
  --glow-critical-sm: color-mix(in srgb, var(--critical) 55%, transparent);
  --glow-hover: color-mix(in srgb, var(--hover-accent) 60%, transparent);
  /* v4: "Active item = --bg-raised fill + --text-primary, never a colored bar." The brand-red
     11% tint this held before was the single most visible hue in the chrome — it sat on screen on
     every page, permanently, marking a location rather than a state. */
  --nav-active-tint: var(--bg-hover);
}

/*
 * `.btn:hover` already goes red (border/label, via `--hover-accent`) and lifts 1px — see the
 * original rule above. This adds a genuine bloom on top, via `filter`, so it never touches
 * `box-shadow` and never fights the inline `border`/`color` the component sets (filter is a
 * property Button's inline style never sets, so there's nothing for it to lose a specificity
 * fight against).
 */
/* The generic button-hover bloom is gone under v4: a drop-shadow glow on an in-page control is a
   shadow doing separation work, whatever property it rides in on. The destructive button's red
   bloom below survives — that one is a severity cue on an action, not chrome. */
@media (prefers-reduced-motion: reduce) {
  .btn:hover:not(:disabled),
  .btn:active:not(:disabled) {
    filter: none;
  }
}

/*
 * Primary sidebar nav — a soft red bloom grows in from the left edge on hover, ahead of the
 * active-row indicator bar `index.tsx` already paints in solid `--brand`. `::before` keeps this
 * off the background/color properties the component sets inline, same reasoning as `.btn` above.
 */
/* v4 removed the brand radial that used to bloom behind a hovered nav row (`.appnav-item::before`,
   a 120x40 red glow). The hover response is now the raised fill `.appnav-item:hover` already
   paints — a location cue, in grayscale, same as the reference consoles. */
@media (prefers-reduced-motion: reduce) {
  .appnav-item::before {
    transition: none;
  }
}

/*
 * The live dot already pulses (`pill-pulse`, permitted loop). Layering a blurred halo behind it
 * on top of that makes the "something is in flight" cue read as a glow instead of a flat dot,
 * without adding a second animation — the halo's opacity rides the same `pill-pulse` keyframe.
 */
.pill-live .pill-dot,
.status-dot-live {
  position: relative;
  filter: drop-shadow(0 0 4px var(--glow-brand-sm));
}

/*
 * Destructive stays an outline until hover, per the Button doc comment — it must never read as
 * the button you're meant to press. This only makes the *warning itself* more legible when it
 * does arrive: the outline earns a gradient glow on hover, in the same red it already used.
 */
.btn:hover:not(:disabled)[style*="critical"] {
  filter: drop-shadow(0 0 8px var(--glow-critical-sm));
}

/*
 * `.boot-glow` — opt-in, for the one headline a console page is allowed to open with (paired with
 * `<DisplayHeading>` from motion.tsx). A single phosphor-flicker settle, not a loop: the terminal
 * cliché done once, on entrance, the same contract `.auth-rise` already uses elsewhere in this
 * file. Text-shadow, not box-shadow, so the shadow ban is untouched.
 */
@keyframes flicker-in {
  0% {
    opacity: 0;
    text-shadow: none;
  }
  40% {
    opacity: 0.4;
    text-shadow: 0 0 24px var(--glow-brand-lg);
  }
  60% {
    opacity: 0.2;
  }
  100% {
    opacity: 1;
    text-shadow: 0 0 18px var(--glow-brand-sm);
  }
}
.boot-glow {
  animation: flicker-in 900ms var(--ease-out-display) both;
}
@media (prefers-reduced-motion: reduce) {
  .boot-glow {
    animation-duration: 1ms;
    text-shadow: none;
  }
}

/*
 * `.crt-surface` — opt-in scanline texture for a hero/boot moment. Static, not animated (a
 * scrolling scanline is exactly the kind of ambient loop the top-of-file rule rules out), built
 * from a repeating gradient so it costs one extra paint layer, not a shadow or an image asset.
 */
.crt-surface {
  position: relative;
  isolation: isolate;
}
.crt-surface::after {
  content: "";
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    to bottom,
    rgba(255, 255, 255, 0.025) 0px,
    rgba(255, 255, 255, 0.025) 1px,
    transparent 1px,
    transparent 3px
  );
  mix-blend-mode: overlay;
  pointer-events: none;
  z-index: 1;
}

/*
 * `.brand-halo` — wraps the wordmark or any single brand glyph that should read as the product's
 * pulse point. Gradient fill + blurred bloom behind it via `::before`; the glyph itself keeps
 * `--on-accent`-style contrast, the halo carries the vibrancy so legibility never trades away.
 */
.brand-halo {
  position: relative;
  background: var(--brand-gradient);
}
.brand-halo::before {
  content: "";
  position: absolute;
  inset: -6px;
  border-radius: inherit;
  background: var(--brand);
  filter: blur(10px);
  opacity: 0.35;
  z-index: -1;
}
@media (prefers-reduced-motion: reduce) {
  .brand-halo::before {
    opacity: 0.2;
  }
}

/*
 * `.brand-mark` — the Kairos "window" (BrandMark). Its two colours are set here per theme rather
 * than taken from the palette, because the mark is cyan on dark and black on light by design and no
 * palette token is either. Mono and contrast use their own accent, so the mark stays inside the
 * colours those themes promise.
 */
.brand-mark {
  /* Lifted from #36404F: on the dark pill and the dark sidebar the outer rings were near-invisible,
     so the mark read as a lone cyan crescent rather than the window. */
  --mark-ring: #5B6A7F;
  --mark-core: #4DE1E8;
}
:root[data-theme="light"] .brand-mark {
  --mark-ring: #C5CDD8;
  --mark-core: #111318;
}
:root[data-theme="mono"] .brand-mark,
:root[data-theme="contrast"] .brand-mark {
  --mark-ring: var(--border-strong);
  --mark-core: var(--accent);
}
/*
 * The front door is a dark stage whatever theme was rolled (`.mk-page` re-points the palette to
 * charcoal), so its mark is always the dark one. Following the rolled theme gave a light roll a black
 * core on a black pill — the rings alone, the window gone. Specific enough to beat every rule above.
 */
:root .mk-page .brand-mark,
:root[data-theme] .mk-page .brand-mark {
  --mark-ring: #5B6A7F;
  --mark-core: #4DE1E8;
}


/* ================================================================== corner radius refinement
 *
 * Reference: the Red Hat OpenShift console screenshot. Its panels read distinctly rounder than
 * this file's original flat 8px across every size of element, which is the "boxy/angular" gap
 * being closed here.
 *
 * UNIFORM, not asymmetric — the reference doesn't show any corner treated differently from its
 * siblings (no single-rounded-corner tabs, no pill-on-one-side elements), so every value below
 * applies to all four corners via the plain `border-radius` shorthand, nothing per-corner.
 *
 * Scaled by element size rather than one flat number everywhere, because a single global radius
 * either looks right on cards and too round on 26px-tall badges, or right on badges and barely
 * perceptible on large panels:
 *   --radius-lg / --radius-card   8px → 14px   panels, cards, modals — the large surfaces the
 *                                               reference's own rounding is most visible on.
 *   --radius-md                   8px → 10px   buttons, inputs, nav rows — a smaller step
 *                                               deliberately: a 34px-tall row at 14px starts
 *                                               reading as a pill, not a rounded rectangle.
 *   --radius-sm                   4px → 6px    tags/small badges — scaled proportionally with
 *                                               the other two rather than left behind at the
 *                                               old flat value.
 *   --radius-full                 unchanged    already fully round (pills, avatars); nothing
 *                                               to refine.
 *
 * As with every colour token changed this session, this is an override appended after the
 * generated block rather than an edit to it — radius lives in the same generated `:root` blocks
 * the colours do, so the same generated/hand-edited drift risk applies even though radius isn't
 * a hex value `tokens.test.ts` obviously cares about.
 *
 * Padding/margin: deliberately NOT changed here. A larger radius pairs naturally with slightly
 * more internal padding (the curve has more room to read before it meets the content), but
 * `--space-5` on `.card`/`.stat-card` is already tuned against a lot of already-shipped density
 * assumptions across this file — bumping it as a side effect of a radius change risks reflowing
 * layouts nobody asked to touch. If a specific card reads cramped against the new curve once
 * this is live, that's a one-line, one-component change, not a global token bump.
 *
 * Shadow/border: no shadow added, and this is a hard constraint, not a style choice — box-shadow
 * outside `.elev-modal`/`.elev-overlay`/`.elev-dropdown` is structurally forbidden in this file
 * (`tokens.test.ts` checks for it). The reference's own "lift" comes from a subtle fill/border
 * contrast against its page background, not a shadow — which is exactly what `--border-hairline`
 * plus `.card`'s existing background-tint already does here. Nothing to add on that front.
 *
 * Cross-browser/cross-size: `border-radius` has been universally supported at 100% for over a
 * decade — no vendor prefixes or fallbacks needed. The one real risk to a visible curve is a
 * square-cornered child bleeding past its rounded parent at small sizes or on a busy row; both
 * `.card` and `.stat-card` carry `overflow: hidden` specifically to guarantee that never happens,
 * regardless of viewport width.
 */
:root {
  --radius-lg: 14px;
  --radius-card: 14px;
  --radius-md: 12px;
  --radius-sm: 10px;
  /* index.tsx's <nav> references --sidebar-bg (added two turns ago for a red-tinted sidebar),
     but this file — the one specified as the reference to use instead — predates that change and
     never defined it. Left undefined, that's a broken/transparent sidebar background, not just a
     missing tint. Aliased back to the original --color-surface here to restore exactly the
     sidebar's original neutral appearance. If the red tint from before is still wanted, say so
     and I'll reapply it — this just prevents the undefined-variable bug in the meantime. */
  --sidebar-bg: var(--color-surface);
}

/* ==========================================================================
   Chrome surfaces: the top bar, the sidebar, and the content ground
   ==========================================================================

   The complaint was "everything was same", and it was literally true. The three regions were painted
   --bg-surface (#131417), --sidebar-bg -> --color-surface, and --bg-page (#0A0B0D), which measure:

       top bar vs sidebar   1.04 : 1
       sidebar vs content   1.03 : 1
       top bar vs content   1.07 : 1

   Three tones within 3-7% of each other. On a calibrated display in a dark room that is a hairline
   difference; on an ordinary monitor in an ordinary office it is nothing at all, which is why the
   whole shell read as one flat sheet with no way to tell where the menu ended.

   It got worse rather than better over time: a previous change removed <nav>'s right border and
   <header>'s bottom border on purpose, so that "header and sidebar read as one continuous chrome
   surface". That is a defensible look when the fills differ enough to carry it. These fills do not,
   so removing the seams removed the only thing left distinguishing the regions.

   The ladder below is deliberate and goes light-to-dark from front to back — the bar is nearest the
   user, the content ground is furthest:

       --chrome-topbar   #1E2126   1.22 : 1 against the content ground
       --chrome-sidebar  #131417   1.07 : 1
       --bg-page         #0A0B0D   the ground everything sits on

   Fill alone still would not be enough at these ratios, so the seams come back as well, in
   --border-strong (#3E434C) rather than --border-hairline (#232529): 1.98:1 against the ground
   versus 1.28:1. A hairline that cannot be seen is not a separator.
   ========================================================================== */
:root,
:root[data-theme="dark"] {
  /* The brief's header #15181B and sidebar #181B1F (2026-10-12): both above the #111315 ground, so
     the frame advances and the content recedes, as the ladder above requires. */
  --chrome-topbar: #15181B;
  --chrome-sidebar: #181B1F;
  /* --border-hairline (#232529), not --border-strong (#3E434C). The strong border was chosen when
     this token drew both the horizontal seam under the bar AND a vertical rule down the sidebar;
     with the vertical rule gone the remaining line only has to mark one edge, and the heavier weight
     read as a drawn divider rather than a boundary. */
  --chrome-seam: var(--border-hairline);
}

:root[data-theme="light"] {
  /* Same ladder, inverted: the bar is the lightest surface and the content ground the darkest, so
     the depth ordering a reader learns in one theme still holds in the other. Reversing it would
     make the chrome recede in light mode and advance in dark, which reads as two different products.
  */
  --chrome-topbar: #FFFFFF;
  /* The brief's sidebar is white like the cards (2026-10-12); the #F6F8FA ground still sits below. */
  --chrome-sidebar: #FFFFFF;
  /* #C2C9D1 rather than #89919D. The strong border measures 2.99:1 against this sidebar - nearly
     body-text contrast - which is what made the vertical rule read as a hard line and got it
     removed. */
  --chrome-seam: var(--border-hairline);
}

/* ==========================================================================
   Shell tokens that a theme may override
   ========================================================================== */
:root {
  /* The shell's ground. A token because the shell sets it inline (index.tsx), and an inline style
     cannot be overridden from here — CLAUDE.md trap 30. */
  --shell-ground: var(--bg-page);
  /* What a floating surface is painted with: a dialog, a dropdown, a toast. The card colour in every
     current theme; a theme with translucent cards would replace it. */
  --overlay-surface: var(--bg-surface);
}

/* ---- High Contrast: a focus ring nobody can miss ----------------------------------------------- */
:root[data-theme="contrast"] :focus-visible {
  outline: 3px solid var(--focus-ring);
  outline-offset: 2px;
}

/* A theme change cross-fades instead of snapping. Only while switching — `ThemeSwitcher` sets the
   class for 300 ms — so no hover or scroll ever pays for a transition on every element. */
html.theme-switching,
html.theme-switching *,
html.theme-switching *::before,
html.theme-switching *::after {
  transition: background-color 250ms var(--ease-out), border-color 250ms var(--ease-out),
    color 250ms var(--ease-out), fill 250ms var(--ease-out), stroke 250ms var(--ease-out);
}

@media (prefers-reduced-motion: reduce) {
  html.theme-switching,
  html.theme-switching *,
  html.theme-switching *::before,
  html.theme-switching *::after {
    transition: none;
  }
}

/* ---- the theme switcher, in the account menu (ThemeSwitcher in index.tsx) ---------------------- */
.user-menu-identity {
  display: grid;
  gap: 2px;
  padding: var(--space-2);
  margin-bottom: var(--space-1);
  border-bottom: 1px solid var(--border-hairline);
  min-width: 0;
}
.user-menu-identity-name { font-size: var(--text-sm); font-weight: 600; overflow-wrap: anywhere; }
.user-menu-identity-meta { font-size: var(--text-xs); color: var(--text-muted); overflow-wrap: anywhere; }
.user-menu-identity-role {
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.user-menu-nested { display: grid; gap: 2px; margin-bottom: var(--space-1); }
.user-menu-nested-row { position: relative; }
.user-menu-field { display: grid; gap: var(--space-1); padding: var(--space-1) var(--space-2) var(--space-2); }
.user-menu-field-label { font-size: var(--text-xs); font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-muted); }
.user-menu-select {
  width: 100%;
  height: var(--size-control-sm);
  padding: 0 var(--space-2);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
  color: var(--color-text);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
}
.user-menu-select:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
.user-menu-parent { gap: var(--space-2); }
.user-menu-parent-label { flex: 1; text-align: left; }
.user-menu-parent-value { font-size: var(--text-xs); color: var(--text-muted); max-width: 8.5em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.user-menu-parent-chevron { flex: 0 0 auto; transform: rotate(-90deg); color: var(--text-muted); }
.user-menu-parent[aria-expanded="true"] { background-color: var(--color-hover); }
/* The nested list opens to the left of the menu: the menu is anchored to the right edge of the header,
   so there is room on that side and none on the other. Where the viewport is too narrow for that, it
   drops below the row instead. */
.user-menu-sub {
  position: absolute;
  top: calc(-1 * var(--space-1));
  right: calc(100% + var(--space-2));
  width: 220px;
  padding: var(--space-1);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  z-index: var(--z-overlay);
}
/* The tiles need more room than a text list. */
.user-menu-sub:has(.theme-switcher-tiles) { width: 268px; }
@media (max-width: 560px) {
  .user-menu-sub { position: static; width: auto; margin-top: var(--space-1); }
}
.theme-switcher { display: grid; gap: 2px; }
/* The theme picker as tiles: each option is a miniature of the theme it selects. */
.theme-switcher-tiles { grid-template-columns: 1fr 1fr; gap: var(--space-2); padding: var(--space-1); }
.theme-switcher-tiles .theme-tile:last-child:nth-child(odd) { grid-column: 1 / -1; }
.theme-tile {
  display: grid;
  gap: var(--space-1);
  padding: 5px;
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  background: none;
  color: var(--color-text);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  text-align: left;
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--ease-out), transform var(--motion-fast) var(--ease-out);
}
.theme-tile:hover { border-color: var(--border-strong); transform: translateY(-1px); }
.theme-tile:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.theme-tile[data-selected="yes"] { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.theme-tile-preview {
  position: relative;
  display: block;
  height: 46px;
  overflow: hidden;
  border-radius: var(--radius-sm);
  background: var(--pv-bg);
  border: 1px solid var(--pv-line);
}
.theme-tile-card {
  position: absolute;
  inset: 8px 22px 8px 8px;
  display: grid;
  align-content: center;
  gap: 4px;
  padding: 0 6px;
  border-radius: 4px;
  background: var(--pv-surface);
  border: 1px solid var(--pv-line);
}
.theme-tile-line { display: block; height: 3px; border-radius: 2px; background: var(--pv-text); opacity: 0.85; }
.theme-tile-line.short { width: 55%; opacity: 0.4; }
.theme-tile-dot { position: absolute; right: 8px; top: 8px; width: 9px; height: 9px; border-radius: 50%; background: var(--pv-accent); }
.theme-tile-name { display: flex; align-items: center; gap: var(--space-1); padding: 0 3px 2px; min-width: 0; }
.theme-tile[data-selected="yes"] .theme-tile-name { font-weight: 600; }
/* The previews are other themes' colours by definition, so they cannot come from the active palette. */
.theme-tile-preview[data-preview="dark"]     { --pv-bg: #111315; --pv-surface: #181B1F; --pv-line: #2A2E34; --pv-text: #E6E8EB; --pv-accent: #AEB5BE; }
.theme-tile-preview[data-preview="light"]    { --pv-bg: #F6F8FA; --pv-surface: #FFFFFF; --pv-line: #D5DBE1; --pv-text: #1B2430; --pv-accent: #2563EB; }
.theme-tile-preview[data-preview="slate"]    { --pv-bg: #0F1419; --pv-surface: #1B232C; --pv-line: #2C3744; --pv-text: #DCE3EA; --pv-accent: #94A3B8; }
.theme-tile-preview[data-preview="mono"]     { --pv-bg: #000000; --pv-surface: #0A0A0A; --pv-line: #4D4D4D; --pv-text: #FFFFFF; --pv-accent: #FFFFFF; }
.theme-tile-preview[data-preview="contrast"] { --pv-bg: #000000; --pv-surface: #0B0B0B; --pv-line: #FFFFFF; --pv-text: #FFFFFF; --pv-accent: #FFD400; }

/* A theme change is a reveal, not a fade: the browser snapshots the old page, the new theme is applied
   underneath, and `ThemeSwitcher` grows the new one as a circle from the pressed option. Both pseudo
   elements are plain bitmaps, so nothing re-lays-out or repaints per frame. The default cross-fade is
   switched off so the circle's edge is the only motion. */
::view-transition-old(root),
::view-transition-new(root) {
  animation: none;
  mix-blend-mode: normal;
}
::view-transition-old(root) { z-index: 1; }
::view-transition-new(root) { z-index: 2; }
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
}
.theme-option {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  height: var(--size-control-sm);
  padding: 0 var(--space-2);
  border: none;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--color-text);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  text-align: left;
  cursor: pointer;
  transition: background-color var(--motion-fast) var(--ease-out);
}
.theme-option:hover { background: var(--bg-hover); }
.theme-option:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
.theme-option[data-selected="yes"] { background: var(--accent-soft); font-weight: 600; }
.theme-option-label { flex: 1; min-width: 0; }
.theme-option-check { flex: 0 0 auto; color: var(--accent-text); }
/* ==========================================================================
   YAML viewer
   ==========================================================================

   ## Why syntax colours are aliases rather than new hues

   Blacklist 7 allows one accent hue, and syntax highlighting needs four or five distinguishable
   ones. Both constraints are real, so the mapping below reuses tokens the palette already carries
   and already contrast-verifies per theme (`tokens.test.ts`) instead of introducing a second accent.
   Nothing new enters the palette; what is new is that the mapping is *named*, so changing how a
   string reads is one line here rather than a hunt through a component.

   The reuse of `--severity-*` for non-severity meaning is deliberate and worth stating: a green
   string in a code block does not read as "healthy" — syntax highlighting is its own visual
   language, and the same green/amber convention is what VS Code, GitHub and GitLab all use. Colour
   only carries severity meaning on *data* surfaces; a manifest is not one.

   These are `--yaml-*` rather than `--color-yaml-*` on purpose: they alias themed tokens instead of
   being themed values themselves, so they need no per-theme block and must not be pulled into the
   token census that governs real palette entries. */
:root {
  --yaml-key:     var(--color-primary-text);
  --yaml-string:  var(--severity-ok-fg);
  --yaml-number:  var(--severity-warning-fg);
  --yaml-boolean: var(--severity-warning-fg);
  --yaml-null:    var(--color-text-subtle);
  --yaml-comment: var(--color-text-subtle);
  --yaml-anchor:  var(--severity-critical-fg);
  --yaml-marker:  var(--color-text-muted);
  --yaml-literal: var(--color-text-muted);
  --yaml-punct:   var(--color-text-muted);
  --yaml-dash:    var(--color-text-muted);
  --yaml-plain:   var(--color-text);
  --yaml-gutter:  var(--color-text-subtle);
}

.yv { display: flex; flex-direction: column; gap: var(--space-2); }

.yv-bar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  justify-content: space-between;
}
.yv-bar-left,
.yv-bar-right { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }

.yv-meta { font-size: var(--text-xs); color: var(--color-text-muted); font-variant-numeric: tabular-nums; }

/* The path to the selected line. Only rendered when a line is selected, so it never occupies space
   saying nothing — at 993 lines the question "where am I" has no other answer on screen. */
.yv-crumb {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  font-family: var(--font-mono);
  overflow-x: auto;
  white-space: nowrap;
  padding-bottom: 2px;
}
.yv-crumb b { color: var(--color-text); font-weight: 500; }
.yv-crumb span { color: var(--color-text-subtle); padding: 0 4px; }

.yv-code {
  background: var(--color-background);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: auto;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 1.65;
  padding: var(--space-3) 0;
}

.yv-line {
  display: flex;
  align-items: flex-start;
  min-height: 1.65em;
  border: none;
  background: none;
  width: 100%;
  text-align: left;
  font: inherit;
  color: inherit;
  padding: 0;
  cursor: default;
}
.yv-line:hover { background: var(--color-hover); }
.yv-line.is-active { background: var(--color-primary-soft); }
.yv-line:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: -2px; }

/* Right-aligned, tabular, and non-selectable so copying the block copies YAML rather than YAML with
   a column of numbers down the left of it. */
.yv-num {
  flex: 0 0 auto;
  width: 4.5ch;
  padding: 0 var(--space-2) 0 var(--space-3);
  text-align: right;
  color: var(--yaml-gutter);
  font-variant-numeric: tabular-nums;
  -moz-user-select: none;
       user-select: none;
  -webkit-user-select: none;
}

.yv-fold {
  flex: 0 0 auto;
  width: 1.4ch;
  border: none;
  background: none;
  padding: 0;
  margin-right: var(--space-1);
  color: var(--color-text-subtle);
  cursor: pointer;
  font: inherit;
  line-height: 1.65;
  -moz-user-select: none;
       user-select: none;
  -webkit-user-select: none;
}
.yv-fold:hover { color: var(--color-text); }
.yv-fold:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 1px; }
.yv-fold.is-empty { cursor: default; }

.yv-text { flex: 1 1 auto; white-space: pre; padding-right: var(--space-4); }
.yv-code.is-wrapped .yv-text { white-space: pre-wrap; overflow-wrap: anywhere; }

/* What a collapsed block leaves behind. It has to say how much it hid — a bare ellipsis turns a
   fold into "some lines are missing" and there is no way to judge whether it matters. */
.yv-folded {
  color: var(--color-text-subtle);
  background: var(--color-sunken);
  border-radius: var(--radius-sm);
  padding: 0 var(--space-2);
  margin-left: var(--space-1);
  font-size: var(--text-xs);
}

.yt-key     { color: var(--yaml-key); }
.yt-string  { color: var(--yaml-string); }
.yt-number  { color: var(--yaml-number); }
.yt-boolean { color: var(--yaml-boolean); }
.yt-null    { color: var(--yaml-null); font-style: italic; }
.yt-comment { color: var(--yaml-comment); font-style: italic; }
.yt-anchor  { color: var(--yaml-anchor); }
.yt-marker  { color: var(--yaml-marker); }
.yt-literal { color: var(--yaml-literal); }
.yt-punct   { color: var(--yaml-punct); }
.yt-dash    { color: var(--yaml-dash); }
.yt-plain   { color: var(--yaml-plain); }
.yt-indent  { color: inherit; }

/* A match is marked, not recoloured: recolouring would destroy the token colour that says what the
   matched text *is*, which is the reason to have looked. */
.yv-hit {
  background: var(--severity-warning-bg);
  border-radius: 2px;
}
.yv-hit.is-current { outline: 1px solid var(--severity-warning-fg); }

/* ---------- YAML structure graph ----------

   Card colour is by depth, cycling through hues the palette already carries. That is not decoration:
   on a 993-line Pod the eye needs some way to tell "this card is two levels down" from "this card is
   five levels down" while panning, and depth is the one property of a card that the text inside it
   never states. It is also why the cycle is short — four steps read as a rhythm, ten read as noise. */
:root {
  --yg-card:    var(--color-surface);
  --yg-edge:    var(--color-border-strong);
  --yg-d0:      var(--color-primary-text);
  --yg-d1:      var(--severity-ok-fg);
  --yg-d2:      var(--severity-warning-fg);
  --yg-d3:      var(--severity-critical-fg);
}

.yg-wrap {
  position: relative;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-background);
  overflow: auto;
}
.yg-canvas { display: block; }
.yg-dots circle { fill: var(--color-grid); }

.yg-edge {
  fill: none;
  stroke: var(--yg-edge);
  stroke-width: 1.2;
  opacity: .5;
}
.yg-edge.is-lit { opacity: 1; stroke-width: 1.8; }

.yg-card rect.yg-body {
  fill: var(--yg-card);
  stroke: var(--color-border-strong);
  rx: 8;
}
.yg-card.is-selected rect.yg-body { stroke: var(--color-primary); stroke-width: 2; }
.yg-card:focus-visible { outline: none; }
.yg-card:focus-visible rect.yg-body { stroke: var(--color-focus-ring); stroke-width: 2; }
.yg-card:hover rect.yg-body { stroke: var(--color-text-muted); }

/* The depth stripe. One rule per step, so the cycle is legible in this file rather than computed. */
.yg-card rect.yg-rail { rx: 2; }
.yg-card.d0 rect.yg-rail { fill: var(--yg-d0); }
.yg-card.d1 rect.yg-rail { fill: var(--yg-d1); }
.yg-card.d2 rect.yg-rail { fill: var(--yg-d2); }
.yg-card.d3 rect.yg-rail { fill: var(--yg-d3); }

.yg-key   { fill: var(--yaml-key); }
.yg-val   { fill: var(--color-text); }
.yg-link  { fill: var(--color-text-muted); font-style: italic; }
.yg-label { fill: var(--color-text-muted); }
.yg-hidden { fill: var(--severity-warning-fg); }
.yg-text { font-family: var(--font-mono); font-size: 11px; }

.yg-fold circle { fill: var(--color-surface); stroke: var(--color-border-strong); }
.yg-fold text { fill: var(--color-text-muted); font-size: 11px; }
.yg-fold:hover circle { stroke: var(--color-text); }

/* ------------------------------------------------------------------ shared request controls
 *
 * The filter input, method chip, clear button and hint box first drawn for the API console, which
 * was removed on 2026-10-20 at the user's request; Endpoints and Monitors still use these four.
 *
 * Method colour carries the meaning it does in every HTTP tool: GET reads, everything else writes —
 * only GET takes the accent and the write verbs share the warn/critical family.
 */

.ac-input {
  flex: 1;
  min-width: 0;
  height: var(--size-control);
  padding: 0 var(--space-3);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
}

.ac-input:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 1px;
  border-color: var(--accent);
}

.ac-method {
  flex: 0 0 auto;
  min-width: 52px;
  padding: 1px var(--space-1);
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  text-align: center;
  letter-spacing: 0.04em;
}

.ac-method.m-get {
  background: var(--accent-soft);
  color: var(--accent-text);
}

.ac-method.m-post {
  background: var(--ok-soft);
  color: var(--ok-text);
}

.ac-method.m-put,
.ac-method.m-patch {
  background: var(--warn-soft);
  color: var(--warn-text);
}

.ac-method.m-delete {
  background: var(--critical-soft);
  color: var(--critical-text);
}

.ac-hint {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin: var(--space-4) 0 0;
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  line-height: var(--leading-body);
  background: var(--bg-sunken);
  color: var(--text-secondary);
}

.ac-clear {
  flex: 0 0 auto;
  height: var(--size-control-sm);
  padding: 0 var(--space-3);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-secondary);
  font-family: inherit;
  font-size: var(--text-sm);
  cursor: pointer;
}

.ac-clear:disabled {
  opacity: 0.4;
  cursor: default;
}

/* ------------------------------------------------------------------ incident resemblance
 *
 * The shared tokens are shown as code chips rather than prose. They are the *reason* a match was
 * offered, and a similarity score with no visible basis is a number to be trusted or ignored with
 * equal justification — the chips are what let a reader disagree with it.
 */

/* ---- the upgrade page's documentation block -------------------------------------------------
 *
 * Three links to Red Hat's own manuals, on the page somebody reads immediately before rebooting
 * every node they own. It was a bare `<h4>` over three unadorned anchors with an identical grey
 * paragraph under each — the set's `covers` line, repeated, because a set that ships several books
 * handed all of them the same sentence. So the one surface whose job is "open this one" gave three
 * equally-weighted links and one description three times.
 *
 * What the treatment has to carry: that this is a different kind of thing from the numbered steps
 * above it (a sunken panel, not another list), which link to open first (`start here`), and that
 * each one leaves the product (the glyph, and a screen-reader-only note).
 */
.upg-docs {
  margin-top: var(--space-5);
  padding: var(--space-4);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  background: var(--bg-sunken);
}

.upg-docs-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.upg-docs-title {
  margin: 0;
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--color-text);
}
/* The version is the whole point of these links, so it sits with the title rather than inside it. */
.upg-docs-ver {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}

.upg-docs-lead {
  margin: var(--space-2) 0 0;
  font-size: var(--text-base);
  line-height: var(--leading-body);
  color: var(--color-text-muted);
  max-width: 78ch;
}
.upg-docs-lead code {
  font-family: var(--font-mono);
  font-size: 0.92em;
  padding: 1px 5px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-hairline);
  background: var(--bg-surface);
  color: var(--color-text);
}

/*
 * Numbered, because the order is the recommendation.
 *
 * `decimal` markers sit outside the content, which keeps the link on the panel's left rule and the
 * description aligned under it rather than under the number.
 */
.upg-docs-list {
  list-style: decimal;
  margin: var(--space-3) 0 0;
  padding-left: var(--space-5);
  display: grid;
  gap: var(--space-3);
}
.upg-doc::marker {
  color: var(--color-text-subtle);
  font-size: var(--text-sm);
}

.upg-doc-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
}
/* The glyph rides with the text so a wrapped title does not leave it stranded on its own line. */
.upg-doc-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--text-md);
  font-weight: 550;
}
.upg-doc-link svg {
  flex: none;
  opacity: 0.7;
}
.upg-doc-link:hover svg,
.upg-doc-link:focus-visible svg {
  opacity: 1;
}

.upg-doc-covers {
  margin-top: 2px;
  font-size: var(--text-sm);
  line-height: var(--leading-body);
  /* `--color-text-muted`, not `--color-text-subtle`: the subtle tier measures 4.40:1 on a sunken
     ground, which is the floor failure the incident panel's chips had one page over. */
  color: var(--color-text-muted);
  max-width: 78ch;
}

/* ---- "Seen before": incidents that resemble this one --------------------------------------
 *
 * Every measurement below was taken against the panel as it shipped, because the complaint was that
 * the card could not be read and the reasons turned out to be arithmetic rather than taste:
 *
 *   - the evidence chips were painted on `--bg-hover`, sitting inside a row painted `--bg-sunken`,
 *     and **those two tokens are the same hex in both themes** (#F4F4F5 light, #18181B dark). The
 *     chip's ground measured **1.00:1** against the row it sat in: the pill shape was not faint, it
 *     was absent, and five chips in a row rendered as one unbroken grey ribbon.
 *   - that ribbon was set at **10px** in `--text-muted`, which measures **4.40:1** — under the 4.5
 *     floor, at a size far below where the 3:1 large-text allowance begins. Failing on both counts.
 *   - the row itself separated from its card at **1.10:1**, so nothing framed any of it.
 *
 * The fix is a ground each element can actually sit on and a border doing the framing, which is what
 * the rest of this stylesheet already uses: a hairline, never a shadow.
 */
.sim-match {
  padding: var(--space-3) var(--space-4);
  /* The row had a left accent and no other edge, on a ground 1.10:1 from the card. The hairline is
     what makes it a row; the accent stays, thicker, as the thing that says it is one of several. */
  border: 1px solid var(--border-hairline);
  border-left: 3px solid var(--border-strong);
  background: var(--bg-sunken);
  border-radius: var(--radius-md);
}

/*
 * The recorded cause, which now leads the row.
 *
 * It was third, under a score badge and a raw locale timestamp, and it is the only line in the row
 * anyone is reading *for* — the score ranks the rows, the cause is what the row says.
 */
.sim-cause {
  margin: 0;
  font-size: var(--text-md);
  font-weight: 550;
  line-height: var(--leading-body);
  color: var(--color-text);
}
/*
 * "No cause was ever recorded for that incident."
 *
 * Set as an absence, not as a finding. The sentence exists because a blank line here would read as
 * "no cause" — a different claim — but promoting the cause to the row's headline had the opposite
 * failure waiting in it: the same weight and colour as a real root cause, so a row nobody ever
 * diagnosed looked exactly like a row somebody did.
 */
.sim-cause--none {
  font-weight: 400;
  font-style: italic;
  color: var(--color-text-muted);
}

.sim-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-top: var(--space-2);
}

/* The match, as a figure. Neutral: the tone beside it carries the outcome, and a coloured
   percentage was reading as though the colour ranked the match. */
.sim-score {
  font-size: var(--text-md);
  font-weight: 650;
  line-height: 1;
  letter-spacing: -0.01em;
  color: var(--color-text);
}
/*
 * `--color-text-muted`, not `--color-text-subtle`.
 *
 * The subtle tier is #71717A, and on this row's `--bg-sunken` ground it measures **4.40:1** — the
 * identical failure the chips had, at the identical size. It reads fine on a white card, which is
 * why it survived; a sunken row is a different ground and the tier below it does not clear the floor
 * there. Anything small sitting on `.sim-match` takes the tier above.
 */
.sim-score-label,
.sim-width {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
/* "over 5 signals" qualifies the percentage and sits with it: 100% over one signal and 100% over
   nine are the same number about very different evidence. */
.sim-width {
  margin-right: auto;
}

/*
 * Open or resolved, as its own mark.
 *
 * One `Badge` used to carry the percentage *and* this, as its tone — so a blue 100% beside a green
 * 75% invited the reading that colour ranked the match. Two marks, one job each.
 */
.sim-state {
  align-self: center;
  padding: 2px 8px;
  border-radius: var(--radius-full);
  border: 1px solid transparent;
  font-size: var(--text-xs);
  white-space: nowrap;
}
.sim-state--resolved {
  background: var(--severity-ok-bg);
  color: var(--severity-ok-fg);
}
.sim-state--open {
  background: var(--bg-surface);
  color: var(--color-text-muted);
  border-color: var(--border-hairline);
}

/* When and where. A footnote to the cause above it, not a heading for the chips below. */
.sim-prov {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
  margin-top: var(--space-1);
  font-size: var(--text-xs);
  /* Same reason as the labels above: the subtle tier does not clear 4.5:1 on a sunken ground. */
  color: var(--color-text-muted);
}
.sim-prov > span + span::before {
  content: "·";
  margin-right: var(--space-2);
  color: var(--border-strong);
}
.sim-where {
  font-family: var(--font-mono);
  color: var(--color-text-muted);
  overflow-wrap: anywhere;
}

.sim-tokens {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin-top: var(--space-3);
}

/*
 * One shared signal.
 *
 * A real ground plus a hairline, because the row it sits in is `--bg-sunken` and the chip's old
 * ground was the same value. `--bg-surface` against that row is only ~1.10:1 in either theme, so the
 * border is what makes it a chip — the same reasoning the cards on this page use, one level down.
 *
 * 11px, not 10: these are colon-joined coordinates a reader has to parse, and the parts are set
 * apart in weight and colour so they do not have to (see `SignalChip`).
 */
.sim-chip {
  display: inline-flex;
  align-items: baseline;
  padding: 2px var(--space-2);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-sm);
  background: var(--bg-surface);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 1.5;
  white-space: nowrap;
}
/* The channel — `alert` or `event` — is the least distinguishing part of a token and recedes. */
.sim-chip-ch {
  color: var(--color-text-subtle);
}
.sim-chip-sep {
  color: var(--border-strong);
  padding: 0 1px;
}
.sim-chip-kind {
  color: var(--color-text-muted);
}
/* The reason is what actually differs between two chips, so it carries full text colour. */
.sim-chip-reason {
  color: var(--color-text);
  font-weight: 500;
}

/* ------------------------------------------------------------------ API monitors
 *
 * A card per check rather than a table row. The four figures — uptime, p50, p95, runs — need to be
 * read together, and a table wide enough to hold them plus a URL either wraps the URL into
 * unreadable fragments or scrolls the figures off the right edge on the screen sizes people use.
 *
 * `.muted` on a stat value is how "not measured" is rendered. It is a real state and the most
 * important one on a new monitor: the figure is absent, not zero.
 */

.mon-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
  gap: var(--space-4);
}

.mon-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
}

.mon-desc {
  margin: var(--space-1) 0 0;
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

.mon-target {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

.mon-target code {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-secondary);
  word-break: break-all;
}

.mon-stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-2);
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-hairline);
}

.mon-stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.mon-stat span {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
}

.mon-stat strong {
  font-size: var(--text-lg);
  color: var(--text-primary);
}

.mon-stat strong.muted {
  color: var(--text-muted);
  font-weight: 400;
}

.mon-failure {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin: var(--space-3) 0 0;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--critical-soft);
  color: var(--critical-text);
  font-size: var(--text-sm);
  line-height: var(--leading-body);
  word-break: break-word;
}

.mon-failure svg {
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
  margin-top: 2px;
}

.mon-meta {
  margin-top: var(--space-3);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

.mon-actions {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

/* ---- a monitor's next 24 hours (monitors/outlook.tsx) ------------------------------------------ */
.mon-outlook { margin-top: var(--space-3); padding-top: var(--space-3); border-top: 1px solid var(--border-hairline); }
.mon-outlook > summary { cursor: pointer; font-size: var(--text-sm); font-weight: 600; color: var(--text-primary); }
.mon-outlook > summary:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; border-radius: var(--radius-sm); }
.mon-ol { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-3); margin-top: var(--space-3); min-width: 0; }
.mon-ol-source { margin: 0; font-size: var(--text-xs); line-height: var(--leading-body); color: var(--text-secondary); }
.mon-ol-chip {
  display: inline-block; margin-right: var(--space-2); padding: 1px var(--space-2);
  border-radius: 999px; font-size: 11px; font-weight: 600;
  background: var(--bg-sunken); color: var(--text-secondary);
}
.mon-ol-chip[data-source="checks"] { background: var(--ok-soft); color: var(--ok-text); }
.mon-ol-chip[data-source="service"] { background: var(--info-soft); color: var(--info-text); }
.mon-ol-figures { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-2); margin: 0; }
.mon-ol-figures > div { display: grid; align-content: start; gap: 2px; min-width: 0; }
.mon-ol-figures dt { font-size: 10px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); }
.mon-ol-figures dd { margin: 0; font-size: var(--text-md); font-weight: 600; color: var(--text-primary); }
.mon-ol-figures dd.mon-ol-sub { font-size: 11px; font-weight: 400; color: var(--text-muted); }
.mon-ol-chart { margin: 0; min-width: 0; }
.mon-ol-chart figcaption { font-size: var(--text-xs); color: var(--text-secondary); margin-bottom: var(--space-1); }
.mon-ol-sentence { margin: var(--space-1) 0 0; font-size: 11px; line-height: var(--leading-body); color: var(--text-muted); }
.mon-ol-note { margin: 0; font-size: var(--text-xs); line-height: var(--leading-body); color: var(--text-secondary); }
.mon-ol-note.is-error { color: var(--critical-text); }
.mon-ol-preds { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.mon-ol-preds li { display: grid; gap: 2px; padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm); background: var(--warn-soft); }
.mon-ol-pred-head { font-size: 11px; font-weight: 600; color: var(--warn-text); }
.mon-ol-pred-msg { font-size: var(--text-xs); line-height: var(--leading-body); color: var(--text-primary); overflow-wrap: anywhere; }

/* "Found in your cluster" on API monitors: discovered APIs, one row each. */
.mon-disc {
  margin-top: var(--space-5);
}
.mon-disc-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}
.mon-disc-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.mon-disc-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  border-top: 1px solid var(--color-border);
}
.mon-disc-main {
  display: grid;
  gap: var(--space-1);
  min-width: 0;
}
.mon-disc-title {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}
.mon-disc-url {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-secondary);
  overflow-wrap: anywhere;
}
.mon-disc-why {
  font-size: var(--text-xs);
  color: var(--text-muted);
}
.mon-disc-act {
  flex: none;
}
@media (max-width: 640px) {
  .mon-disc-row {
    flex-direction: column;
    align-items: flex-start;
  }
}

.mon-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.mon-form label {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
}

.mon-form label > span {
  flex: 0 0 130px;
  padding-top: 8px;
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

.mon-form label > .ac-input,
.mon-form label > select,
.mon-form label > textarea {
  flex: 1;
  min-width: 0;
}

.mon-form textarea.ac-input {
  height: auto;
  padding: var(--space-2) var(--space-3);
  line-height: var(--leading-body);
  resize: vertical;
}

.mon-req {
  flex: 1;
  display: flex;
  gap: var(--space-2);
  min-width: 0;
}

.mon-req select {
  flex: 0 0 110px;
}

@media (max-width: 640px) {
  .mon-form label {
    flex-direction: column;
    gap: var(--space-1);
  }
  .mon-form label > span {
    flex: none;
    padding-top: 0;
  }
  .mon-stats {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* ---------------------------------------------------------------- endpoint board
 *
 * The outcome bar's colours are the load-bearing part. `unclassified` is deliberately hatched and
 * neutral rather than green: an endpoint with no baseline is not healthy, it is unjudged, and a
 * green segment would make a board that is 90% unjudged read as 90% healthy.
 */
.ep-evidence { color: var(--text-secondary); font-size: 0.85rem; margin: 0 0 1rem; max-width: 68ch; }
.ep-list { display: flex; flex-direction: column; gap: 0.75rem; }
.ep-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.ep-id { display: flex; align-items: center; gap: 0.6rem; min-width: 0; flex-wrap: wrap; }
.ep-route { font-weight: 600; }
.ep-service { color: var(--text-secondary); font-size: 0.85rem; }
.ep-unjudged {
  display: flex; gap: 0.4rem; align-items: flex-start;
  color: var(--text-secondary); font-size: 0.82rem; margin: 0.6rem 0 0; max-width: 76ch;
}
.ep-unjudged svg { flex: 0 0 auto; width: 14px; height: 14px; margin-top: 2px; }

.ep-bar { display: flex; height: 8px; border-radius: 4px; overflow: hidden; margin: 0.75rem 0; background: var(--bg-sunken); }
.ep-seg { display: block; height: 100%; }
.ep-normal { background: var(--ok-text); }
.ep-slow { background: var(--warn-text); }
.ep-very_slow { background: color-mix(in srgb, var(--warn-text) 60%, var(--critical)); }
.ep-stalled { background: color-mix(in srgb, var(--critical) 70%, black); }
.ep-error { background: var(--critical); }
/*
 * Neutral, and never one of the outcome colours.
 *
 * This segment is an absence of evidence and must not read as a measurement. It said so with a
 * 45-degree hatch, which was a repeating gradient — and blacklist 6 rejects every gradient in this
 * stylesheet, precisely because "it is justified here" is what the next five would also say. A flat
 * neutral from the border ramp carries the same meaning: it is visibly not ok, warn or critical.
 */
.ep-unclassified { background: var(--border-strong); }

.ep-stats { display: flex; flex-wrap: wrap; gap: 1.5rem; }
.ep-trend {
  display: flex; gap: 0.4rem; align-items: flex-start;
  color: var(--warn-text); font-size: 0.82rem; margin: 0.6rem 0 0;
}
.ep-trend svg { flex: 0 0 auto; width: 14px; height: 14px; margin-top: 2px; }

.ep-blame-title { margin: 0 0 0.3rem; font-size: 0.95rem; }
.ep-blame-note { color: var(--text-secondary); font-size: 0.82rem; margin: 0 0 0.9rem; max-width: 72ch; }
.ep-blame { display: flex; flex-direction: column; gap: 0.45rem; }
.ep-blame-row { display: grid; grid-template-columns: minmax(0, 18rem) 1fr 3.5rem; gap: 0.75rem; align-items: center; }
.ep-blame-label { display: flex; gap: 0.5rem; min-width: 0; align-items: baseline; }
.ep-blame-label code { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.8rem; color: var(--text-secondary); }
.ep-blame-track { height: 6px; border-radius: 3px; background: var(--bg-sunken); overflow: hidden; }
.ep-blame-fill { display: block; height: 100%; background: var(--accent); }
.ep-blame-pct { text-align: right; font-size: 0.8rem; color: var(--text-secondary); font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------- instrumentation */
.ins-h { margin: 0 0 0.6rem; font-size: 0.95rem; }
.ins-steps { margin: 0 0 0.8rem; padding-left: 1.2rem; color: var(--text-primary); font-size: 0.88rem; line-height: 1.6; max-width: 76ch; }
.ins-steps li { margin-bottom: 0.35rem; }
/*
 * A note with an icon, and it must not be a flex container.
 *
 * It was, and the bug that produces is invisible until a note contains inline markup. **Every child
 * of a flex container becomes a flex item, including bare text nodes**, so
 *
 *     <p class="ins-note"><IconWarning />Older meshes have no <code>opentelemetry.http</code>
 *     setting and will send OTLP over gRPC.</p>
 *
 * laid out as four side-by-side columns — icon, "Older meshes have no", the `<code>`, then the rest
 * of the sentence — each wrapping inside its own narrow strip. On this page that is most of the
 * notes, because most of them name a field or a file. The first note on the screen has no inline
 * element in it and rendered perfectly, which is why the block looked like a one-off rather than a
 * rule that was wrong.
 *
 * A hanging indent instead: the icon is inline, so the text flows around it as text, and the
 * negative `text-indent` against the matching `padding-left` pulls the first line back under it. No
 * call site has to wrap its prose in a span, which is the other fix and the one that is forgotten at
 * the eleventh note.
 */
.ins-note {
  display: block;
  padding-left: 1.25rem;
  text-indent: -1.25rem;
  color: var(--text-secondary); font-size: 0.83rem; margin: 0.7rem 0 0; max-width: 78ch; line-height: 1.55;
}
.ins-note svg {
  width: 14px; height: 14px;
  /* Inline, and nudged onto the text baseline rather than sitting on it. */
  vertical-align: -2px;
  margin-right: 0.45rem;
  /* The indent is inherited by descendants otherwise, and a wrapped `<code>` gets pulled left. */
  text-indent: 0;
}
.ins-note code, .ins-note strong { text-indent: 0; }
.ins-field { display: flex; flex-direction: column; gap: 0.35rem; margin-bottom: 1.1rem; }
.ins-field label { font-size: 0.85rem; font-weight: 600; }
.ins-field input[type="text"], .ins-field input:not([type]), .ins-field select {
  padding: 0.45rem 0.6rem; border-radius: 6px; border: 1px solid var(--border-hairline);
  background: var(--bg-surface); color: var(--text-primary); max-width: 26rem;
}
.ins-hint { color: var(--text-secondary); font-size: 0.8rem; max-width: 78ch; line-height: 1.5; }
.ins-langs { border: 1px solid var(--border-hairline); border-radius: 8px; padding: 0.8rem; margin-bottom: 1.1rem; }
.ins-langs legend { font-size: 0.85rem; font-weight: 600; padding: 0 0.4rem; }
.ins-lang {
  display: grid; grid-template-columns: auto 1fr; gap: 0.2rem 0.6rem;
  padding: 0.5rem 0; border-bottom: 1px solid var(--border-hairline);
}
.ins-lang:last-child { border-bottom: 0; }
.ins-lang input { grid-row: 1 / 2; align-self: center; }
.ins-lang-name { display: flex; align-items: center; gap: 0.5rem; font-weight: 600; font-size: 0.88rem; }
.ins-lang-covers { grid-column: 2; color: var(--text-secondary); font-size: 0.8rem; }
/* The refusal reason. Wider than the row above it because it is a paragraph, not a label. */
.ins-lang-why { grid-column: 2; color: var(--text-secondary); font-size: 0.78rem; line-height: 1.5; max-width: 78ch; font-style: italic; }
.ins-lang.is-off .ins-lang-name { color: var(--text-secondary); }
.ins-problems { margin: 0; padding-left: 1.1rem; font-size: 0.85rem; }

/* ------------------------------------------------------- the two paths, side by side
 *
 * These three classes were the ones the page rendered and the stylesheet never defined, and the
 * default they fell back to is worse than nothing: `.ins-path` is a `<button>`, and a button centres
 * its own text. So the comparison — the one thing on the page that has to be *read across* — came
 * out as a centred ladder of alternating labels and values, `Reach` above `Every meshed workload at
 * once` above `Cost`, with no way to see that the two cards were answering the same three questions.
 *
 * The `<dl>` is the structure that says so, and it needs a two-column grid to look like one. The
 * term column is sized to the longest term rather than to content, so `Reach`, `Cost` and `Depth`
 * start their values at one x-position in both cards — that shared edge is what makes the pair
 * comparable at a glance, and it is the whole reason this is a definition list.
 */
.ins-paths {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
  gap: var(--space-3);
  margin-top: 0.9rem;
  align-items: start;
}

.ins-path {
  /* A button, so every inherited control default has to be undone before anything else applies. */
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  text-align: left;
  font: inherit;
  color: var(--text-primary);
  background: var(--bg-surface);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  padding: 0.85rem 0.95rem;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  transition: border-color 120ms var(--ease-out), background 120ms var(--ease-out);
}
.ins-path:hover { border-color: var(--border-strong); }
.ins-path.is-on {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 7%, var(--bg-surface));
}
.ins-path > strong { font-size: 0.92rem; }

.ins-path dl {
  display: grid;
  /* Sized to the longest term, so both cards align their values on one edge. */
  grid-template-columns: max-content 1fr;
  gap: 0.3rem 0.75rem;
  margin: 0;
  font-size: 0.82rem;
}
.ins-path dt {
  color: var(--text-secondary);
  font-weight: 600;
  /* Uppercase and tracked, so a term reads as a label rather than as the start of the value. */
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 0.72rem;
  padding-top: 0.1rem;
}
.ins-path dd { margin: 0; line-height: 1.5; }

/*
 * The limitation, styled to be unskippable — the component's own comment asks for that and nothing
 * delivered it.
 *
 * Same hanging indent as `.ins-note` and for the same reason: these strings name protocols and
 * headers, and the moment one of them is wrapped in a `<code>` a flex container would break the
 * sentence into columns again.
 */
.ins-limit {
  display: block;
  margin: 0;
  padding: 0.55rem 0.65rem 0.55rem 1.9rem;
  text-indent: -1.25rem;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--warn) 10%, transparent);
  color: var(--text-primary);
  font-size: 0.79rem;
  line-height: 1.5;
}
.ins-limit svg {
  width: 14px; height: 14px;
  vertical-align: -2px;
  margin-right: 0.45rem;
  color: var(--warn);
  text-indent: 0;
}
.ins-limit code, .ins-limit strong { text-indent: 0; }

/* ---------------------------------------------------------------- trace waterfall
 *
 * Two rules here are load-bearing, and both are about not lying with a rectangle.
 *
 * **`.tr-bar`'s `min-width` is in pixels, deliberately.** A 3ms hop inside a 40s trace is 0.0075%
 * of the track — a bar of zero width, and a reader takes "invisible" to mean "did not happen". A
 * percentage floor would fix that at one track width and distort more the wider the panel gets; a
 * pixel floor is a constant "this exists" mark whose proportional cost shrinks as there is more room
 * to be accurate in. Do not convert it to a percentage.
 *
 * **`.tr-bar.is-hairline` is a tick, not a short rectangle.** Once a bar is at the floor its width
 * is no longer a measurement, and a reader comparing two of them would be comparing the floor with
 * itself. Rendering it as a narrow mark says "here, but too small to scale" — the same move
 * `.ep-unclassified` above makes by hatching rather than filling. The duration is printed as text on
 * every row regardless, which is what lets the bar round without misleading anybody.
 *
 * Colour encodes status and never service identity. A hue per service (Jaeger's default) would spend
 * the alarm colour on an ordinary hop — see the note on two-hue discipline in `console/heatmap.tsx`.
 */
.tr-controls { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; margin-bottom: 1rem; }
.tr-window {
  padding: 0.3rem 0.8rem; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--border-hairline); background: var(--bg-surface); color: var(--text-secondary);
  font-size: 0.85rem;
}
.tr-window.is-on { background: var(--accent-soft); color: var(--accent); border-color: var(--accent); }

/*
 * The trace list's filters, set as the sentence they make: "Showing [whose] from the last [window]
 * only those containing [narrowings]". The connective words are quiet so the controls carry the
 * line; the two single choices are `Segmented`, the narrowings are checkbox toggles, and the panel
 * marks the whole row as the query the list below answers.
 */
.tr-filter {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3) var(--space-5);
  margin-bottom: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-lg);
  background: var(--bg-surface);
}
/* A word stays with its control, but the pair may break on a phone rather than run off the edge. */
.tr-filter-part {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  max-width: 100%;
}
.tr-filter-word {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  white-space: nowrap;
}
/* Wider than a phone's row: scroll inside the control rather than past the panel. */
.tr-filter .seg {
  max-width: 100%;
  overflow-x: auto;
  overscroll-behavior-x: contain;
}
/*
 * The chosen segment, outlined. `Segmented` lifts it to `--bg-surface`, which is this panel's own
 * fill — in the dark theme the selection read as a hole back to the panel rather than a raised
 * choice. A background swap cannot fix both themes (light's hover token equals its sunken one), so
 * the lift is an inset outline, which reads on either.
 */
.tr-filter .seg-item-on {
  box-shadow: inset 0 0 0 1px var(--border-strong);
  font-weight: 600;
}
.tr-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 28px;
  padding: 0 var(--space-3);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  background: var(--bg-sunken);
  color: var(--text-secondary);
  font: inherit;
  font-size: var(--text-xs);
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
}
.tr-toggle:hover {
  color: var(--hover-accent-text);
  transition: color var(--motion-fast) var(--ease-out);
}
.tr-toggle[aria-pressed="true"] {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--text-primary);
}
.tr-toggle:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 1px;
}
/* The checkbox: an outline when off, filled with a tick when on — the state reads without colour. */
.tr-toggle-box {
  position: relative;
  flex: 0 0 auto;
  width: 12px;
  height: 12px;
  border: 1.5px solid currentColor;
  border-radius: 3px;
  opacity: 0.7;
}
.tr-toggle[aria-pressed="true"] .tr-toggle-box {
  border-color: var(--accent);
  background: var(--accent);
  opacity: 1;
}
.tr-toggle[aria-pressed="true"] .tr-toggle-box::after {
  content: "";
  position: absolute;
  left: 3px;
  top: 0;
  width: 3px;
  height: 7px;
  border: solid var(--bg-surface);
  border-width: 0 1.5px 1.5px 0;
  transform: rotate(45deg);
}
/* The service clause once there are too many services to segment: the same well as `.seg`, so the
   row still reads as one set of controls rather than a form field dropped into a sentence. */
.tr-filter-select {
  height: 28px;
  max-width: 100%;
  padding: 0 var(--space-6) 0 var(--space-3);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  background: var(--bg-sunken);
  color: var(--text-primary);
  font: inherit;
  font-size: var(--text-xs);
  font-weight: 500;
  cursor: pointer;
}
.tr-filter-select:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 1px;
}
.tr-filter-clear {
  margin-left: auto;
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  font: inherit;
  font-size: var(--text-xs);
  cursor: pointer;
}
.tr-filter-clear:hover { text-decoration: none; }
.tr-filter-clear:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}
.tr-evidence { color: var(--text-secondary); font-size: 0.85rem; margin: 0 0 1rem; max-width: 72ch; }

.tr-stats { display: flex; flex-wrap: wrap; gap: 1.5rem; margin-bottom: 1rem; }

/*
 * The analysis panels below a waterfall, and the estate-wide performance board.
 *
 * `tr-stmt` wraps rather than truncates. A SQL statement is the identity of a finding here — two
 * queries against the same table differ in their WHERE clause, which is at the end — so an ellipsis
 * would hide the only part that distinguishes one N+1 from another. `overflow-wrap: anywhere`
 * because these strings have no spaces near the point a column boundary falls.
 */
.tr-insight { display: flex; flex-direction: column; gap: 1rem; margin-top: 1.5rem; }
.tr-stmt {
  font-family: var(--font-mono, ui-monospace, monospace); font-size: 0.78rem;
  color: var(--text-secondary); overflow-wrap: anywhere; white-space: pre-wrap;
  display: block; max-width: 60ch;
}
.tr-delta { font-variant-numeric: tabular-nums; }
/*
 * The declared severity tokens, with no fallback.
 *
 * A `var(--x, #hex)` fallback for a property nothing declares is a colour that never switches theme
 * and looks like a token — `fallbacks.test.ts` exists for exactly that, and caught an invented
 * `--ready` here. These two are declared in all three theme blocks above, so they follow the theme.
 */
.tr-delta.is-worse { color: var(--severity-critical-fg); font-weight: 600; }
.tr-delta.is-better { color: var(--severity-ok-fg); }
.tr-quiet { color: var(--text-secondary); }
/* Splits a chip row into groups — whose traces, then which window — so the two do not read as one
   list of eight interchangeable toggles. */
.ins-scan-detail { display: flex; flex-direction: column; gap: 0.35rem; padding: 0.5rem 0; }
.ins-scan-row {
  display: grid; grid-template-columns: minmax(12rem, 18rem) 10rem 1fr;
  gap: 0.75rem; align-items: baseline; font-size: 0.85rem;
}
.ins-scan-name { font-family: var(--font-mono, ui-monospace, monospace); overflow-wrap: anywhere; }
/* The detection evidence. Wraps rather than truncates: it names the image, and the image is the
   whole reason the answer is what it is. */
.ins-scan-why { color: var(--text-secondary); overflow-wrap: anywhere; }

.tr-caveat {
  display: flex; gap: 0.45rem; align-items: flex-start;
  color: var(--text-secondary); font-size: 0.82rem; margin: 0 0 0.9rem; max-width: 78ch; line-height: 1.55;
}
.tr-caveat svg { flex: 0 0 auto; width: 14px; height: 14px; margin-top: 2px; }

.tr-wf { display: flex; flex-direction: column; gap: 0.3rem; }
.tr-row { display: grid; grid-template-columns: minmax(0, 22rem) 1fr 5rem; gap: 0.75rem; align-items: center; }
.tr-row-label { display: flex; align-items: baseline; gap: 0.45rem; min-width: 0; }
.tr-service { font-weight: 600; font-size: 0.85rem; white-space: nowrap; }
.tr-op {
  color: var(--text-secondary); font-size: 0.8rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The one place a depth is spent: an indent that reads as nesting without a connector drawing. */
.tr-indent { flex: 0 0 auto; display: block; }

.tr-track { position: relative; height: 10px; border-radius: 5px; background: var(--bg-sunken); overflow: hidden; }
.tr-bar {
  position: absolute; top: 0; height: 100%;
  border-radius: 5px; background: var(--accent);
  /* See the header: pixels, never percent. */
  min-width: 2px;
}
.tr-bar.is-error { background: var(--critical); }
/* No longer a length — a mark. Square rather than rounded so it does not read as a tiny pill. */
.tr-bar.is-hairline { border-radius: 1px; }
/*
 * Clock skew: this bar lost an end to the clamp, and the reader is owed that fact.
 *
 * A border rather than an `outline`, because an outline here never painted. The bar is
 * `height: 100%` of the track and the track is `overflow: hidden` — which the track needs, so a bar
 * sitting at the far edge cannot paint past the rounded corner — so anything drawn outside the
 * bar's own box is clipped away on exactly the rows that carry this class. Measured against a real
 * trace at a 580px track: bar top == track top and bar height == track height, leaving the
 * outline's horizontal edges nowhere to land. `box-sizing: border-box` is universal here, so a
 * border paints inside the box instead and survives the clip. Dashed is kept deliberately: it is
 * the texture that says this edge is not the real one.
 */
.tr-bar.is-clipped { border: 1px dashed var(--warn-text); }

.tr-dur { text-align: right; font-size: 0.8rem; color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.tr-flag {
  display: inline-block; font-size: 0.72rem; color: var(--warn-text);
  border: 1px solid var(--warn-text); border-radius: 999px; padding: 0 0.4rem; white-space: nowrap;
}
.tr-detached { color: var(--text-secondary); font-size: 0.78rem; font-style: italic; white-space: nowrap; }

/* ==========================================================================
   The top bar
   ==========================================================================

   Full width, above both the sidebar and the content. The brand block moved here out of <nav>,
   where it had been boxed into the sidebar's width.

   This class was referenced from index.tsx before it existed, for one commit, and the result is
   worth recording: an unstyled `.app-topbar` is `display: block` with no height, so the brand and
   the search field stacked vertically, the bar grew to whatever they needed, and the shell below it
   lost its layout entirely. A className is a promise that a rule exists.
   ========================================================================== */
.app-topbar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  height: var(--size-topbar);
  /* No left padding: the brand block carries its own, so the logo lines up with the sidebar's
     icons below it rather than sitting inboard of them. */
  padding: 0 var(--space-4) 0 0;
  flex-shrink: 0;
  background: var(--chrome-topbar);
  /* The one seam that stays: it separates the chrome from everything below it, and unlike the
     sidebar's vertical rule it runs along an edge the eye already expects a boundary at.
     --chrome-seam is the hairline, not the strong border - see the token block for the measurements
     that made the difference matter. */
  border-bottom: 1px solid var(--chrome-seam);
  /* Above the content region, which floats its own panels and would otherwise paint over the seam
     while scrolling. */
  position: relative;
  z-index: var(--z-sticky);
}

/* The sidebar: the middle step of the chrome ladder.
 *
 * No border-right. It had one, and in light mode it was the wrong weight by a wide margin: the seam
 * resolved to --border-strong, which is #89919D there, and against the #F7F8FA sidebar that measures
 * 2.99:1 - close to the contrast of body text. Subtle in dark, a hard grey rule in light, and it was
 * the first thing anyone noticed about the new layout.
 *
 * The separation is carried by the fills instead. That is weaker than a rule and it is the trade
 * that was asked for; if the boundary turns out to need help, the honest fix is a stronger fill
 * difference rather than a heavier line. */
.app-sidebar {
  background: var(--chrome-sidebar);
  /* Layout lives here rather than inline in `AppShell`, so the phone and drawer rules below can
     change it — an inline `display` or `position` beats every rule in this file (trap 30). */
  display: flex;
  flex-direction: column;
  position: relative;
  flex-shrink: 0;
  height: 100%;
}

/*
 * The navigation drawer, below 1024px.
 *
 * On a phone there is no rail: 56px of a 390px screen is a seventh of it, spent on icons a touch
 * screen can never label (their names were `title` tooltips, which need a hover). The sidebar is
 * hidden and the hamburger opens it here — full height, labels and groups intact, over a scrim. On a
 * tablet the rail stays and the same drawer floats over it, with `.app-sidebar-spacer` holding the
 * rail's 56px so the page does not jump sideways when the menu opens.
 *
 * Above the top bar, the alert rail and the assistant dock, and below a dialog or a toast. The
 * insets are the notch and the home indicator; they resolve to 0 until the page opts into
 * `viewport-fit=cover`, and are correct either way.
 */
@media (max-width: 768px) {
  .app-sidebar:not(.app-sidebar--drawer) {
    display: none;
  }
}

.app-sidebar--drawer {
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  height: 100dvh;
  z-index: calc(var(--z-modal) - 1);
  padding-top: env(safe-area-inset-top, 0px);
  padding-bottom: env(safe-area-inset-bottom, 0px);
  padding-left: env(safe-area-inset-left, 0px);
  border-right: 1px solid var(--chrome-seam);
  animation: nav-drawer-in var(--motion-base) var(--ease-out);
}

@keyframes nav-drawer-in {
  from { transform: translateX(-24px); opacity: 0; }
}

.nav-scrim {
  position: fixed;
  inset: 0;
  z-index: calc(var(--z-modal) - 2);
  background: var(--color-scrim);
  /* The page behind does not scroll under a finger dragged across the scrim. */
  overscroll-behavior: contain;
  touch-action: none;
}

.app-sidebar-spacer {
  flex-shrink: 0;
  width: var(--size-nav-rail);
  background: var(--chrome-sidebar);
}

/* The drawer's own head, standing in for the top bar the scrim covers: mark, name, close. */
.nav-drawer-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  height: var(--size-topbar);
  flex-shrink: 0;
  padding: 0 var(--space-2) 0 var(--space-3);
  border-bottom: 1px solid var(--chrome-seam);
}

.nav-drawer-title {
  flex: 1;
  min-width: 0;
  font-size: var(--text-lg);
  font-weight: 600;
  letter-spacing: -0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.nav-drawer-close {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border: none;
  color: var(--text-secondary);
  cursor: pointer;
}

@media (prefers-reduced-motion: reduce) {
  .app-sidebar--drawer { animation: none; }
}

/* ============================================================================
   The assistant: the chat itself, and the handle that pulls it up.

   ## Why these rules did not exist

   `apps/portal/app/sre/page.tsx` shipped with a full set of class names and no stylesheet behind
   any of them. Every `.sre-*` selector below was being asked for by that page and resolving to
   nothing, so the AI SRE console rendered as stacked unstyled divs: a bare textarea, a default
   button, no thread. It looked broken rather than unfinished, which is worse - a reader assumes the
   feature is broken rather than that the CSS is missing.

   These rules are therefore not new decoration. They are the missing half of a page that already
   exists, and the dock below reuses them so there is one chat in the product rather than two that
   drift.
   ============================================================================ */

.sre {
  display: flex;
  flex-direction: column;
  /* The composer is pinned to the bottom of whatever box this sits in; the thread scrolls above it.
     `min-height: 0` is load-bearing on the child - a flex item's default `min-height: auto` refuses
     to shrink below its content, so without it the thread grows the container instead of scrolling
     and the composer is pushed off the bottom of the panel. */
  min-height: 0;
  gap: var(--space-3);
}

/*
 * Split in two, because `<Conversation>` scrolls a box of its own.
 *
 * `use-stick-to-bottom` renders three elements: a positioned wrapper (`.sre-stream`), the scroller
 * it keeps pinned, and the content inside that. The scroller is the middle one and the library owns
 * its `height`, so `.sre-thread` is passed to it as `scrollClassName` and carries only what a
 * scroller needs. Putting the flex column and the gap there instead would space the scroller's one
 * child against nothing, and the turns would run together with no sign of why.
 */
.sre-thread {
  min-height: 0;
  min-width: 0;
  overflow-y: auto;
  padding-right: var(--space-2);
}

/* The turns themselves. The component's own `p-4` is dropped at the call site with `p-0`, so the
   product's spacing is the only spacing here. */
.sre-thread-items {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  min-width: 0;
}

/*
 * `min-width: 0` down the whole chain, and it is a bug fix rather than a precaution.
 *
 * The evidence `<pre>` has `overflow-x: auto`, which everyone assumes bounds it. It does not: a flex
 * item's default `min-width` is `auto`, meaning "at least max-content", so the pre's intrinsic width
 * — a 300-character line of `{id: …, detail: …}` — propagates up through `.sre-block`, `.sre-answer`
 * and `.sre-turn` and widens the sheet itself. The scroller then has nothing to scroll, because its
 * container grew to fit. That is why the panel ran off the right of the window with the text clipped
 * by the viewport instead of scrolling inside its own block.
 */
.sre-turn {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}

/* The question, right-aligned and in the accent wash.
   Asymmetry is doing the work here rather than an avatar: two speakers, one aligned each way, is
   the cheapest way to make a transcript scannable and it survives at any width. */
.sre-question {
  align-self: flex-end;
  max-width: 85%;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  background: var(--accent-soft);
  color: var(--text-primary);
  font-size: var(--text-sm);
  line-height: 1.5;
  white-space: pre-wrap;
  /* A pasted stack trace must not widen the panel; it wraps mid-token instead. */
  overflow-wrap: anywhere;
}

/*
 * The answer, and it is deliberately the largest text in the panel.
 *
 * It was `--text-sm` — the same size as the question bubble and barely above the evidence — in a
 * panel where the evidence is several hundred lines. Whatever is biggest is what a reader treats as
 * the point, so the point has to be biggest. `--measure` keeps a line near 65 characters even when
 * the sheet is wide, because prose set to the full width of a laptop is read by nobody.
 */
.sre-answer {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  color: var(--text-primary);
  min-width: 0;
}

.sre-answer p { margin: 0; max-width: 68ch; overflow-wrap: anywhere; }

.sre-working {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  color: var(--text-secondary);
  font-size: var(--text-sm);
}

.sre-spin { animation: sre-spin 1s linear infinite; flex: none; }
@keyframes sre-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .sre-spin { animation: none; } }

/* Which tools ran. Failures are shown, never omitted - an answer built from three of four
   measurements has to look different from one built from four. */
.sre-tools-ran {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

.sre-tool {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 2px var(--space-2);
  border-radius: var(--radius-full);
  border: 1px solid var(--color-border);
  font-size: var(--text-xs);
  color: var(--text-secondary);
}
.sre-tool svg { width: 12px; height: 12px; flex: none; }
.sre-tool code { font-size: var(--text-xs); }
/*
 * The status colours in the chat, on the product's own tokens.
 *
 * These were `var(--tone-ok, #3FB950)` and two more like it, and `--tone-ok` has never been declared
 * anywhere — so the fallback was not a fallback, it was the value, in both themes. And the three
 * hexes behind those names are exactly the dark palette's `--ok`, `--critical` and `--warn`, so a
 * light page rendered them at around 2:1 against white: a tool marked "ok" was a pale green smudge
 * and the dropped-evidence warning was the least legible line in the panel.
 *
 * `tokens.test.ts` cannot catch this and says so — it exempts a `var()` with a fallback, on the
 * reasoning that a declared fallback is a deliberate optional read. That is true of a fallback for
 * something optional and false of one standing in for a token nobody created.
 *
 * `-text` for foregrounds, the plain token for the border: the same split every severity surface in
 * this product uses, and the reason light's `--critical-text` is a deep red while `--critical` stays
 * bright enough to see as a 1px line.
 */
.sre-tool.ok svg { color: var(--ok-text); }
.sre-tool.bad {
  border-color: var(--critical);
  color: var(--critical-text);
}
.sre-tool.bad em { font-style: normal; opacity: 0.85; }

.sre-dropped {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin: var(--space-3) 0 0;
  font-size: var(--text-xs);
  color: var(--warn-text);
}
.sre-dropped svg { width: 14px; height: 14px; flex: none; margin-top: 2px; }

.sre-evidence, .sre-tools { margin-top: var(--space-3); min-width: 0; }
.sre-evidence > summary, .sre-tools > summary {
  cursor: pointer;
  font-size: var(--text-xs);
  color: var(--text-secondary);
  -webkit-user-select: none;
     -moz-user-select: none;
          user-select: none;
  padding: var(--space-1) 0;
  border-radius: var(--radius-sm);
}
.sre-evidence > summary:hover, .sre-tools > summary:hover { color: var(--accent-text); }
.sre-evidence > summary:focus-visible,
.sre-tools > summary:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.sre-evidence-label { letter-spacing: 0.01em; }

.sre-evidence-body { display: grid; gap: var(--space-2); min-width: 0; margin-top: var(--space-2); }

.sre-evidence-bar {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.sre-evidence-note { font-size: var(--text-xs); color: var(--text-muted); margin-right: auto; }

.sre-evidence-wrap {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  background: var(--bg-sunken);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-sm);
  padding: 2px var(--space-2);
  font: inherit;
  font-size: 10px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
  cursor: pointer;
  flex: none;
}
.sre-evidence-wrap:hover { color: var(--text-primary); border-color: var(--border-strong); }
.sre-evidence-wrap:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }

.sre-block {
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  overflow: hidden;
  min-width: 0;
}
/* A cited block is marked on its leading edge rather than by a fill: the evidence is monospace on a
   sunken ground and a second background here would fight the <pre> inside it. */
.sre-block.cited { border-left: 2px solid var(--accent); }

.sre-block-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  background: var(--bg-sunken);
  border-bottom: 1px solid var(--border-hairline);
  min-width: 0;
}
/* The human title leads. The citation key is for matching a reference in the answer back to here,
   which is a lookup and not a heading — so it is last, small and muted. */
.sre-block-title {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text-primary);
  margin-right: auto;
  overflow-wrap: anywhere;
}
.sre-block-id {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--text-muted);
  flex: none;
}

.sre-block-pre {
  margin: 0;
  padding: var(--space-3);
  font-size: var(--text-xs);
  line-height: 1.6;
  color: var(--text-secondary);
  /* Its own scroller in verbatim mode; the page body must never scroll sideways because of it. */
  overflow-x: auto;
  /* And the wheel stops here. Without this, reaching the end of a wide evidence line hands the
     remaining scroll to the page, which slides the whole sheet sideways under the reader. */
  overscroll-behavior-x: contain;
  white-space: pre;
  min-width: 0;
  max-height: 22rem;
  overflow-y: auto;
}
/*
 * Wrapped is the default view, and it changes no characters — only where the line ends on screen.
 * The hanging indent keeps a wrapped row visibly part of the row above it rather than reading as a
 * new one, which is what makes a list of `- {…}` rows still scannable after it wraps.
 */
.sre-block-pre.is-wrapped {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  overflow-x: hidden;
  padding-left: calc(var(--space-3) + 1.25rem);
  text-indent: -1.25rem;
}

/*
 * The live panel: what is being done, while it is being done.
 *
 * Every row here has a twin in `.sre-tools-ran` once the answer lands, and the two are deliberately
 * different shapes — this one is a list because it grows a row at a time and the eye should be able
 * to follow the newest, the finished one is a wrapped strip because it is a summary being scanned.
 * They carry the same facts either way.
 */
.sre-live { display: grid; gap: var(--space-2); min-width: 0; }

/*
 * The working line wraps rather than overflowing.
 *
 * `.sre-working` is a flex row written for one short sentence, and the phases here are longer —
 * "Checking every claim in the answer against those measurements." beside a clock. At the sheet's
 * full width they share a line; at phone width, without this, the clock is pushed off the edge and
 * the sentence is clipped. `min-width: 0` is what lets the sentence give way; a flex item defaults
 * to `min-width: auto`, so it would otherwise refuse to shrink below its own longest word.
 */
.sre-live > .sre-working { flex-wrap: wrap; }
.sre-live-phase { min-width: 0; overflow-wrap: anywhere; }

/* Pushed to the end of the working line and set apart, so the sentence reads as a sentence and the
   numbers read as an instrument. `tabular-nums` stops the seconds jittering the line on every tick. */
.sre-live-clock {
  margin-left: auto;
  flex: none;
  font-size: var(--text-xs);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* The model's own words about why it chose these measurements. Quoted, not paraphrased. */
.sre-live-why {
  margin: 0;
  font-size: var(--text-xs);
  line-height: 1.6;
  color: var(--text-secondary);
  max-width: 68ch;
  overflow-wrap: anywhere;
}

.sre-live-calls { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-1); min-width: 0; }

.sre-live-call {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  min-width: 0;
  /* Wraps rather than truncating: the arguments are the point, and a clipped `search: …` is the one
     value a reader needed to see. */
  flex-wrap: wrap;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
  font-size: var(--text-xs);
}
.sre-live-call.running { background: var(--bg-hover); }
.sre-live-call.bad { background: var(--severity-critical-bg); }

/* `align-items: baseline` on the row is right for the text and wrong for a 12px icon, which would
   sit on the text baseline instead of centred on it. The mark opts out for itself. */
.sre-live-mark { flex: none; align-self: center; display: inline-flex; width: 12px; height: 12px; }
.sre-live-mark svg { width: 12px; height: 12px; }
.sre-live-call.ok .sre-live-mark { color: var(--ok-text); }
.sre-live-call.bad .sre-live-mark { color: var(--critical-text); }
.sre-live-call.running .sre-live-mark { color: var(--accent); }

.sre-live-tool { flex: none; color: var(--text-primary); font-size: var(--text-xs); }
.sre-live-args {
  min-width: 0;
  color: var(--text-muted);
  font-family: var(--font-mono);
  overflow-wrap: anywhere;
}
.sre-live-ms { margin-left: auto; flex: none; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.sre-live-error { color: var(--critical-text); min-width: 0; overflow-wrap: anywhere; }

/* Why the plan is being made a second time. Warn-toned because it is not an error — the question is
   still being answered — but it is the reason the wait just got longer. */
.sre-live-note {
  margin: 0;
  font-size: var(--text-xs);
  color: var(--warn-text);
  max-width: 68ch;
  overflow-wrap: anywhere;
}

/* The arguments on a finished call. Same reasoning as the live row; muted so the tool name still
   leads, monospace because these are values from a cluster and not prose. */
.sre-tool-args {
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  min-width: 0;
  overflow-wrap: anywhere;
}

.sre-tools ul { margin: var(--space-2) 0 0; padding-left: var(--space-4); }
.sre-tools li { font-size: var(--text-xs); color: var(--text-secondary); margin-bottom: var(--space-1); }
.sre-tools li code { margin-right: var(--space-2); color: var(--text-primary); }

/*
 * The first screen, which is a different layout from the rest of the conversation rather than the
 * same one with nothing in it.
 *
 * What was here before was an opening *card*: an icon, a heading and a paragraph stacked at the top
 * of a scroller that had nothing to scroll, with the composer pinned to the bottom of the sheet a
 * few hundred pixels below it. The one thing a newcomer is meant to do was the furthest thing on
 * screen from the only thing on screen, and the space between them was empty.
 *
 * Centred instead, with the question box directly under the greeting — the shape every assistant a
 * customer has already used opens in, and the reason it is that shape is that there is no transcript
 * yet to anchor the box to the bottom of. `.sre-composer` moves back to the bottom the moment there
 * is something above it; see `sre--opening` in chat.tsx.
 *
 * `--text-2xl` is a real step in this scale (28px) and not an invented one. The note this rule
 * replaced said `--text-lg` was the top of the system, which was true of the panel and never true of
 * the stylesheet.
 */
.sre--opening { justify-content: center; }

/*
 * `flex: 1 1 auto` with `overflow-y: auto` and `margin: auto` on the inner block is the combination
 * that centres and stays reachable: the group sits in the middle of a tall panel, and in a short one
 * — the dock on a laptop, with the tool list expanded — it scrolls instead of being clipped with the
 * composer pushed out of the sheet.
 */
.sre-hero {
  display: flex;
  flex: 1 1 auto;
  min-height: 0;
  min-width: 0;
  overflow-y: auto;
  padding: var(--space-6) var(--space-2);
}

.sre-hero-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  width: 100%;
  min-width: 0;
  max-width: 640px;
  margin: auto;
}

/*
 * Nothing in the centred group may be shrunk to make it fit, and this is a bug fix rather than a
 * precaution.
 *
 * A flex item's default `flex-shrink: 1` applies down a column too. In a dock 431px tall the group
 * wanted more room than that, so every child was compressed — and the one that gives is the wrapping
 * strip of suggestions, which does not reflow when it is squashed: it keeps its three rows and paints
 * the last one straight through the sentence underneath. Measured at 88px tall holding 121px of
 * chips, with the fourth chip ending 34px below its own container and over the provenance note.
 *
 * `flex: none` makes the group keep its real height, which `.sre-hero`'s `overflow-y: auto` then
 * scrolls. The failure was invisible on a tall window and present on every short one.
 */
.sre-hero-inner > * { flex: none; max-width: 100%; }

.sre-hero-mark { width: 28px; height: 28px; color: var(--accent); flex: none; }

.sre-hero-title {
  margin: 0;
  text-align: center;
  font-size: var(--text-2xl);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.25;
  color: var(--text-primary);
  text-wrap: balance;
}

/*
 * The provenance sentence, kept on this screen and set quietly under the box rather than dropped for
 * the clean look. It is the panel's claim about where an answer comes from, and this is the one
 * moment somebody reads it.
 */
.sre-hero-note {
  margin: 0;
  max-width: 60ch;
  text-align: center;
  font-size: var(--text-sm);
  color: var(--text-muted);
  line-height: 1.6;
}

.sre-hero-tools { margin-top: 0; text-align: center; }
.sre-hero-tools ul { text-align: left; display: inline-block; }

/*
 * The strip, on this screen only: centred, and released from the cap it wears in the thread.
 *
 * `.sre-examples` is capped at 5.5rem with its own scroller further down this file, and the reason
 * given there is precise and still correct — in the thread layout the strip is a sibling of
 * `.sre-thread`, which is `flex: 1 1 auto`, so an unbounded strip does not overflow, it takes the
 * transcript's height.
 *
 * There is no thread on this screen, so the cap has no job here and does real harm: four chips need
 * 122px, the cap gives them 88, and the fourth is drawn clipped inside a scroller nobody can see
 * over the top of the provenance sentence. `.sre-hero` already scrolls the whole group, which is the
 * same guarantee — nothing pushed out of reach — applied at the level that has one here.
 */
.sre--opening .sre-examples {
  justify-content: center;
  max-height: none;
  overflow-y: visible;
}

.sre-examples, .sre-followups {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.sre-followups { margin-top: var(--space-3); }

/*
 * Example questions, which are controls and were drawn as faint 11px outlines — the same treatment
 * this stylesheet gives read-only status pills, so the one part of the empty state a newcomer is
 * meant to press looked like the part they were meant to ignore.
 *
 * Set at the body size on a filled ground so they read as pressable, and left pill-shaped so they
 * stay clearly distinct from the square-cornered evidence blocks further down the same column.
 */
.sre-example {
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-full);
  border: 1px solid var(--color-border);
  background: var(--bg-sunken);
  color: var(--text-primary);
  font: inherit;
  font-size: var(--text-sm);
  cursor: pointer;
  text-align: left;
  transition: border-color var(--motion-fast) var(--ease-out), color var(--motion-fast) var(--ease-out),
    background var(--motion-fast) var(--ease-out);
}
.sre-example:hover {
  border-color: var(--accent);
  color: var(--accent-text);
  background: var(--accent-soft);
}
.sre-example:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }

.sre-composer {
  display: flex;
  gap: var(--space-2);
  align-items: flex-end;
  flex: none;
}

.sre-input {
  flex: 1 1 auto;
  min-width: 0;
  resize: none;
  padding: var(--space-3);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
  background: var(--bg-sunken);
  color: var(--text-primary);
  font: inherit;
  font-size: var(--text-sm);
  line-height: 1.5;
}
.sre-input:focus-visible { outline: 2px solid var(--accent); outline-offset: -1px; }
.sre-input:disabled { opacity: 0.6; }

.sre-send {
  flex: none;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  background: var(--accent-solid);
  /* The token, not the literal it currently equals. Both themes put white here today; a change to
     one of them would otherwise leave this button's label behind in the other. */
  color: var(--on-accent);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
  transition: background var(--motion-fast) var(--ease-out), border-color var(--motion-fast) var(--ease-out);
}
/*
 * An edge, because the fill alone does not give it one in the default theme: `--accent-solid` is
 * #1A1A1D there against a #121214 surface, so the panel's primary action was a nearly invisible
 * rectangle two shades off its own background. The border is drawn from the same token in both
 * themes, so the light theme gains a defined edge rather than a second colour.
 */
.sre-send { border-color: var(--border-strong); }
/*
 * Hover brightens the edge and never the fill. `--accent-hover` is #FFFFFF in the default theme
 * while `--on-accent` is #F0F0F1, so using it as a background here would have put white text on a
 * white button, so this button declines to define a hover fill at all rather than picking one that
 * works in a single theme.
 */
.sre-send:hover:not(:disabled) { border-color: var(--accent); }
.sre-send:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.sre-send:disabled { opacity: 0.45; cursor: not-allowed; }

.sre-foot, .sre-provenance {
  margin: 0;
  font-size: var(--text-xs);
  color: var(--text-muted);
  flex: none;
}
/*
 * Provenance, readable. It was `--text-muted` on a card at 11px — the lowest-contrast text in the
 * panel, carrying the two facts most worth checking: which model answered, and whether the question
 * left the network. The egress half is now marked, because "stayed on your network" and "left your
 * network" differ by one word and were rendered identically.
 */
.sre-provenance {
  margin-top: var(--space-3);
  padding-top: var(--space-2);
  border-top: 1px solid var(--border-hairline);
  font-size: var(--text-xs);
  color: var(--text-secondary);
}
.sre-provenance .sre-egress-out { color: var(--warn-text); font-weight: 600; }
.sre-provenance .sre-egress-local { color: var(--ok-text); }

/* ---------------------------------------------------------------------------
   The handle, and the sheet it pulls up.

   ## Why a drag and a click, not a drag alone

   The gesture asked for is a pull-up. A control that ONLY responds to a drag is unreachable from a
   keyboard, invisible to a screen reader, and undiscoverable to anyone who clicks it once, sees
   nothing happen, and concludes it is decoration. So the handle is a real <button>: click, Enter and
   Space open it, and the drag is an enhancement layered on top for people who reach for it.

   ## Why it is fixed to the viewport and not to the content column

   It has to be reachable from every page at any scroll position - that is the point of a dock rather
   than a panel on one page. Fixed positioning with a bottom-centre anchor is the only placement that
   does not move as the content behind it changes length.
   --------------------------------------------------------------------------- */

/* The launcher is a wrapper, not the control: it holds the ring canvas and the button together so
   one transform moves both, and so the glow can bleed past the button instead of being clipped by
   it. It is inert — every pointer event belongs to the button inside. */
.chatdock-launcher {
  position: fixed;
  left: 50%;
  bottom: 0;
  transform: translateX(-50%);
  /* Above sticky chrome, below modals and toasts: a dialog opened from a page must still cover this,
     and a toast must still cover the dialog. */
  z-index: var(--z-overlay);
  /* 76 around a 34px visible puck, sat on the bottom edge. The ratio matters more than either
     number: ring radii are normalised to this box, so the puck covers everything inside about 34/76
     and the rings the dock asks for start just outside it - the same proportion as the 44-in-96 it
     replaced (2026-10-21, "make this little small"). Shrinking the box alone is safe; shrinking the
     puck without the box would push the innermost ring under it. */
  width: 76px;
  height: 76px;
  display: grid;
  place-items: center;
  /* Otherwise this square would swallow every click within ~26px of the button, including clicks on
     whatever the page has put at the bottom centre. The rings inside carry it too. */
  pointer-events: none;
  transition: transform 140ms ease;
}

/* The rings fill the launcher, so they extend well past the 44px puck — that overhang is the glow. */
.chatdock-rings {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.chatdock-handle {
  position: relative;
  pointer-events: auto;
  /* 44 is the floor, not a preference: it is the smallest target WCAG 2.2 asks for, and this is the
     only way into the assistant from most of the product. The button keeps it while the puck a
     reader sees is drawn smaller inside it by ::before - smaller to look at, never smaller to hit. */
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--text-secondary);
  font: inherit;
  cursor: pointer;
  /* The drag is a vertical gesture on a touch device; without this the browser claims it for
     scrolling and the pointermove events never arrive. */
  touch-action: none;
  transition: color 140ms ease, border-color 140ms ease, background-color 140ms ease;
}

/* `:has` rather than a rule on the button, so the halo lifts with the puck rather than the puck
   sliding out of its own glow. */
.chatdock-launcher:has(.chatdock-handle:hover),
.chatdock-launcher:has(.chatdock-handle:focus-visible) {
  /* Lifts a few pixels on hover: the affordance for "this comes up" is that it already moves up. */
  transform: translateX(-50%) translateY(-4px);
}
/* The visible puck: 34px, centred in the 44px target, carrying the border and surface. */
.chatdock-handle::before {
  content: "";
  position: absolute;
  inset: 5px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  background: var(--color-surface);
  transition: border-color 140ms ease, background-color 140ms ease;
}
.chatdock-handle:hover,
.chatdock-handle:focus-visible {
  color: var(--accent-text);
}
.chatdock-handle:hover::before,
.chatdock-handle:focus-visible::before {
  border-color: var(--accent);
  background: var(--bg-hover);
}
/* Drawn on the puck, not on the larger invisible target around it. */
.chatdock-handle:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; }

/* Positioned so it paints above the puck, which as a positioned box would otherwise cover it. */
.chatdock-chevron { position: relative; width: 14px; height: 14px; flex: none; }

/* While a drag is in progress the launcher follows the pointer, so the sheet feels attached to the
   finger rather than appearing after it. `--chatdock-drag` is set inline by the component. */
.chatdock-launcher[data-dragging="true"] {
  transition: none;
  transform: translateX(-50%) translateY(calc(-1 * var(--chatdock-drag, 0px)));
}

@media (prefers-reduced-motion: reduce) {
  .chatdock-handle,
  .chatdock-handle::before,
  .chatdock-launcher,
  .chatdock-launcher[data-dragging="true"] { transition: none; }
}

/* The mount point `MagicRings` renders. The canvas it appends is sized in JS from these bounds, so
   a rule that left this at `auto` would give the shader a zero-height viewport. */
.magic-rings { width: 100%; height: 100%; }
.magic-rings canvas { display: block; }

.chatdock-scrim {
  position: fixed;
  inset: 0;
  /* One above the overlay layer, with the sheet: the alert rail is portalled to <body> (so it follows
     the page in tab order) and therefore comes later in the DOM; at an equal z-index it would paint
     over an open sheet. */
  z-index: calc(var(--z-overlay) + 1);
  background: rgba(0, 0, 0, 0.45);
}

.chatdock-sheet {
  position: fixed;
  left: 50%;
  bottom: 0;
  transform: translateX(-50%);
  z-index: calc(var(--z-overlay) + 1);
  /*
   * The working size of the panel, and it is deliberately most of the window.
   *
   * It was 760 x 600, which was sized for a chat bubble and not for what this thing actually shows:
   * an answer, the tools that produced it with their arguments, and the evidence those tools
   * returned — the last of which is now open rather than folded away. At 600px tall the evidence
   * had nowhere to go, which is most of why it was hidden in the first place.
   *
   * `100vw`/`dvh` keep the ceiling at the viewport, so this is "as large as the window allows" and
   * not a fixed box that can hang off the bottom of a laptop screen. `dvh` rather than `vh` because
   * a mobile browser's collapsing address bar would otherwise push the composer out of view.
   *
   * The remaining margin is not decoration: the scrim behind it has to stay clickable, or the only
   * ways out of a full-screen sheet are Escape and the close button, and a reader who reaches for
   * neither is stuck on top of the page they wanted to look at.
   */
  width: min(1180px, calc(100vw - var(--space-6)));
  height: min(920px, calc(100dvh - var(--space-6)));
  display: flex;
  flex-direction: column;
  border: 1px solid var(--color-border);
  border-bottom: none;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  background: var(--color-surface);
  box-shadow: 0 -8px 40px rgba(0, 0, 0, 0.35);
  animation: chatdock-rise 180ms ease-out;
}

@keyframes chatdock-rise {
  from { transform: translateX(-50%) translateY(16px); opacity: 0; }
  to   { transform: translateX(-50%) translateY(0); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .chatdock-sheet { animation: none; }
}

.chatdock-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--color-border);
  flex: none;
}

.chatdock-title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text-primary);
}
.chatdock-title svg { width: 16px; height: 16px; color: var(--accent); }

.chatdock-head-actions { margin-left: auto; display: flex; align-items: center; gap: var(--space-1); }

.chatdock-icon-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-secondary);
  cursor: pointer;
}
.chatdock-icon-button:hover { background: var(--bg-hover); color: var(--text-primary); }
.chatdock-icon-button:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.chatdock-icon-button svg { width: 15px; height: 15px; }

/* ------------------------------------------------------------------ the readout
 *
 * What this assistant can sense, and what one question is allowed to spend of it.
 *
 * The panel is not a chat window and should not open looking like one. Everything it says is
 * screened against measurements it actually took — so the honest first impression is an instrument
 * face: the number of measurements available, and the share of them a single question may use.
 *
 * Both numbers were previously a sentence inside the empty state, which put them on screen exactly
 * until the first answer replaced them. They govern every answer after that one too.
 *
 * The figures are set in the mono face with `tabular-nums` because they are readings, and because
 * a two-digit catalogue must not shift the label when it ticks to three.
 */
.chatdock-readout {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border-bottom: 1px solid var(--color-border);
  background: var(--bg-sunken);
}
.chatdock-readout[hidden] { display: none; }

.chatdock-readout-figure {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--text-primary);
  line-height: 1;
}

/*
 * `--text-secondary`, not `--text-muted`. Muted is #5E5E66 on the #1A1A1D sunken ground in the
 * default theme, which measures 2.7:1 — a legible-looking grey that fails AA for 11px text, and
 * these two words are what say which number is the catalogue and which is the budget. Secondary
 * measures 6.2:1 on the same ground and 8:1 in the light theme.
 */
.chatdock-readout-label {
  font-size: var(--text-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-secondary);
}

/*
 * The ratio, drawn once so "4 of 12" needs no arithmetic. `--fill` is set inline because it is the
 * only value here that is data rather than design. Flexible so the strip stays one line at any
 * sheet width, with a floor that keeps it a bar rather than a dot on a narrow viewport.
 */
/* The bar that drew "N of M per question" is gone with the per-question cap; the label sits right. */
.chatdock-readout-budget {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-left: auto;
}

/* ------------------------------------------------------------------ the panel with no chat in it
 *
 * When the catalogue cannot be fetched the console never mounts, so this block is the entire panel
 * rather than a line inside it. The state it renders most often is the collection guard's 503 — a
 * cluster that has stopped reporting — which made it, before this, the least finished surface in the
 * product sitting at the exact moment a customer most needs the product to make sense.
 *
 * Centred and filling the sheet, because a message pinned to the top-left of six hundred empty
 * pixels reads as a chat window that failed to load. Centred, it reads as the answer.
 */
.sre-state {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  padding: var(--space-6);
  text-align: center;
}

.sre-state-icon {
  width: 28px;
  height: 28px;
  flex: none;
  color: var(--warn-text);
}
.sre-state-icon--spin { color: var(--text-secondary); animation: sre-spin 1s linear infinite; }
@media (prefers-reduced-motion: reduce) { .sre-state-icon--spin { animation: none; } }

.sre-state-title {
  margin: 0;
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--text-primary);
}

/*
 * The server's own sentence, at a size somebody reads. It names the cluster, when collection
 * stopped and what to do about it — the three facts the panel exists to deliver in this state — and
 * it was previously set smaller and fainter than anything else on screen.
 */
.sre-state-body {
  margin: 0;
  max-width: 52ch;
  font-size: var(--text-sm);
  line-height: 1.6;
  color: var(--text-secondary);
}

/*
 * `--text-secondary`, for the reason the readout label uses it: muted measures 2.9:1 against the
 * sheet in the default theme and fails AA at 11px. This line says a question the reader typed was
 * thrown away, which is not a detail to render in the least legible ink on the panel.
 */
.sre-state-note {
  margin: 0;
  max-width: 52ch;
  font-size: var(--text-xs);
  color: var(--text-secondary);
  border-top: 1px solid var(--border-hairline);
  padding-top: var(--space-2);
}

/* The body owns the padding and the scroll boundary; `.sre` inside it is the same chat the /sre
   page renders, unchanged. `min-height: 0` again, for the same flex reason. */
.chatdock-body {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  padding: var(--space-4);
}
.chatdock-body > * { flex: 1 1 auto; min-height: 0; }

/* `hidden` has to be given back its meaning here, and this is not a nicety.
 *
 * The UA stylesheet's `[hidden] { display: none }` is a single type-less rule and loses to any class
 * that sets `display` — `.chatdock-sheet` sets `display: flex`, so the attribute did nothing at all.
 * The sheet stayed on screen after Escape, and because it is `position: fixed` over the bottom of the
 * viewport it also went on swallowing every click aimed at the handle underneath it: the dock could
 * be opened once and then neither closed nor reopened.
 *
 * Attribute-plus-class beats class, so no `!important` is needed. Anything else that hides a flex or
 * grid container with `hidden` needs the same line — it is the attribute that is fragile, not this
 * component. */
.chatdock-sheet[hidden],
.chatdock-scrim[hidden] { display: none; }
/* The sheet takes focus itself only until its message box exists (see `chat-dock.tsx`); a ring around
   the whole panel for that moment would read as a selection, not as where typing will go. */
.chatdock-sheet:focus { outline: none; }

/* And the tab bodies, for the identical reason — `.chatdock-body` sets `display: flex` too.
 *
 * The dock keeps both halves mounted and hides the inactive one so neither loses its state: the
 * chat keeps its transcript, the shell keeps the pod you picked and the command you were half way
 * through typing. Without this line `hidden` loses to that `display: flex` and both render at once,
 * stacked — the chat and the command box on screen together, with the tabs claiming otherwise. */
.chatdock-body[hidden] { display: none; }

/* ------------------------------------------------------------------ ask about this
 *
 * The double-click affordance and the popover that confirms the question. See
 * `apps/portal/app/ask-about.tsx` for why the gesture works the way it does.
 *
 * The hover marker is an `outline` and deliberately nothing else. `outline` takes no space, creates
 * no stacking context and does not need `position: relative` — and that last one matters, because
 * `[data-ask]` lands on a `<tr>` and on a row whose whole hit area is a stretched `::after`. Setting
 * `position` on those would move the stretched link's containing block and silently shrink the
 * clickable region of the primary action on the page.
 *
 * `@media (hover: hover)` so a touch device, where every tap would light it up, gets nothing. */
@media (hover: hover) {
  [data-ask]:hover {
    outline: 1px dashed var(--color-border-strong);
    outline-offset: 2px;
  }
}

.askabout {
  position: fixed;
  z-index: var(--z-modal);
  width: min(340px, calc(100vw - 24px));
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  box-shadow: 0 10px 34px rgba(0, 0, 0, 0.35);
  animation: askabout-in 120ms ease-out;
}

@keyframes askabout-in {
  from { transform: translateY(-4px); opacity: 0; }
  to   { transform: translateY(0); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .askabout { animation: none; }
}

.askabout-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.askabout-head > svg {
  width: 15px;
  height: 15px;
  flex: none;
  color: var(--accent);
}

/* The subject wraps rather than truncating. It is the answer to "about what", and an ellipsis in the
   middle of a pod name is the one place this popover must not be economical. */
.askabout-subject {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text-primary);
  line-height: 1.3;
  min-width: 0;
}

.askabout-dismiss {
  margin-left: auto;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-secondary);
  cursor: pointer;
}
.askabout-dismiss:hover { background: var(--bg-hover); color: var(--text-primary); }
.askabout-dismiss:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.askabout-dismiss svg { width: 13px; height: 13px; }

/* No `line-clamp`, no `text-overflow`. The whole purpose of this popover is that somebody reads the
   question before it is sent; a question cut off by CSS is one they have not read. */
.askabout-question {
  margin: 0;
  font-size: var(--text-sm);
  line-height: 1.45;
  color: var(--text-secondary);
}

.askabout-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
}

.askabout-cancel {
  background: transparent;
  border: 1px solid transparent;
  color: var(--text-secondary);
}
.askabout-cancel:hover:not(:disabled) { background: var(--bg-hover); color: var(--text-primary); }

.askabout-ask {
  background: var(--color-primary-solid);
  border: 1px solid var(--color-primary-solid);
  color: var(--color-on-primary);
}

.askabout-foot {
  margin: 0;
  font-size: var(--text-xs);
  color: var(--color-text-subtle);
}

/* ------------------------------------------------------------------ muted text
 *
 * `.muted-sm` is used across both portals — on empty states, timestamps, the sentence under a
 * figure — and was defined nowhere, so every one of those rendered at full body weight and colour.
 * Nothing was broken, which is why it survived: the text was there, just shouting. */
.muted-sm {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* ------------------------------------------------------------------ risk ranking
 *
 * The overview's "what to look at first" panel. One row per object, with its score, when it is
 * expected, and — once expanded — the itemised signals the score was summed from. */
.risk-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.risk-row {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  background: var(--color-surface);
}

/* A button because it toggles the evidence, but it must read as a row: the default button chrome
 * would make a list of ten of these look like a toolbar. */
.risk-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
  text-align: left;
  color: inherit;
  font: inherit;
}

.risk-head:hover { background: var(--color-hover); }
.risk-head:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* Tabular so a column of scores lines up on the units digit — 9 and 86 in a ragged column read as
 * unrelated numbers. */
.risk-score {
  flex: 0 0 auto;
  min-width: 2.75rem;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--color-sunken);
  color: var(--color-text);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  text-align: center;
}

.risk-score.is-urgent {
  background: var(--critical-soft);
  color: var(--critical-text);
}

.risk-ident {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1 1 auto;
}

/* The identifier is the one thing that must stay readable when the row is narrow, and a pod name is
 * long enough to push everything else off. */
.risk-ident > strong {
  overflow-wrap: anywhere;
}

.risk-when { flex: 0 0 auto; }

.risk-cause {
  margin: var(--space-2) 0 0;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}

.risk-evidence {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border);
}

.risk-evidence ul {
  margin: var(--space-2) 0;
  padding-left: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.risk-evidence li {
  font-size: var(--text-sm);
  overflow-wrap: anywhere;
}

.risk-weights {
  margin-top: var(--space-3);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.risk-weights summary { cursor: pointer; }
.risk-weights ul { margin: var(--space-2) 0; padding-left: var(--space-5); }

/* ------------------------------------------------------------------ scheduling board
 *
 * The five panels of /monitoring/scheduling. Every one of them shows a verdict beside the evidence
 * that produced it, so the shapes here are mostly "a claim, then its working": a rejection table
 * under a dead end, a wave list under a cascade, a reason line under a ranked node.
 *
 * No gradients — blacklist item 6, and these are data surfaces where a gradient would imply a scale
 * that the figures beside it do not have.
 */

.sched-sub {
  display: block;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  margin-top: 2px;
}

.sched-note {
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.sched-list {
  margin: var(--space-2) 0 0;
  padding-left: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.sched-list li { overflow-wrap: anywhere; }

/* The ordered placement recommendation: rank, node, and why it ranked there. */
.sched-rank {
  margin: 0;
  padding-left: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.sched-rank li {
  display: flex;
  gap: var(--space-2);
  align-items: baseline;
}

.sched-rank-n {
  flex: none;
  min-width: 1.4em;
  height: 1.4em;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-full);
  background: var(--color-sunken);
  border: 1px solid var(--color-border);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
  color: var(--color-text-muted);
}

/* ---- dead ends */

.sched-deadends {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.sched-deadend {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  background: var(--color-sunken);
}

.sched-deadend > header {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
  margin-bottom: var(--space-2);
}

.sched-deadend > p { margin: 0 0 var(--space-3); }

/* Every node's own refusal, rolled up by reason. The working, not a summary of it. */
.sched-reasons {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
}

.sched-reasons th,
.sched-reasons td {
  text-align: left;
  padding: var(--space-2) var(--space-3) var(--space-2) 0;
  border-top: 1px solid var(--color-border);
  vertical-align: top;
}

.sched-reasons th { font-weight: 600; white-space: nowrap; }
.sched-reasons td { color: var(--color-text-muted); }

.sched-event {
  margin: var(--space-3) 0 0;
  padding: var(--space-2) var(--space-3);
  border-left: 3px solid var(--color-border-strong);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  overflow-wrap: anywhere;
}

.sched-blind {
  margin-top: var(--space-4);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.sched-blind summary { cursor: pointer; }

/* ---- failure domains */

.sched-domains {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.sched-domain {
  flex: 1 1 8rem;
  min-width: 8rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  background: var(--color-sunken);
}

.sched-domain h4 {
  margin: 0;
  font-size: var(--text-sm);
  overflow-wrap: anywhere;
}

.sched-domain p { margin: 0; }

/* ---- storms */

.sched-storms {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}

.sched-storm {
  border: 1px solid var(--critical-text);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  background: var(--color-sunken);
}

.sched-storm > header {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
  margin-bottom: var(--space-2);
}

.sched-storm > p { margin: 0 0 var(--space-3); }

.sched-waves {
  margin: 0;
  padding-left: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  font-size: var(--text-sm);
}

.sched-waves li {
  display: flex;
  gap: var(--space-3);
  align-items: baseline;
}

.sched-wave-n {
  flex: none;
  color: var(--color-text-subtle);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: var(--text-sm);
  min-width: 4.5rem;
}

/* ---- quiet hours */

.sched-best {
  margin: 0 0 var(--space-1);
  font-size: var(--text-base);
}

.sched-hours {
  margin: var(--space-4) 0 0;
  padding-left: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.sched-hours li {
  display: grid;
  grid-template-columns: 2rem 1fr 2.5rem;
  gap: var(--space-2);
  align-items: center;
  font-size: var(--text-sm);
}

.sched-hours li.is-thin { opacity: 0.45; }

.sched-hour-label { color: var(--color-text-muted); }

.sched-hour-bar {
  height: 0.65rem;
  background: var(--color-sunken);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.sched-hour-fill {
  display: block;
  height: 100%;
  background: var(--color-primary);
  border-radius: var(--radius-sm);
}

.sched-hour-n {
  text-align: right;
  color: var(--color-text-muted);
}

/* ------------------------------------------------------------------ control-plane checklist
 *
 * The fourteen-row per-node table. Its distinguishing feature is that rows which cannot be measured
 * are rendered rather than dropped, so the styling has to make "blank on purpose" visibly different
 * from both a healthy row and a failing one — without making it look broken.
 */

.vit-watch { color: var(--color-text-muted); font-size: var(--text-sm); }

.vit-value { display: block; font-variant-numeric: tabular-nums; }

.vit-detail {
  display: block;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  margin-top: 2px;
  max-width: 56ch;
}

/* The sentence that stands in for a reading. Italic so it never reads as a measurement. */
.vit-requires {
  display: block;
  font-size: var(--text-sm);
  font-style: italic;
  color: var(--color-text-subtle);
  max-width: 62ch;
}

/* A row nothing can answer: present, legible, and visibly not a result. */
tr[data-blank] { background: var(--color-sunken); }
tr[data-blank] strong { color: var(--color-text-muted); font-weight: 500; }

.vit-cond {
  margin: 0 0 var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  overflow-wrap: anywhere;
}

.vit-gaps { margin: var(--space-4) 0 0; display: flex; flex-direction: column; gap: var(--space-4); }
.vit-gaps dt { display: flex; gap: var(--space-2); align-items: center; font-weight: 600; }
.vit-gaps dd {
  margin: var(--space-2) 0 0;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  max-width: 82ch;
}

/* =============================================================================================
 * v4 Type B primitives — stat tile and horizontal bar list
 *
 * Both come straight from the v4 spec's Components section. They exist as classes rather than
 * inline styles because the tile carries a *conditional tinted background* and the bar list carries
 * a series colour per row — two things that have to be readable in one place when someone asks
 * "why is this row purple", and unreadable when they are spread across a component's style objects.
 *
 * The one filled-background exception in the whole product lives here: `.v4-tile--<severity>` mixes
 * its status colour into the panel ground at 12%, which the spec allows for a severity tile in a
 * dense grid and nowhere else. It is `color-mix` against the existing tokens rather than a new
 * rgba literal, so it cannot drift from the status palette it is quoting.
 * ============================================================================================= */

.v4-tile {
  background: var(--bg-surface);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-card);
  padding: var(--space-3) var(--space-4);
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

/* The spec's 11px uppercase label. `--text-muted` deliberately, not secondary: the label is the
   least important thing in the tile and the number is the point. */
.v4-tile-label {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* 24px, per the spec — a Type B tile, explicitly NOT the 32-40px Type A hero. */
.v4-tile-value {
  font-size: 24px;
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.01em;
  color: var(--text-primary);
}

.v4-tile-hint {
  font-size: var(--text-xs);
  color: var(--text-muted);
}

.v4-tile--ok { background: color-mix(in srgb, var(--ok) 12%, var(--bg-surface)); }
.v4-tile--warn { background: color-mix(in srgb, var(--warn) 12%, var(--bg-surface)); }
.v4-tile--critical { background: color-mix(in srgb, var(--critical) 12%, var(--bg-surface)); }
.v4-tile--info { background: color-mix(in srgb, var(--info) 12%, var(--bg-surface)); }

.v4-tile--ok .v4-tile-value { color: var(--ok-text); }
.v4-tile--warn .v4-tile-value { color: var(--warn-text); }
.v4-tile--critical .v4-tile-value { color: var(--critical-text); }

/* ---- horizontal bar list -------------------------------------------------------------------
 * The spec: "Best component in the whole reference set for 'top N by X' — use it instead of a pie
 * chart, always." Three columns so the labels, the bars and the figures each align down their own
 * edge; a flex row with a growing bar puts every number at a different x and the column stops being
 * scannable, which is the entire job.
 */
.v4-bars {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}

.v4-bar-row {
  display: grid;
  grid-template-columns: minmax(0, 7rem) minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}

.v4-bar-label {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.v4-bar-track {
  height: var(--size-bar);
  border-radius: var(--radius-full);
  background: var(--bg-sunken);
  overflow: hidden;
}

.v4-bar-fill {
  height: 100%;
  border-radius: var(--radius-full);
  /* A zero-width fill is invisible, and an invisible bar beside a "0" reads as a broken component
     rather than as a real zero. The minimum is a nub, not a full bar. */
  min-width: 2px;
}

.v4-bar-value {
  font-size: var(--text-sm);
  color: var(--text-primary);
  text-align: right;
  white-space: nowrap;
}

@media (max-width: 767px) {
  /* Dense grids stack; they never scroll sideways. The label column stops reserving 7rem it does
     not have on a 390px viewport. */
  .v4-bar-row {
    grid-template-columns: minmax(0, 5rem) minmax(0, 1fr) auto;
    gap: var(--space-2);
  }
}

/*
 * The Type B tile rail.
 *
 * `auto-fit` with a `min()` lower bound, matching `Grid` — a bare `minmax(150px, 1fr)` cannot go
 * below 150px, so on a narrow content column the track overflows and the page scrolls sideways,
 * which the responsive rules forbid outright. Six tiles sit in one row at 1440px, three at tablet,
 * and stack at 390px without a horizontal scrollbar appearing at any width between.
 *
 * Gap is `--space-3` (12px), not the 24px a Type A page uses: v4 asks explicitly for 8-12px gaps on
 * explorer surfaces, and that tightening is what makes a dense rail read as one instrument instead
 * of six unrelated cards.
 */
.v4-tile-rail {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr));
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

/*
 * The assistant dock's tabs and its shell target picker.
 *
 * The dock carries two halves — Ask and Shell — since the standalone `/sre` console was removed on
 * 2026-08-26. Everything the chat itself needs is in `.sre-*` above; these are only the frame.
 */
/*
 * Housed in a track, so the pair reads as one two-position switch rather than as two buttons that
 * happen to sit together. The switch says the panel has two modes; two loose buttons said nothing,
 * and "Ask" and "Shell" floating side by side were routinely read as a heading and a link.
 *
 * The v4 rule below still holds — the selected position is a raised fill, not a coloured bar. The
 * track only gives that fill something to be raised out of.
 */



/* v4: the selected tab is a raised fill and the primary ink, never a coloured bar or underline. */



/*
 * Three fields across when there is room, stacking when there is not. `auto-fit` with a `min()`
 * lower bound rather than a fixed `minmax`: the dock sheet is narrower than a page, and a track that
 * cannot shrink below its floor makes the sheet itself scroll sideways.
 */




/*
 * A flow hop that opens its API detail.
 *
 * The cursor and the focus ring are the whole affordance: an SVG `<g>` carries no browser default
 * for either, so a hop wired to `onClick` with no styling is a control that looks exactly like the
 * decoration next to it. `:focus-visible` rather than `:focus` — a pointer user clicking a hop
 * should not be left with a ring on it.
 */
.appflow-hop.is-openable {
  cursor: pointer;
}

.appflow-hop.is-openable:hover .appflow-hop-box,
.appflow-hop.is-openable:focus-visible .appflow-hop-box {
  stroke: var(--border-strong);
  stroke-width: 2;
}

.appflow-hop.is-openable:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

/*
 * The AI-operations panel on a customer's Fine-tuning tab.
 *
 * Scoped names rather than a generic `.row-actions` / `.rule`: this file already carries
 * `.feature-actions` and `.feature-rule` for the model list, and a second pair of generic names
 * would be two conventions for one job, with the next person picking whichever they saw first.
 */
.aiops-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-3);
}
/* Whatever follows a row of actions stands clear of it. The row carries only a top margin, so the
   explanation under "Pause passes" sat flush against the buttons' bottom edge. A sibling rule rather
   than a bottom margin, so a row that ends its card adds nothing to the card's own padding. */
.aiops-actions + * {
  margin-top: var(--space-3);
}

/* A real separator between the three decisions on this card — free the card, turn AI off, turn
   retrieval on. They are related enough to share a card and distinct enough to need a line. */
.aiops-rule {
  height: 1px;
  margin: var(--space-4) 0;
  border: 0;
  background: var(--border-hairline);
}

/*
 * A heading between cards on the customer AI tab.
 *
 * The tab is one workflow in four parts — what is running, which models serve it, what it may read,
 * and training a new one — and cards alone do not say where one part ends. Sized as a section label
 * rather than a page title: the page already has one, and a second at the same weight would compete
 * with it (blacklist 1, one hero per screen).
 */
.aiops-section {
  margin: var(--space-6) 0 var(--space-2);
  font-size: var(--text-xl);
  font-weight: 600;
  color: var(--text-primary);
}

/* The first section heading follows a card, so it needs no extra lead-in above it. */
.aiops-section:first-child {
  margin-top: 0;
}

/* ------------------------------------------------------------------ the change log
 *
 * A table whose rows are sentences, which is a layout problem the generic table cannot solve on its
 * own. `resource_changes.summary` is written to be read in full — "Service test-app/nginx-headless
 * was deleted. Its cluster DNS name no longer resolves, so anything addressing it now fails to
 * connect even though the pods it selected are untouched and may still look healthy." — and a
 * hundred of those stacked is a wall nobody scans. Each row also carries a timestamp, an object
 * name, a facet and a writer, all short.
 *
 * So the sentence is clamped and everything else is given a fixed column. Without the widths the
 * browser hands the paragraph column whatever it asks for, which is all of it, and the four short
 * columns collapse to ragged slivers that no two rows agree on — the misalignment this rule set
 * exists to remove. Nothing is lost to the clamp: the full text is the first line of the detail
 * panel, and the table's own caption says a row opens.
 */
.changelog td {
  /* Top, not middle. A one-line row beside a three-line row centres its content into the middle of
   * a tall band and the eye loses the horizontal it was tracking along. */
  vertical-align: top;
}

/*
 * Above the stacking breakpoint only.
 *
 * `DataTable` renders `.tbl-stack`, which below 768px turns every row into a block and every cell
 * into a labelled line. A fixed `width` on a cell that is `display: block` is not a column any more,
 * it is a 7.5rem-wide box in a vertical list — so these widths have to stop exactly where the
 * columns do.
 */
@media (min-width: 768px) {
  .changelog th:nth-child(1),
  .changelog td:nth-child(1) {
    width: 7.5rem;
  }
  .changelog th:nth-child(2),
  .changelog td:nth-child(2) {
    width: 13rem;
  }
  .changelog th:nth-child(4),
  .changelog td:nth-child(4) {
    width: 8rem;
  }
}

/*
 * A row is two lines of content, not one value, so it cannot use the fixed row height.
 *
 * `.tbl tbody td` sets `height: var(--size-row)` — right for a table of single values, and here it
 * is a floor that some rows clear and others do not, which is what made the run of rows read as
 * ragged rather than as a list. An explicit vertical padding with `height: auto` gives every row the
 * same breathing space above and below whatever it contains, so a one-line row and a three-line row
 * are different heights on purpose instead of by accident.
 */
.changelog .tbl tbody td {
  height: auto;
  padding-top: var(--space-3);
  padding-bottom: var(--space-3);
}

/* The facet and the area it belongs to, on one line. They are one fact and were a column apart. */
.change-what {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-bottom: var(--space-1);
}

/*
 * A measure, not a container.
 *
 * This column inherits every pixel the fixed columns do not take — around a thousand of them on a
 * wide screen — and a line of prose that long loses the reader on the return sweep: the eye cannot
 * reliably find the start of the next line. Roughly 80 characters is the width typography has used
 * for continuous text for about five centuries, and the reason has not changed.
 */
.change-summary {
  max-width: 80ch;
}

/* Two lines, then an ellipsis. `-webkit-line-clamp` is unprefixed nowhere yet and is supported in
 * every browser this ships to; the fallback if it ever is not is an unclamped paragraph, which is
 * the state before this rule and not a broken one. */
.clamp-2 {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ------------------------------------------------------------------ before and after
 *
 * Two values that have to be compared, so they have to line up. Rendered as pills of their own
 * intrinsic width they sat at different heights with different left edges, and a truncated one read
 * as a different kind of thing from a short one rather than as the same field with more in it.
 *
 * A fixed two-column grid, aligned to the top, each value in a box of the same shape: same
 * background, same padding, wrapping rather than truncating. `min-width: 0` on the cells because a
 * grid child's default `min-width: auto` refuses to shrink below its longest unbroken token, which
 * is how a long label pushes its neighbour off the panel.
 */
.change-values {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-4);
  align-items: start;
  margin: var(--space-4) 0;
}
.change-values > div {
  min-width: 0;
}
.change-value-label {
  font-size: var(--text-xs);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--space-2);
}
.change-value-box {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: var(--leading-body);
  background: var(--bg-sunken);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
  /* Wrap anywhere. These are label sets and image references — one unbroken 90-character string is
   * ordinary here, and letting it decide the panel's width is how the grid above stops holding. */
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}
.change-value-box.is-absent {
  font-family: inherit;
  color: var(--text-muted);
  background: transparent;
  border-style: dashed;
}

@media (max-width: 767px) {
  .change-values {
    grid-template-columns: 1fr;
  }
}

/* ============================================================ CODE & DEPLOY workspace */

/*
 * A three-pane workspace inside a page that is otherwise a scrolling document.
 *
 * `min-height: 0` on every descendant that scrolls is the load-bearing line. Without it a grid child
 * refuses to shrink below its content, so Monaco reports the height of the whole file, the page grows
 * to match, the editor never scrolls and the tab strip leaves the screen.
 *
 * `100dvh` rather than `100vh`: on a phone `vh` includes the browser chrome that is about to slide
 * away, which puts the status bar underneath it.
 */
.cdw {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  height: calc(100dvh - var(--space-8));
  min-height: 34rem;
  min-width: 0;
  border-radius: var(--radius-card);
  overflow: hidden;
  border: 1px solid var(--border-hairline);
  /* The same translucent ground `.card` uses, so this reads as a surface of this product and not as
     an embedded application. `color-mix` against the token rather than a new alpha literal. */
  background: color-mix(in srgb, var(--bg-surface) 94%, transparent);
  backdrop-filter: blur(16px) saturate(140%);
  -webkit-backdrop-filter: blur(16px) saturate(140%);
}

.cdw-panes {
  display: grid;
  /*
   * Two columns, not three.
   *
   * The third was a permanent right-hand column holding the commit pane and the deployment pane —
   * 356px of a screen somebody is reading code on, for two things nobody looks at while typing. Both
   * moved behind buttons in `.cdw-topbar`, and the facts that were worth keeping in sight went into
   * that bar as text. The editor got the width back.
   */
  grid-template-columns: 240px minmax(0, 1fr);
  min-height: 0;
  min-width: 0;
}

.cdw-left,
.cdw-right {
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
  background: var(--bg-page);
}
.cdw-left { border-right: 1px solid var(--border-hairline); }
.cdw-right {
  border-left: 1px solid var(--border-hairline);
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  align-content: start;
}
.cdw-main { min-width: 0; min-height: 0; display: grid; background: var(--bg-surface); }

/*
 * Thin, quiet scrollbars — three panes with three default bars is three grey slabs.
 *
 * `::-webkit-scrollbar` only. This rule used to carry `scrollbar-width` and `scrollbar-color` as
 * well, which is the exact mistake the block at the top of this file was written about: specifying
 * either standard property moves the element off Blink's `::-webkit-scrollbar` path and onto its
 * standard scrollbar with platform stepper arrows, and every pseudo-element rule below it then does
 * nothing. The standard properties are already applied globally by `* {}` inside
 * `@supports not selector(::-webkit-scrollbar)`, so there was nothing to gain and a scrollbar to
 * lose. `scroll.test.ts` catches it, and did.
 */
.cdw-left::-webkit-scrollbar, .cdw-right::-webkit-scrollbar { width: 8px; height: 8px; }
.cdw-left::-webkit-scrollbar-thumb, .cdw-right::-webkit-scrollbar-thumb {
  background: var(--border-hairline);
  border-radius: 4px;
}
.cdw-left:hover::-webkit-scrollbar-thumb, .cdw-right:hover::-webkit-scrollbar-thumb { background: var(--border-strong); }

/* ------------------------------------------------------------------ left: the file tree */

.cdw-tree { display: grid; gap: var(--space-4); padding: var(--space-3) 0; align-content: start; }
.cdw-tree-repo { display: grid; gap: var(--space-1); padding: 0 var(--space-3); }
.cdw-tree-section { display: grid; gap: 1px; align-content: start; }
.cdw-group { display: grid; gap: 1px; }

.cdw-sec-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0 var(--space-3) var(--space-1);
}

/*
 * The section's disclosure: chevron and label together, as one target.
 *
 * The label alone is a poor hit area at 11px, and an editor's explorer folds a section by clicking
 * its title rather than by finding the arrow. The actions beside it stay separate controls, so
 * collapsing and creating cannot be confused for one another.
 */
.cdw-sec-toggle {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  border: 0;
  background: none;
  padding: 2px 0;
  margin-right: auto;
  display: flex;
  align-items: center;
  gap: var(--space-1);
  cursor: pointer;
  min-width: 0;
  border-radius: var(--radius-sm);
}
.cdw-sec-toggle .cdw-label { margin-right: 0; }
.cdw-sec-toggle:hover .cdw-label,
.cdw-sec-toggle:hover .cdw-caret { color: var(--text-primary); }
.cdw-sec-toggle:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }


/* The product's section label: 0.08em tracking, 600, muted. Same as `.panel-eyebrow` everywhere. */
.cdw-label {
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-right: auto;
}

/* A count that sits beside a label without becoming a badge — a pill per section is noise. */
.cdw-count {
  font-size: 10px;
  color: var(--text-muted);
  background: var(--bg-sunken);
  border: 1px solid var(--border-hairline);
  border-radius: 999px;
  padding: 0 6px;
  line-height: 1.5;
  flex: none;
}
.cdw-count.is-dirty { color: var(--warn-text); border-color: color-mix(in srgb, var(--warn) 40%, transparent); }

.cdw-select {
  width: 100%;
  min-width: 0;
  padding: 5px var(--space-2);
  font: inherit;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-primary);
  background: var(--bg-sunken);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-sm);
  transition: border-color var(--motion-fast) var(--ease-out);
}
.cdw-select:hover { border-color: var(--border-strong); }
.cdw-select:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }

/*
 * 11px mono rows. Selection is a ground change plus a left rail, hover is a ground change alone —
 * never colour on the name, because this pane sits beside a diff where colour already means added
 * and removed.
 */
.cdw-file,
.cdw-folder {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  border: 0;
  background: none;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  padding: 4px var(--space-3);
  font: inherit;
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 1.6;
  color: var(--text-secondary);
  cursor: pointer;
  text-align: left;
  min-width: 0;
  border-left: 2px solid transparent;
  transition: background var(--motion-fast) var(--ease-out), color var(--motion-fast) var(--ease-out);
}
.cdw-file:hover,
.cdw-folder:hover { background: var(--bg-hover); color: var(--text-primary); }
.cdw-file:focus-visible,
.cdw-folder:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }
.cdw-file.is-open {
  background: var(--bg-sunken);
  color: var(--text-primary);
  border-left-color: var(--text-primary);
}
.cdw-file.is-nested { padding-left: calc(var(--space-3) + 1.15rem); }
.cdw-folder { color: var(--text-secondary); }

.cdw-file-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cdw-file-dir {
  margin-left: auto;
  font-size: 10px;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: none;
  max-width: 6rem;
}

/* Icons carry the file's kind. Muted by default so the names stay the figure and these the ground. */
.cdw-ic { flex: none; color: var(--text-muted); }
.cdw-ic-helm { color: var(--info-text); }
.cdw-ic-tpl { color: var(--text-secondary); }
.cdw-ic-yaml { color: var(--text-secondary); }
.cdw-ic-md { color: var(--text-muted); }
.cdw-ic-folder { color: var(--text-secondary); }
.cdw-file.is-open .cdw-ic { color: var(--text-primary); }

.cdw-caret {
  flex: none;
  display: inline-flex;
  color: var(--text-muted);
  transition: transform var(--motion-fast) var(--ease-out);
}
.cdw-caret.is-open { transform: rotate(90deg); }

/* M / A / D, in the three channels the staged list and the sync preview also use. */
.cdw-mark {
  flex: none;
  width: 0.85rem;
  font-weight: 700;
  font-size: 10px;
  text-align: center;
  font-family: var(--font-mono);
}
.is-modified > .cdw-mark { color: var(--warn-text); }
.is-added    > .cdw-mark { color: var(--ok-text); }
.is-deleted  > .cdw-mark { color: var(--critical-text); }

/* ------------------------------------------------------- centre: tabs, editor, diff drawer */

.cdw-center {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  min-height: 0;
  min-width: 0;
}

.cdw-tabs {
  display: flex;
  align-items: stretch;
  min-width: 0;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  background: var(--bg-page);
  border-bottom: 1px solid var(--border-hairline);
}
.cdw-tabs-empty {
  padding: var(--space-2) var(--space-4);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

/*
 * The active tab is the one connected to the editor below it: it takes the editor's ground and its
 * bottom hairline is removed, so the two read as one surface. A colour swap alone leaves the tab
 * looking like a selected item in a list rather than like the thing you are looking at.
 */
.cdw-tab {
  display: flex;
  align-items: center;
  border-right: 1px solid var(--border-hairline);
  border-top: 2px solid transparent;
  margin-bottom: -1px;
  flex: none;
  transition: background var(--motion-fast) var(--ease-out);
}
.cdw-tab:hover { background: var(--bg-hover); }
.cdw-tab.is-active {
  background: var(--bg-surface);
  border-top-color: var(--text-primary);
  border-bottom: 1px solid var(--bg-surface);
}

.cdw-tab-name {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  border: 0;
  background: none;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 7px var(--space-1) 7px var(--space-3);
  font: inherit;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-muted);
  cursor: pointer;
  white-space: nowrap;
}
.cdw-tab.is-active .cdw-tab-name { color: var(--text-primary); }
.cdw-tab-name:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }

/* A shape, not a recoloured name — a renamed colour is not a state. */
.cdw-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--warn-text); flex: none; }
.cdw-dot.is-new { background: var(--ok-text); }

/*
 * One slot on the tab, two meanings, as an editor's tab strip does it: the unsaved dot until the
 * pointer or the keyboard arrives, then the ×. Both children are laid on top of each other in a
 * fixed square, so the tab does not resize as they swap and the strip does not shuffle under the
 * cursor — which is the whole reason this is a grid rather than two rules toggling `display`.
 */
.cdw-tab-x {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  border: 0;
  background: none;
  padding: 0;
  margin: 0 var(--space-2) 0 2px;
  width: 16px;
  height: 16px;
  display: grid;
  place-items: center;
  /* 4px: chrome nested inside a padded control, per the design spec's radius rule. */
  border-radius: 4px;
  cursor: pointer;
  flex: none;
  transition: color var(--motion-fast) var(--ease-out), background var(--motion-fast) var(--ease-out);
}
.cdw-tab-x > * { grid-area: 1 / 1; }

.cdw-tab-cross {
  font-size: 15px;
  line-height: 1;
  color: transparent;
  transition: color var(--motion-fast) var(--ease-out);
}
/* Revealed on hover or focus. A close button on every tab makes the strip read as a row of buttons. */
.cdw-tab:hover .cdw-tab-cross,
.cdw-tab.is-active .cdw-tab-cross { color: var(--text-muted); }
.cdw-tab-x:hover .cdw-tab-cross { color: var(--critical-text); }
.cdw-tab-x:hover { background: var(--bg-hover); }
.cdw-tab-x:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
.cdw-tab-x:focus-visible .cdw-tab-cross { color: var(--text-primary); }

/*
 * The dot is only shown while the buffer holds something uncommitted, and it gets out of the way
 * the moment the × becomes reachable — otherwise the two would sit on top of one another.
 */
.cdw-tab-x .cdw-dot { display: none; }
.cdw-tab-x.is-unsaved .cdw-dot { display: block; }
.cdw-tab:hover .cdw-tab-x.is-unsaved .cdw-dot,
.cdw-tab-x.is-unsaved:focus-visible .cdw-dot { display: none; }
/* The × has to be there to replace it, on a tab that is neither active nor hovered. */
.cdw-tab:hover .cdw-tab-x.is-unsaved .cdw-tab-cross { color: var(--text-muted); }

.cdw-editor { min-height: 0; min-width: 0; background: var(--bg-surface); }
.cdw-editor-empty {
  margin: 0;
  padding: var(--space-8) var(--space-6);
  font-size: var(--text-sm);
  line-height: var(--leading-relaxed);
  color: var(--text-muted);
  max-width: 54ch;
}

.cdw-drawer {
  border-top: 1px solid var(--border-hairline);
  display: grid;
  grid-template-rows: auto;
  min-height: 0;
  background: var(--bg-page);
}
/* sash, head, diff. The height is dragged; 17rem is only where it starts. */
.cdw-drawer.is-open { grid-template-rows: 7px auto var(--cdw-drawer-h, 17rem); }

/*
 * The sash: a thin strip on the drawer's top edge, with the grip an editor puts there.
 *
 * `touch-action: none` is what makes the drag work on a touchpad-driven browser at all — without
 * it the browser claims the gesture as a scroll partway through and the pointer events simply stop
 * arriving, which reads as a handle that works for half a drag.
 */
.cdw-sash {
  display: grid;
  place-items: center;
  cursor: row-resize;
  touch-action: none;
  background: none;
  transition: background var(--motion-fast) var(--ease-out);
}
.cdw-sash:hover,
.cdw-sash:focus-visible { background: var(--bg-hover); }
.cdw-sash:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }
.cdw-sash-grip {
  width: 26px;
  height: 3px;
  border-radius: 2px;
  color: var(--text-muted);
  /* Three dots, as a grip is drawn everywhere: read as "hold me" without a word. */
  background-image: radial-gradient(circle at center, currentColor 1px, transparent 1.2px);
  background-size: 5px 3px;
  background-repeat: repeat-x;
  opacity: 0.55;
  transition: opacity var(--motion-fast) var(--ease-out);
}
.cdw-sash:hover .cdw-sash-grip,
.cdw-sash:focus-visible .cdw-sash-grip { opacity: 1; }

.cdw-drawer-head {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  border: 0;
  background: none;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-2) var(--space-4);
  font: inherit;
  cursor: pointer;
  text-align: left;
  transition: background var(--motion-fast) var(--ease-out);
}
.cdw-drawer-head:hover { background: var(--bg-hover); }
.cdw-drawer-head:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }
.cdw-drawer-head .cdw-label { margin-right: 0; }
.cdw-drawer-note {
  margin-left: auto;
  font-size: var(--text-xs);
  font-family: var(--font-mono);
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cdw-diff { min-height: 0; min-width: 0; border-top: 1px solid var(--border-hairline); }

/* ------------------------------------------------------------------- right: the deploy rail */

.cdw-pane {
  display: grid;
  gap: var(--space-3);
  align-content: start;
  padding: var(--space-4);
  border-bottom: 1px solid var(--border-hairline);
  min-width: 0;
}

.cdw-pane-head { display: flex; align-items: baseline; gap: var(--space-2); }
.cdw-pane-title {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--text-primary);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin-right: auto;
}
.cdw-branch {
  font-size: 10px;
  color: var(--text-muted);
  background: var(--bg-sunken);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-sm);
  padding: 1px 6px;
}

.cdw-pane-empty,
.cdw-pane-note {
  margin: 0;
  font-size: var(--text-xs);
  line-height: var(--leading-relaxed);
  color: var(--text-muted);
}
.cdw-pane-error { margin: 0; font-size: var(--text-xs); color: var(--critical-text); }

.cdw-staged { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; min-width: 0; }
.cdw-staged-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 11px;
  min-width: 0;
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
}
.cdw-staged-path {
  color: var(--text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.cdw-message { min-height: 4.5rem; font-size: 11px; line-height: 1.55; resize: vertical; }

.cdw-deps { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }

/*
 * One deployment. The left rail carries the state so the column is scannable without reading, and
 * it is a rail rather than a tint because a tinted card at four rows deep is a wall of colour.
 */
.cdw-dep {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-3);
  background: var(--bg-sunken);
  border: 1px solid var(--border-hairline);
  border-left: 2px solid var(--border-strong);
  border-radius: var(--radius-sm);
  min-width: 0;
  transition: border-color var(--motion-fast) var(--ease-out);
}
.cdw-dep.s-out-of-sync { border-left-color: var(--warn); }
.cdw-dep.h-degraded { border-left-color: var(--critical); }
.cdw-dep:hover { border-color: var(--border-strong); }

.cdw-dep-head { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
.cdw-dep-name {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text-primary);
  margin-right: auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cdw-health-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex: none;
  background: var(--text-muted);
}
.cdw-health-dot.is-healthy { background: var(--ok); }
.cdw-health-dot.is-degraded { background: var(--critical); }
.cdw-health-dot.is-missing { background: var(--text-muted); }
/* Hollow, not grey: the cluster's agent is silent, so no measurement was taken. A filled dot of any
   colour would read as one. */
.cdw-health-dot.is-unknown { background: transparent; box-shadow: inset 0 0 0 1.5px var(--text-muted); }
.cdw-health-dot.is-progressing {
  background: var(--info);
  /* The only thing in this module that moves, because it is the only state going to change on its
     own. A still amber dot and a still red one look equally settled, which is wrong mid-sync. */
  animation: cdw-pulse 1.4s var(--ease-out) infinite;
}
@keyframes cdw-pulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--info) 60%, transparent); }
  70% { box-shadow: 0 0 0 5px transparent; }
}
@media (prefers-reduced-motion: reduce) {
  .cdw-health-dot.is-progressing { animation: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--info) 35%, transparent); }
}

.cdw-dep-row { display: flex; align-items: baseline; gap: var(--space-2); flex-wrap: wrap; }
.cdw-dep-health { font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; font-weight: 600; color: var(--text-muted); }
.cdw-dep-health.h-healthy { color: var(--ok-text); }
.cdw-dep-health.h-degraded { color: var(--critical-text); }
.cdw-dep-health.h-progressing { color: var(--info-text); }
.cdw-dep-ns { font-size: 10px; color: var(--text-muted); margin-left: auto; }

/*
 * The revision track. Equal ends read as one settled thing; unequal ends put the gap between them
 * on screen, which is the whole of "OutOfSync". The connector is the only part that changes.
 */
.cdw-track { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
.cdw-track-end {
  font-size: 10px;
  color: var(--text-muted);
  background: var(--bg-surface);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-sm);
  padding: 1px 5px;
  flex: none;
}
.cdw-track-end.is-target { color: var(--text-secondary); }
.cdw-track-line { flex: 1 1 auto; height: 1px; background: var(--border-hairline); min-width: 0.75rem; }
.cdw-track.s-out-of-sync .cdw-track-line { background: var(--warn); }
.cdw-track.s-out-of-sync .cdw-track-end.is-target { color: var(--warn-text); border-color: color-mix(in srgb, var(--warn) 45%, transparent); }
.cdw-track.s-synced .cdw-track-end { color: var(--text-secondary); }

.cdw-dep-when { font-size: 10px; color: var(--text-muted); }
.cdw-dep-when.is-await { color: var(--warn-text); }
.cdw-dep-when.is-await a { color: inherit; text-decoration: underline; }
.cdw-dep-when.is-bad { color: var(--critical-text); overflow-wrap: anywhere; }
.cdw-dep-detail {
  margin: 0;
  font-size: 11px;
  line-height: 1.5;
  color: var(--critical-text);
  overflow-wrap: anywhere;
  padding: var(--space-2);
  background: color-mix(in srgb, var(--critical) 8%, transparent);
  border-radius: var(--radius-sm);
}

/* --------------------------------------------------------------------------- the status bar */

.cdw-status {
  display: flex;
  align-items: stretch;
  border-top: 1px solid var(--border-hairline);
  background: var(--bg-page);
  font-size: 11px;
  color: var(--text-muted);
  overflow-x: auto;
  overscroll-behavior-x: contain;
}
.cdw-status-seg {
  display: flex;
  align-items: center;
  padding: 5px var(--space-3);
  border-right: 1px solid var(--border-hairline);
  white-space: nowrap;
}
.cdw-status-seg:last-child { border-right: none; margin-left: auto; }
.cdw-status-seg.is-dirty { color: var(--warn-text); }
.cdw-status-seg.is-warn { color: var(--warn-text); }
/* The shortcut, offered where the count it acts on already is. Muted: it is a reminder, not a state. */
.cdw-status-hint { color: var(--text-muted); }
/* A degraded deployment is never a quiet number. */
.cdw-status-seg.is-bad {
  color: var(--critical-text);
  font-weight: 600;
  background: color-mix(in srgb, var(--critical) 10%, transparent);
}

/* ---------------------------------------------------------------------------- the sync gate */

.cdw-sync { display: grid; gap: var(--space-4); min-width: 0; }
.cdw-sync-facts {
  display: grid;
  gap: var(--space-2);
  margin: 0;
  padding: var(--space-3);
  background: var(--bg-sunken);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
}
.cdw-sync-facts > div { display: flex; align-items: baseline; gap: var(--space-3); }
.cdw-sync-facts dt {
  font-size: var(--text-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  min-width: 5.5rem;
  flex: none;
}
.cdw-sync-facts dd { margin: 0; font-size: var(--text-sm); color: var(--text-primary); }

.cdw-sync-options { display: grid; gap: var(--space-3); }
.cdw-option { display: flex; align-items: flex-start; gap: var(--space-3); }
.cdw-option-text { display: grid; gap: 2px; min-width: 0; }
.cdw-option-name { font-size: var(--text-sm); color: var(--text-primary); font-weight: 500; }
.cdw-option-hint { font-size: var(--text-xs); line-height: var(--leading-relaxed); color: var(--text-muted); }

/* ------------------------------------------------------------------ the tree is a tree */

/*
 * Depth is a variable on the row, set by the pane from the folder the row is in. One rule for any
 * depth, instead of a class per level that stops at whatever level somebody wrote rules for.
 */
.cdw-file,
.cdw-folder {
  --cdw-depth: 0;
  padding-left: calc(var(--space-3) + 1.15rem * var(--cdw-depth));
}
/* The repository row: the root, and the one row that does not fold. */
.cdw-root { cursor: default; color: var(--text-primary); font-weight: 600; }
.cdw-root:hover { background: none; color: var(--text-primary); }

.cdw-tree-name { display: grid; gap: 2px; min-width: 0; padding-top: var(--space-1); }
.cdw-tree-name .cdw-file-name { font-size: 12px; font-weight: 600; color: var(--text-primary); }
.cdw-tree-branch { font-size: 10px; color: var(--text-muted); }
.cdw-back {
  width: -moz-fit-content;
  width: fit-content;
  font-size: var(--text-xs);
  color: var(--text-secondary);
  text-decoration: none;
}
.cdw-back:hover { color: var(--text-primary); text-decoration: none; }
.cdw-back:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; border-radius: var(--radius-sm); }

/* ------------------------------------------------------------------ a new file, from the tree */

.cdw-newfile-btn {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  border: 1px solid var(--border-hairline);
  background: var(--bg-sunken);
  color: var(--text-secondary);
  border-radius: var(--radius-sm);
  width: 1.25rem;
  height: 1.25rem;
  line-height: 1;
  font-size: 12px;
  cursor: pointer;
  flex: none;
}
.cdw-newfile-btn:hover { color: var(--text-primary); border-color: var(--border-strong); }
.cdw-newfile-btn:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
.cdw-newfile { padding: 0 var(--space-3) var(--space-1); }
.cdw-newfile-input {
  width: 100%;
  min-width: 0;
  padding: 4px var(--space-2);
  font: inherit;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-primary);
  background: var(--bg-sunken);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-sm);
}
.cdw-newfile-input:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }

/*
 * The form is a row of the tree, at the depth the thing will live at.
 *
 * That indent is what states the folder, the way an editor's explorer does it. The first cut put
 * the folder ahead of the field as text and it ate half a 300px pane before a character was typed.
 */
.cdw-newfile {
  --cdw-depth: 0;
  padding: 1px var(--space-3) 1px calc(var(--space-3) + 1.15rem * var(--cdw-depth));
}
.cdw-newfile-row { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
.cdw-newfile-ic { flex: none; display: inline-flex; color: var(--text-muted); }

/* --------------------------------------------------------------- the tree's own toolbar */

/*
 * New file, new folder, refresh, collapse — an editor's explorer, in its order, because that order
 * is the one anybody arriving here has already learned. Icon-only, so each carries an aria-label.
 */
.cdw-treeacts { display: flex; align-items: center; gap: 1px; }
.cdw-treeact {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  border: 0;
  background: none;
  color: var(--text-muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.35rem;
  height: 1.35rem;
  border-radius: var(--radius-sm);
  cursor: pointer;
  flex: none;
  transition: color var(--motion-fast) var(--ease-out), background var(--motion-fast) var(--ease-out);
}
.cdw-treeact:hover { color: var(--text-primary); background: var(--bg-hover); }
.cdw-treeact:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
.cdw-treeact:disabled { cursor: not-allowed; opacity: 0.4; }
.cdw-treeact:disabled:hover { background: none; color: var(--text-muted); }

/* A folder that holds nothing yet says so in a word: 0 reads as a folder somebody emptied. */
.cdw-count.is-empty {
  font-family: var(--font-sans);
  font-size: 10px;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  border-color: transparent;
  background: none;
}

/* ------------------------------------------------- a folder row: the fold, and its own new file */

/*
 * A folder needs two controls — fold it, and create a file inside it — and a button cannot contain
 * a button. So the row is the flex wrapper around both, and it is also the drop target: accepting
 * on the row rather than on the fold button means the whole width of the row accepts, which is what
 * a drag needs from a 11px target.
 */
.cdw-row { display: flex; align-items: center; min-width: 0; border-radius: var(--radius-sm); }
.cdw-row > .cdw-folder { flex: 1 1 auto; min-width: 0; width: auto; }

.cdw-rowadd {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  border: 0;
  background: none;
  color: var(--text-muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  width: 1.25rem;
  height: 1.25rem;
  border-radius: var(--radius-sm);
  cursor: pointer;
  flex: none;
  opacity: 0;
  transition: opacity var(--motion-fast) var(--ease-out), color var(--motion-fast) var(--ease-out);
}
/* Shown on hover, and on focus — otherwise it is reachable by mouse and by nothing else. */
.cdw-row:hover .cdw-rowadd,
.cdw-rowadd:focus-visible { opacity: 1; }
.cdw-row > .cdw-rowadd:last-child { margin-right: var(--space-2); }
.cdw-rowadd:hover { color: var(--text-primary); background: var(--bg-sunken); }
.cdw-rowadd:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
.cdw-rowadd:disabled { cursor: not-allowed; }

/*
 * Delete, on a file row and a folder row. Same reveal as the create actions, and red only on hover:
 * a row of standing red marks would read as a list of problems rather than a list of files.
 */
.cdw-rowdel {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  border: 0;
  background: none;
  color: var(--text-muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  width: 1.25rem;
  height: 1.25rem;
  margin-right: var(--space-2);
  border-radius: var(--radius-sm);
  cursor: pointer;
  flex: none;
  opacity: 0;
  transition: opacity var(--motion-fast) var(--ease-out), color var(--motion-fast) var(--ease-out);
}
.cdw-row:hover .cdw-rowdel,
.cdw-rowdel:focus-visible { opacity: 1; }
.cdw-rowdel:hover { color: var(--critical-text); background: var(--bg-sunken); }
.cdw-rowdel:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
.cdw-rowdel:disabled { cursor: not-allowed; }
/* Restore is not a destructive act, so it never turns red. */
.cdw-rowdel.is-restore:hover { color: var(--ok-text); }

/* The one question this pane asks: a folder's files are not all on screen, so the count is said. */
.cdw-confirm {
  --cdw-depth: 0;
  margin: 2px var(--space-3) var(--space-2) calc(var(--space-3) + 1.15rem * var(--cdw-depth));
  padding: var(--space-2);
  display: grid;
  gap: var(--space-2);
  background: var(--bg-sunken);
  border: 1px solid color-mix(in srgb, var(--critical) 40%, transparent);
  border-radius: var(--radius-sm);
}
.cdw-confirm-text { margin: 0; font-size: 11px; line-height: 1.5; color: var(--text-secondary); overflow-wrap: anywhere; }
.cdw-confirm-acts { display: flex; gap: var(--space-2); }
.cdw-confirm-yes,
.cdw-confirm-no {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  font: inherit;
  font-size: 11px;
  padding: 3px var(--space-2);
  border-radius: var(--radius-sm);
  cursor: pointer;
  border: 1px solid var(--border-hairline);
  background: var(--bg-surface);
  color: var(--text-secondary);
}
.cdw-confirm-yes { border-color: color-mix(in srgb, var(--critical) 50%, transparent); color: var(--critical-text); }
.cdw-confirm-yes:hover { background: color-mix(in srgb, var(--critical) 12%, transparent); }
.cdw-confirm-no:hover { color: var(--text-primary); border-color: var(--border-strong); }
.cdw-confirm-yes:focus-visible,
.cdw-confirm-no:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }

/* --------------------------------------------------------------------- dragging into a folder */

/*
 * The row a drop would land on: a ground change and an inset ring, not a hue. The accent is
 * achromatic in dark by design, and the three status colours in this pane already mean added,
 * modified and removed — a fourth meaning on that channel would be read as one of those three.
 */
.cdw-row.is-drop,
.cdw-root.is-drop {
  background: var(--bg-hover);
  box-shadow: inset 0 0 0 1px var(--border-strong);
}
.cdw-row.is-drop > .cdw-folder { color: var(--text-primary); }

.cdw-tree-hint {
  margin: 0;
  padding: 0 var(--space-3) var(--space-1);
  font-size: 10px;
  line-height: 1.5;
  color: var(--text-muted);
}

/* Why a drop did nothing. It stays until dismissed: a notice that clears itself is one nobody read. */
.cdw-drop-problem {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin: 0 var(--space-3) var(--space-1);
  padding: var(--space-2);
  background: var(--bg-sunken);
  border: 1px solid color-mix(in srgb, var(--critical) 40%, transparent);
  border-radius: var(--radius-sm);
}
.cdw-drop-problem > .cdw-create-error { min-width: 0; overflow-wrap: anywhere; }
.cdw-drop-x {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  border: 0;
  background: none;
  color: var(--text-muted);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  flex: none;
  margin-left: auto;
}
.cdw-drop-x:hover { color: var(--text-primary); }
.cdw-drop-x:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }

/* ------------------------------------------------------------------ the browser at /deploy/<repo> */

.cdw-browse { display: grid; gap: var(--space-4); min-width: 0; }
.cdw-browse-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
}
.cdw-browse-title { display: grid; gap: var(--space-1); min-width: 0; }
.cdw-browse-title h1 { margin: 0; font-size: var(--text-xl); font-weight: 600; color: var(--text-primary); }
.cdw-browse-meta, .cdw-browse-deps { margin: 0; display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.cdw-browse-commit { color: var(--text-secondary); }
.cdw-browse-actions { display: flex; align-items: center; gap: var(--space-2); flex: none; }

.cdw-crumbs {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 2px;
  font-size: 12px;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
}
.cdw-crumb-seg { display: inline-flex; align-items: center; gap: 2px; }
.cdw-crumb-sep { color: var(--text-muted); padding: 0 2px; }
.cdw-crumb {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  border: 0;
  background: none;
  padding: 2px var(--space-1);
  font: inherit;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-secondary);
  cursor: pointer;
  border-radius: var(--radius-sm);
}
.cdw-crumb:hover { color: var(--text-primary); background: var(--bg-hover); }
.cdw-crumb[aria-current="page"] { color: var(--text-primary); font-weight: 600; cursor: default; }
.cdw-crumb:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }

.cdw-addfile {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--bg-surface) 94%, transparent);
}
.cdw-addfile .cdw-newfile-input { width: 18rem; }

/* The platform checkboxes on the licence form: one row per platform, its name and what it is. */
.lic-platforms {
  display: grid;
  gap: var(--space-3);
  max-height: 32rem;
  overflow-y: auto;
  padding: var(--space-2);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
}
/*
 * Checkbox, then the name with its note on the line under it.
 *
 * Each option is its own grid, so a three-column `auto auto 1fr` sized the name column per row: the
 * note started 4px after "k3s" and 4px after "Mirantis Kubernetes Engine", and its wrapped second
 * line returned to a third position again. Under the name, every name and every note in the list
 * share one left edge.
 */
.lic-platform {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: baseline;
  -moz-column-gap: var(--space-2);
       column-gap: var(--space-2);
  row-gap: 2px;
  cursor: pointer;
}
.lic-platform > .lic-platform-note { grid-column: 2; }
.lic-platform > .lic-platform-note:empty { display: none; }
.lic-platform-name { font-weight: 600; font-size: var(--text-sm); white-space: nowrap; }
.lic-platform-edit {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  font-size: var(--text-xs);
  color: var(--accent);
  text-align: left;
  cursor: pointer;
}
.lic-platform-edit:hover { text-decoration: none; }
.lic-platform-edit:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.lic-platform-note { font-size: var(--text-xs); color: var(--text-muted); }

/*
 * What each platform ships, as named parts, so no two rows read as the same Kubernetes. A part only
 * that platform runs is drawn solid; parts it shares with others (CoreDNS, kube-proxy) are outlined;
 * add-ons are dashed. The capability line says what only OpenShift has, and its absence elsewhere.
 */
.lic-platform > .lic-platform-parts,
.lic-platform > .lic-platform-caps { grid-column: 2; }
.lic-platform-parts { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px; margin-top: 2px; }
.lic-platform-parts-label { min-width: 4.25rem; font-size: 11px; font-weight: 600; color: var(--text-secondary); }
.lic-part {
  display: inline-block;
  padding: 1px 6px;
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-sm);
  font-size: 11px;
  line-height: 1.5;
  color: var(--text-secondary);
  white-space: nowrap;
}
.lic-part.is-own { border-color: transparent; background: var(--accent-soft); color: var(--accent-text); }
.lic-part.is-addon { border-style: dashed; }
.lic-platform-caps { font-size: 11px; line-height: 1.45; color: var(--text-muted); }

/* A cluster's platform components, on its page. */
.pcomp-name { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2); font-weight: 600; color: var(--text-primary); }
.pcomp-own,
.pcomp-addon { font-size: 10px; font-weight: 600; padding: 0 5px; border-radius: var(--radius-sm); line-height: 1.6; }
.pcomp-own { background: var(--accent-soft); color: var(--accent-text); }
.pcomp-addon { border: 1px dashed var(--border-strong); color: var(--text-muted); }
.pcomp-objects { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; font-size: var(--text-xs); }
.pcomp-objects .mono { overflow-wrap: anywhere; }
.pcomp-fault { margin-bottom: 2px; font-size: var(--text-xs); color: var(--critical-text); }
.pcomp-preview { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px; margin: calc(-1 * var(--space-2)) 0 var(--space-4); }
.pcomp-preview-label { margin-right: 4px; font-size: var(--text-xs); font-weight: 600; color: var(--text-secondary); }

.cdw-editor-failed {
  display: grid;
  gap: var(--space-2);
  justify-items: center;
  align-content: center;
  padding: var(--space-6) var(--space-4);
  text-align: center;
}
.cdw-retry {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  padding: 4px var(--space-3);
  font: inherit;
  font-size: 12px;
  color: var(--text-primary);
  background: var(--bg-surface);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.cdw-retry:hover { background: var(--bg-hover); }
.cdw-retry:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }

.cdw-folder-dep { margin: 0; }
.cdw-folder-dep .mono { font-size: 11px; }
.cdw-empty-actions { display: inline-flex; gap: var(--space-2); flex-wrap: wrap; justify-content: center; }

.cdw-entry > td { vertical-align: middle; }
.cdw-entry-name {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  border: 0;
  background: none;
  padding: 0;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--text-primary);
  text-decoration: none;
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.cdw-entry-name:hover { text-decoration: none; }
.cdw-entry-name:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; border-radius: var(--radius-sm); }

.cdw-dep-link { color: var(--text-primary); text-decoration: none; }
.cdw-dep-link:hover { text-decoration: none; }
.cdw-dep-link:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; border-radius: var(--radius-sm); }

/* ------------------------------------------------------------------ the deployments board */

/* Same pinned single column as `.cdw-index`, and for the same reason — this page carries the same
   folded opener and would size itself to that sentence too. */
.cdw-argo-page { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-4); min-width: 0; }
.cdw-argo-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-4); flex-wrap: wrap; }
.cdw-argo-head h1 { margin: 0; font-size: var(--text-xl); font-weight: 600; color: var(--text-primary); }
.cdw-argo-head p { margin: 0; max-width: 60ch; }
.cdw-argo-actions { display: flex; align-items: center; gap: var(--space-2); flex: none; }

.cdw-argo { display: grid; grid-template-columns: 220px minmax(0, 1fr); gap: var(--space-4); align-items: start; }
.cdw-argo-rail { display: grid; gap: var(--space-4); align-content: start; }
.cdw-facet {
  display: grid;
  gap: 2px;
  padding: var(--space-3);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-card);
  background: color-mix(in srgb, var(--bg-surface) 94%, transparent);
}
.cdw-facet .cdw-label { margin-bottom: var(--space-1); }
.cdw-facet-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 3px 2px;
  font-size: var(--text-sm);
  color: var(--text-secondary);
  cursor: pointer;
  border-radius: var(--radius-sm);
}
.cdw-facet-row:hover { background: var(--bg-hover); color: var(--text-primary); }
.cdw-facet-row input { accent-color: var(--text-primary); }
.cdw-facet-name { margin-right: auto; }

.cdw-argo-main { display: grid; gap: var(--space-3); min-width: 0; align-content: start; }
.cdw-argo-toolbar { display: flex; align-items: center; gap: var(--space-3); }
.cdw-argo-toolbar > :first-child { flex: 1; max-width: 28rem; }

.cdw-approws { display: grid; gap: var(--space-2); }
.cdw-approw {
  display: grid;
  /*
   * The last track is bounded rather than `auto`, and that is what makes this a board instead of a
   * stack of unrelated rows.
   *
   * Each row is its own grid, so an `auto` final track sized itself to that row's own badges — 292px
   * for one, 311px for the next, 210px for a deployment with nothing to report — and every column in
   * front of it moved to absorb the difference. Measured at 1440px: SOURCE began at x=533, 528 and
   * 558 down three consecutive rows, and REVISION wandered 78px. Column headings that do not share
   * an x are not columns, and the reader re-finds them on every line.
   *
   * 20rem is the width at which the common case — two pills and a "synced 14 minutes ago" — stops
   * wrapping; a sync still waiting on approval says more than that and wraps inside its own track,
   * which changes that row's height and no other row's geometry.
   *
   * The four in front of it are not equal because what they hold is not equal. A revision is seven
   * characters; a namespace is as long as somebody named it. Split evenly, `ocp-lab-01/payments-prod`
   * needed 168px of a 167px track and was ellipsised down to `ocp-lab-01/payments-pr…` — two
   * characters of the one field somebody is checking, traded for a single pixel, while the column
   * beside it held a short sha in twice the room it needed.
   */
  grid-template-columns:
    minmax(9rem, 1fr)        /* name and the repository it watches */
    minmax(0, 1.15fr)        /* source: a branch and a folder */
    minmax(0, 1.15fr)        /* destination: a cluster and a namespace */
    minmax(0, 0.7fr)         /* revision: seven characters and maybe a chip */
    minmax(0, 20rem);        /* health, sync and what the last one did */
  gap: var(--space-3);
  align-items: center;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border-hairline);
  border-left: 3px solid var(--border-strong);
  border-radius: var(--radius-card);
  background: color-mix(in srgb, var(--bg-surface) 94%, transparent);
  text-decoration: none;
  color: var(--text-primary);
  min-width: 0;
  transition: border-color var(--motion-fast) var(--ease-out), background var(--motion-fast) var(--ease-out);
}
.cdw-approw:hover { background: var(--bg-hover); border-color: var(--border-strong); }
.cdw-approw:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.cdw-approw.is-healthy { border-left-color: var(--ok); }
.cdw-approw.is-progressing { border-left-color: var(--info); }
.cdw-approw.is-degraded { border-left-color: var(--critical); }
.cdw-approw.is-missing { border-left-color: var(--border-strong); }
.cdw-approw-id { display: grid; gap: 2px; min-width: 0; }
.cdw-approw-name { font-size: 13px; font-weight: 600; }
.cdw-approw-cell { display: grid; gap: 2px; min-width: 0; }
.cdw-cell-label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}
/*
 * The value under a column label, clipped to its own track.
 *
 * Both halves of this rule are load-bearing and both were measured on the board at 1440px before it
 * was written. A source like `platform-manifests:main/apps/payments` is 197px of unbreakable text in
 * a 174px track: the span is inline, so it did not shrink and it was not clipped — it was painted
 * **11px over the Destination column beside it**, one path drawn on top of another, in the row whose
 * whole job is to say where a revision came from and where it went.
 *
 * `overflow: hidden` is what actually fixes it, and not because of the clipping: a grid item's
 * automatic minimum size is its content, so `min-width: 0` on the cell above is undone by the span
 * inside it until the span itself is given a scroll container. `text-overflow` is then how the loss
 * is admitted, and every caller pairs it with a `title` carrying the whole value.
 *
 * `nowrap` is the second half, and it is what squares the header row. The cells are centred on their
 * own content, so one value wrapping to two lines lifted that cell's label 9px clear of the other
 * two — SOURCE, DESTINATION and REVISION were never once on the same line. One line per value means
 * one height per cell, and the labels align because there is nothing left to push them apart.
 */
.cdw-cell-value {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cdw-headchip {
  display: inline-block;
  /*
   * On the baseline an inline-block sits with its bottom margin edge on it, so this chip's whole
   * 15px stood above a 12px line's ascent and grew the line box under it. That was the last 1px of
   * stagger left in the board's header row after the values stopped wrapping: the Revision cell
   * carries a HEAD chip, so only that cell grew, and only its label sat a pixel high.
   */
  vertical-align: middle;
  margin-left: var(--space-2);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.06em;
  padding: 1px 5px;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  color: var(--text-secondary);
}
.cdw-approw-badges { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; justify-content: flex-end; }

.cdw-newdep {
  display: grid;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-card);
  background: color-mix(in srgb, var(--bg-surface) 94%, transparent);
}
.cdw-newdep-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: var(--space-3); }

/* ------------------------------------------------------------------ one deployment: the tree */


/* ==================================================== the deployment console, in the reference's
 *                                                      own visual language
 *
 * The brief was "make it exactly how Argo CD will be", so the status palette below is the
 * reference's literal hex rather than this product's semantic tokens. That is deliberate and it is
 * the one place in this stylesheet where a raw colour is correct: `--ok` here is #3FB950 in dark
 * and #35C48C in light, and a reader comparing this page against the tool it is modelled on would
 * see a different green in each theme. The reference keeps one set of status hues across both of
 * its themes, so these do not vary either — only the surfaces under them do, which is what makes
 * the page work on this product's dark ground.
 *
 * Everything that is not a status colour still comes from the token system, so the page inherits
 * the theme, the focus ring and the type scale like every other surface here.
 */
/*
 * Every value below is read out of the reference's own source, not sampled from a screenshot.
 *
 *   argo-ui/src/styles/config.scss     the palette
 *   argo-cd/ui/src/app/styles/…theme   the light/dark theme map
 *   …/application-resource-tree.scss   the node, the connectors, the labels
 *   …/shared/components/colors.ts      which palette entry each reading takes
 *
 * Three of these corrected a comment in this file that claimed to be quoting the reference and was
 * not. The shadow was written here as `1px 1px 3px rgba(0,0,0,.3)`; it is `1px 1px 1px` of a grey.
 * The canvas was "sampled from it" as #dee6ec; it is #dee6eb, which is gray-3 exactly. And Unknown
 * was gray-5, where `COLORS.*.unknown` is gray-4. A sampled colour is a guess with a decimal point
 * on it.
 */
.cdw-argo-page {
  /* $argo-success-color / $argo-running-color / $argo-failed-color / $argo-status-warning-color. */
  --argo-healthy: #18be94;
  --argo-progressing: #0dadea;
  --argo-degraded: #e96d76;
  --argo-missing: #f4c030;
  --argo-suspended: #766f94;
  /* gray-4. `COLORS.health.unknown` and `COLORS.sync.unknown` are both this, not gray-5. */
  --argo-unknown: #ccd6dd;
  --argo-synced: #18be94;
  --argo-outofsync: #f4c030;

  /*
   * The card, and the ground behind it. `themed('background-2')` and `themed('background-1')`.
   *
   * Dark here first, because this product's page defaults to dark: #100f0f is the reference's own
   * dark ground and #303237 its dark card. The pair is what gives the drawing its depth — a card
   * two full steps lighter than the canvas needs no border to be a card.
   */
  --argo-card: #303237;
  --argo-card-text: #ffffff;
  --argo-canvas: #100f0f;
  /* `box-shadow: 1px 1px 1px $argo-color-gray-7` under `.theme-dark`. A grey, not an alpha black. */
  --argo-shadow: #495763;
  /* `themed('pod-cyan')` — the reference tints Pod cards, and it is the fastest read on the page. */
  --argo-pod: #004c67;

  /*
   * The connector and its arrowhead.
   *
   * Neither is themified in the reference: the line is gray-5 and the arrowhead is the literal
   * #a3a3a3 written into `application-resource-tree.scss`. They read on both grounds, which is
   * presumably why it never needed a second value.
   */
  --argo-line: #8fa4b1;
  --argo-arrow: #a3a3a3;

  /*
   * The kind icon and its label, `$argo-color-gray-7` and `$argo-color-gray-6` — and the one place
   * this page deliberately leaves the reference.
   *
   * Those two are not themified there either, so on the reference's own dark card a #495763 icon
   * sits at 1.7:1 against #303237 and is effectively gone. Following it exactly would mean shipping
   * a known wart. Light keeps the reference's values; dark lifts both to gray-4/gray-5, which is
   * the same relationship against the dark card that the originals have against white.
   */
  --argo-kind-icon: #ccd6dd;
  --argo-kind-label: #8fa4b1;

  /* The hanging pills: `__node-label` is gray-4 on gray-7 with a gray-5 hairline. */
  --argo-label-bg: #495763;
  --argo-label-text: #ccd6dd;
  --argo-label-border: #8fa4b1;
}
:root[data-theme="light"] .cdw-argo-page {
  /*
   * The one place the status palette leaves the reference, and it is not a preference.
   *
   * Measured against the reference's own white card, four of its five status colours fail the 3:1
   * floor a non-text indicator has to clear:
   *
   *   healthy     #18be94  2.38:1
   *   progressing #0dadea  2.57:1
   *   degraded    #e96d76  3.04:1  (the only one that passes)
   *   missing     #f4c030  1.69:1
   *   unknown     #ccd6dd  1.48:1
   *
   * At the 13px these glyphs are drawn at, 1.69:1 is not a quiet colour — it is a shape you cannot
   * find. The glyphs differ per state as well, so no meaning is lost to a reader who cannot see
   * them, but "the information survives" is not a reason to draw something nobody can see.
   *
   * Each is the same hue scaled down until it clears 3:1 with margin, so the page still reads as
   * the reference's palette. Dark is untouched and exactly the reference's, because every one of
   * them passes against #303237 (4.22 to 8.69).
   */
  --argo-healthy: #14a17e;
  --argo-progressing: #0c9ad0;
  --argo-missing: #b08a23;
  --argo-unknown: #898f94;
  --argo-synced: #14a17e;
  --argo-outofsync: #b08a23;

  /* `themed('background-2')` is white, `background-1` is gray-3, text-2 is gray-8. */
  --argo-card: #ffffff;
  --argo-card-text: #363c4a;
  --argo-canvas: #dee6eb;
  /* `box-shadow: 1px 1px 1px $argo-color-gray-4`. */
  --argo-shadow: #ccd6dd;
  --argo-pod: lightcyan;
  --argo-kind-icon: #495763;
  --argo-kind-label: #6d7f8b;
  --argo-label-bg: #ccd6dd;
  --argo-label-text: #495763;
  --argo-label-border: #8fa4b1;
}

/* ------------------------------------------------------------------------- the title bar */

.cdw-argo-title {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-3) var(--space-4);
  flex-wrap: wrap;
}
.cdw-argo-crumb {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  flex-wrap: wrap;
  min-width: 0;
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--text-primary);
}
.cdw-argo-crumb > a {
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--text-muted);
  text-decoration: none;
}
.cdw-argo-crumb > a:hover { color: var(--text-primary); text-decoration: none; }
.cdw-argo-crumb-sep { font-size: var(--text-sm); font-weight: 400; color: var(--color-text-subtle); }
.cdw-argo-crumb-here { font-size: var(--text-lg); font-weight: 650; letter-spacing: -0.01em; }
/* The destination, on the same line rather than a paragraph below: it is an address, not a sentence. */
.cdw-argo-crumb-where {
  flex-basis: 100%;
  font-size: 11px;
  font-weight: 400;
  color: var(--text-muted);
}
.cdw-argo-crumb-where a { color: inherit; }

/*
 * The toolbar. Uppercase, letterspaced, hairline-bordered — the reference's `argo-button--base`.
 *
 * These are plain `<button>`s rather than the shared `Button`, because `Button` writes its palette
 * as an inline style (`css()` is the identity function, CLAUDE.md trap 30) and an inline style
 * cannot be overridden from a stylesheet. Restyling it here was not an option; matching it was not
 * the goal either, since this row is the part of the page that has to read as the reference.
 */
.cdw-argo-bar { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.cdw-argo-group { display: flex; align-items: center; gap: var(--space-1); flex-wrap: wrap; }
.cdw-argo-btn {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 6px 12px;
  border: 1px solid var(--border-hairline);
  border-radius: 4px;
  background: var(--bg-surface);
  color: var(--text-secondary);
  font: inherit;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  line-height: 1.4;
  white-space: nowrap;
  cursor: pointer;
  transition:
    background var(--motion-fast) var(--ease-out),
    color var(--motion-fast) var(--ease-out),
    border-color var(--motion-fast) var(--ease-out);
}
.cdw-argo-btn:hover:not(:disabled) { background: var(--bg-hover); color: var(--text-primary); border-color: var(--border-strong); }
.cdw-argo-btn:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.cdw-argo-btn:disabled { opacity: 0.45; cursor: not-allowed; }
/* The selected view, not a second kind of button: a filled ground and the primary text colour. */
.cdw-argo-btn.is-active {
  background: var(--bg-sunken);
  border-color: var(--border-strong);
  color: var(--text-primary);
}
.cdw-argo-btn.is-go { color: var(--argo-synced); border-color: color-mix(in srgb, var(--argo-synced) 45%, var(--border-hairline)); }
.cdw-argo-btn.is-go:hover:not(:disabled) { border-color: var(--argo-synced); color: var(--argo-synced); }
.cdw-argo-btn.is-danger { color: var(--argo-degraded); }
.cdw-argo-btn.is-danger:hover:not(:disabled) { border-color: var(--argo-degraded); color: var(--argo-degraded); }
/* A count rides inside the button rather than beside it, so the row's rhythm does not change. */
.cdw-argo-btn-count {
  min-width: 1.35em;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--bg-sunken);
  color: var(--text-primary);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0;
  text-align: center;
}
/*
 * The icon on an action button, sized to the cap height of the label beside it.
 *
 * `flex: none` and a fixed box, because an SVG in a flex row with no intrinsic sizing collapses on
 * one browser and stretches on another — and a button whose glyph is a different size from its
 * neighbour's reads as a different kind of control.
 */
.cdw-argo-ico { flex: none; display: block; }
.cdw-argo-btn { display: inline-flex; align-items: center; gap: 6px; }
.cdw-argo-btn.is-active .cdw-argo-btn-count { background: var(--bg-surface); }

/* --------------------------------------------------------------------- the status panel */

.cdw-argo-status {
  display: grid;
  /* `min()` so the 15rem floor stands down on a page narrower than it, rather than overflowing. */
  grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
  border: 1px solid var(--border-hairline);
  border-radius: 4px;
  background: var(--bg-surface);
  box-shadow: 0 1px 3px var(--argo-shadow);
  overflow: hidden;
}
/*
 * Divided by hairlines, the way the reference divides it — not gapped into separate cards.
 *
 * One panel of four readings is a different claim from four cards: it says these are four faces of
 * one state, which is exactly what they are. The first cell has no divider because a rule at the
 * panel's own edge would double its border.
 */
.cdw-argo-stat {
  display: grid;
  align-content: start;
  gap: 3px;
  padding: var(--space-3) var(--space-4);
  border-left: 1px solid var(--border-hairline);
  min-width: 0;
}
.cdw-argo-stat:first-child { border-left: 0; }
.cdw-argo-stat-label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.cdw-argo-stat-value {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--text-primary);
  min-width: 0;
}
/*
 * The mark carries the colour, the word stays in the text colour.
 *
 * Colouring the word as well was the first attempt and it made every cell read as an alert; the
 * reference tints the icon and leaves the label alone, which is why a healthy app there looks
 * calm rather than green.
 */
/* Holds a `StatusIcon`, which sizes itself in `em` — so this font-size is the glyph's size. */
.cdw-argo-mark { display: inline-flex; font-size: 15px; line-height: 1; color: var(--argo-unknown); }
.cdw-argo-stat-value.is-healthy .cdw-argo-mark,
.cdw-argo-stat-value.is-synced .cdw-argo-mark,
.cdw-argo-stat-value.is-succeeded .cdw-argo-mark { color: var(--argo-healthy); }
.cdw-argo-stat-value.is-progressing .cdw-argo-mark,
.cdw-argo-stat-value.is-running .cdw-argo-mark { color: var(--argo-progressing); }
.cdw-argo-stat-value.is-degraded .cdw-argo-mark,
.cdw-argo-stat-value.is-failed .cdw-argo-mark { color: var(--argo-degraded); }
.cdw-argo-stat-value.is-missing .cdw-argo-mark,
.cdw-argo-stat-value.is-out-of-sync .cdw-argo-mark,
.cdw-argo-stat-value.is-pending .cdw-argo-mark { color: var(--argo-missing); }
/* The auto-sync cell holds a control, not a reading, so it sizes to the switch beside it. */
.cdw-argo-stat-value.is-plain { font-size: var(--text-sm); font-weight: 500; }
.cdw-argo-stat-note {
  font-size: 11px;
  line-height: 1.5;
  color: var(--text-muted);
  overflow-wrap: anywhere;
}
.cdw-argo-behind { color: var(--argo-missing); font-weight: 600; }

/* ------------------------------------------------------------------------ the resource tree */

/*
 * The canvas is now the full width of its column, and that reverses what this said.
 *
 * It read "the canvas wraps the drawing, not the viewport", sized `fit-content`, because a bordered
 * box around a half-width tree was mostly empty ruled space. True then and wrong once the canvas
 * gained a zoom: `fit-content` sizes to the *unscaled* drawing, so zooming out shrank the box along
 * with the tree and showed no more of it than before — which is the one thing zooming out is for.
 * The box holds still now and the drawing scales inside it.
 */
.cdw-atree {
  width: 100%;
  /*
   * A floor, so a two-node tree still reads as a canvas rather than as a strip — and so the sticky
   * toolbar always has a ground under it instead of hanging over the page.
   */
  min-height: 18rem;
  /* Top padding clears the floating toolbar; the drawing starts below it, not under it. */
  padding: 3.25rem var(--space-5) var(--space-5);
  border: 1px solid var(--border-hairline);
  border-radius: 4px;
  /*
   * The reference's own canvas ground: a blue-grey, several steps off the page behind it. White
   * cards on a near-white ground have only their hairline to separate them, and at 60% zoom that
   * hairline is half a pixel.
   */
  background: var(--argo-canvas);
  overflow: auto;
}
/*
 * The scaled drawing.
 *
 * `transform-origin` at the inline start keeps the root card still under every zoom step; anchored
 * at the centre, the thing the reader is looking at moves each time they press a button. `width:
 * fit-content` is what the canvas gave up — the drawing is still exactly as wide as the tree, which
 * is what `fit` measures with `scrollWidth`.
 */
.cdw-atree-art {
  width: -moz-fit-content;
  width: fit-content;
  transform-origin: 0 0;
  transition: transform 140ms ease;
}
@media (prefers-reduced-motion: reduce) {
  .cdw-atree-art { transition: none; }
}
.cdw-abranch { display: flex; align-items: center; }
/*
 * 20px between siblings and 80px between ranks, which are the reference's own layout numbers.
 *
 * `application-resource-tree.tsx` lays the graph out with dagre at `{nodesep: 25, ranksep: 80,
 * rankdir: 'LR'}` and every node additionally carries `margin: 10px`. Ours was 12px and 40px, which
 * is why the drawing read as a packed list where the reference reads as a diagram — the white space
 * between ranks is what makes an ownerReference chain legible at a glance.
 *
 * The 20px gap is also what the hanging pills need; see `.cdw-anode-pills`.
 */
.cdw-akids { display: flex; flex-direction: column; gap: 20px; }
.cdw-akid { position: relative; padding-left: 80px; }
/*
 * Dashed elbows with an arrowhead, which reverses what this note used to argue.
 *
 * It read "solid elbows, not dashes ... a dashed line reads as provisional, and an ownerReference is
 * the most definite fact on this page" — written from memory of the reference rather than from the
 * reference, which draws them dashed. The reasoning was also wrong on its own terms: the dashes do
 * not qualify the relationship, they give it a *direction*, which a plain rule cannot. An
 * ownerReference points one way, and the arrow is how the drawing says which.
 *
 * The horizontal run is the dashed part; the vertical riser below stays solid, because it is a
 * bracket grouping siblings rather than a relationship of its own.
 */
.cdw-akid::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 72px;
  height: 0;
  /* `__line` is `border-top: 1px dashed $argo-color-gray-5` — one pixel, not two. */
  border-top: 1px dashed var(--argo-line);
}
/*
 * The arrowhead, on the card rather than on the connector.
 *
 * `.cdw-akid` has spent both its pseudo-elements on the two lines, and the head has to sit at the
 * card's vertical centre — which is not `.cdw-akid`'s centre once that child has children of its
 * own, because the row then grows to hold the whole subtree. The card knows where its own middle
 * is, so the triangle hangs off it.
 */
/*
 * `.cdw-anode-wrap` is in this chain and was not, which is why the arrowheads were invisible.
 *
 * The wrap was introduced so the kebab could be a sibling of the card rather than a child of a
 * `<button>`, and this selector kept naming the card as a direct child of the branch. A `>` chain
 * that stops matching does not fail — it just silently draws nothing, and the dashes have been
 * running into the cards with no head on them ever since. Found by rendering the real markup over
 * the real stylesheet; no test could have caught it, because the rule and the markup were each
 * correct on their own.
 */
.cdw-akid > .cdw-abranch > .cdw-anode-wrap > .cdw-anode::before {
  content: "";
  position: absolute;
  left: -8px;
  top: 50%;
  width: 0;
  height: 0;
  transform: translateY(-50%);
  border: 4px solid transparent;
  /*
   * Its own colour, and a lighter one than the line. The reference writes the arrowhead as a 10px
   * U+25BA in the literal `#a3a3a3` while the line is gray-5, so the head is deliberately quieter
   * than the run it sits on.
   */
  border-inline-start-color: var(--argo-arrow);
  border-inline-end-width: 0;
}
.cdw-akid::after {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 0;
  border-left: 1px dashed var(--argo-line);
}
.cdw-akid:first-child::after { top: 50%; }
.cdw-akid:last-child::after { bottom: 50%; }
.cdw-akid:only-child::after { display: none; }

/*
 * One card, at the reference's own dimensions: 282 x 52 CSS pixels.
 *
 * The width is fixed rather than a range because a column of cards that each sized to their own
 * content had no straight edge anywhere, and the column is the unit the eye follows down the tree.
 * `flex: none` is load-bearing: the root card is a flex item of `.cdw-abranch`, and without it a
 * tree wider than the canvas squeezed that one card to 111px beside its 272px children.
 *
 * Three columns now, not two: the icon stack, the body, and the corner the menu overlays. The body
 * is the only one that may grow, so a long resource name shortens nothing but itself.
 */
.cdw-anode-wrap { position: relative; display: flex; flex: none; }
.cdw-anode {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  position: relative;
  display: grid;
  /*
   * 60px, which is `__node-kind-icon`'s own width in the reference, and 282px overall — `NODE_WIDTH`
   * from `application-resource-tree.tsx` exactly.
   */
  grid-template-columns: 3.75rem minmax(0, 1fr);
  align-items: start;
  gap: 0 var(--space-2);
  width: 17.625rem;
  /*
   * `NODE_HEIGHT` is 52 and this is a floor rather than that number.
   *
   * The reference's card is exactly 52 because everything in it is `white-space: nowrap` with an
   * ellipsis, so it cannot grow. Ours carries a controller's own sentence — the `FailedCreate`
   * explanation this page was built to surface — and truncating that to one line would hide the
   * only text on the card that says *why*. So a card with nothing to add is the reference's height
   * to the pixel, and a card with something to say grows instead of swallowing it.
   */
  min-height: 3.25rem;
  flex: none;
  /* Room at the bottom for the pills, which are out of flow and would otherwise sit on the text. */
  /*
   * Right padding leaves the dots a lane of their own, so a long name never runs under them; the
   * bottom is the band the out-of-flow pills sit in. Widening the right lane for the menu dropped
   * that bottom value once already and the pills landed on the controller's sentence — which is
   * what `padding` shorthand does when a three-value form becomes a four-value one.
   */
  /*
   * Bottom padding is back to the reference's own, because the pills left the card.
   *
   * They used to sit inside it, which is why this had a 1.35rem bottom band reserved for them and
   * a note about a three-value `padding` becoming a four-value one. The reference hangs its labels
   * *below* the card at `bottom: -14px`, and that is the whole reason its cards are 52px — see
   * `.cdw-anode-pills`. With them outside, the band is not needed and the card loses 14px of empty
   * air it never used.
   */
  padding: var(--space-2) 1.6rem var(--space-2) var(--space-3);
  /*
   * A transparent border, not no border.
   *
   * The reference separates a card from the canvas with a shadow alone, and this followed it — then
   * hover had nothing to change but the shadow, which on a blue-grey ground is nearly invisible.
   * Reserving the border keeps the reference's look at rest and gives hover and focus something to
   * draw, without the 2px of reflow that adding a border on hover would cause.
   */
  border: 1px solid transparent;
  border-radius: 4px;
  background: var(--argo-card);
  /*
   * `1px 1px 1px` of a grey, which is the reference's own and not what this file used to claim.
   *
   * It is barely a shadow — a one-pixel offset with no spread reads as a second edge on the bottom
   * and right rather than as lift, and that hard-edged offset is a large part of why the reference's
   * tree looks like a diagram instead of like a page of material cards. The soft `0 1px 3px` black
   * that was here is a different visual language.
   */
  box-shadow: 1px 1px 1px var(--argo-shadow);
  color: var(--argo-card-text);
  font: inherit;
  text-align: start;
  cursor: pointer;
}
/*
 * Pod cards are tinted, as the reference tints them (`themed('pod-cyan')`).
 *
 * It is the one kind that gets its own ground there, and on a tree whose bottom rank is thirty Pods
 * it is the fastest read on the page — you can see where the workloads stop without reading a word.
 */
.cdw-anode.is-pod { background: var(--argo-pod); }
.cdw-anode:hover { border-color: var(--border-strong); box-shadow: 2px 2px 1px var(--argo-shadow); }
.cdw-anode:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.cdw-anode.is-app { cursor: default; border-color: var(--border-strong); }
.cdw-anode.is-app:hover { box-shadow: 0 1px 3px var(--argo-shadow); }

/*
 * The icon stack: a circle holding a drawing, with the kind's short tag under it.
 *
 * The tag used to be inside the circle, where it had 2.25rem to live in and `endpointslice` was cut
 * to six characters — the one abbreviation on this card a reader cannot expand. Under the circle it
 * has the column's width and wraps rather than truncating, which is the reference's arrangement and
 * the reason the reference can afford long tags at all.
 */
.cdw-anode-icon { display: grid; justify-items: center; gap: 3px; padding-top: 1px; min-width: 0; }
/*
 * The reference draws the kind as a bare 2em pictogram on the card, with no disc behind it.
 *
 * The disc was ours, and it was carrying the card: on the old near-white ground a floating glyph
 * had nothing to sit against. With the card at `themed('background-2')` and the canvas two steps
 * away from it the glyph reads on its own, and dropping the disc is what makes the left column look
 * like the reference's rather than like a row of avatars. Only the application node keeps a circle,
 * because there it is a filled badge rather than a container — `--big` in the reference.
 */
.cdw-anode-glyph {
  display: grid;
  place-items: center;
  width: 1.9rem;
  height: 1.9rem;
  color: var(--argo-kind-icon);
}
.cdw-anode-glyph > svg { width: 1.55rem; height: 1.55rem; }
/*
 * The application's own node reads as the root, not as another resource.
 *
 * The reference draws it with a noticeably larger filled circle, and the difference is doing real
 * work: it is the only card in the drawing that is not a Kubernetes object, so a reader scanning
 * for "where does this start" has something to land on. Its abbreviation is dropped for the same
 * reason — `app` under a circle that is plainly the app is a label for nobody.
 */
/*
 * `__node-kind-icon--big`: a 65px gray-5 disc with a 4px ring in the card's own colour, hung off the
 * card's left edge at `left: -20px; top: -8px`.
 *
 * The overhang is the point. It is the only card in the drawing that is not a Kubernetes object,
 * and breaking the left edge of the column is what says so before a single word is read. The ring
 * is in `themed('background-2')`, not white, so it reads as the badge punching through the card
 * rather than as a stroke around it.
 *
 * Scaled to 2.9rem here rather than the literal 65px, because our card is the reference's width
 * with a 60px icon column and a 65px disc inside it would cover the name.
 */
.cdw-anode.is-app .cdw-anode-glyph {
  width: 2.9rem;
  height: 2.9rem;
  margin-left: -1.15rem;
  margin-top: -0.4rem;
  background: #8fa4b1;
  border: 4px solid var(--argo-card);
  color: #ffffff;
}
.cdw-anode.is-app .cdw-anode-glyph > svg { width: 1.6rem; height: 1.6rem; }
.cdw-anode.is-app .cdw-anode-icon { gap: 0; }
.cdw-anode.is-app .cdw-anode-abbr { display: none; }
.cdw-anode-art { display: block; }
/*
 * The tag wraps rather than truncating, and that decides the column's width.
 *
 * `endpointslice` is thirteen characters; at 9px it is about 54px, which is why the icon column is
 * 2.5rem rather than the circle's 1.9rem. Anything longer takes a second line, which costs the card
 * nothing because the body beside it is taller than the circle either way.
 */
/* `__node-kind`: 0.7em of the card's 14px, in gray-6. */
.cdw-anode-abbr {
  font-size: 9.8px;
  line-height: 1.25;
  letter-spacing: 0.02em;
  text-align: center;
  color: var(--argo-kind-label);
  overflow-wrap: anywhere;
}
/*
 * `line-height: 0.95` is the reference's, and it is not a rounding of 1.
 *
 * Sub-one leading is what lets a kind, a name and a row of status icons sit inside 52px without
 * looking crammed, and it is most of why the reference's cards read as dense rather than as small.
 */
.cdw-anode-main { display: grid; gap: 4px; min-width: 0; line-height: 0.95; }
/* `__node-title`: 0.8em. */
.cdw-anode-name {
  font-size: 11.2px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--argo-card-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cdw-anode-status, .cdw-anode-marks { display: flex; align-items: center; gap: 3px 5px; flex-wrap: wrap; }
.cdw-anode-note {
  font-size: 10px;
  line-height: 1.4;
  color: var(--argo-kind-label);
  overflow-wrap: anywhere;
}
/*
 * Ours, and deliberately quiet. The reference has nothing like it, so it borrows the kind label's
 * grey rather than inventing a third tone on a card that already carries two.
 */
.cdw-anode-chip {
  padding: 0 4px;
  border-radius: 3px;
  background: color-mix(in srgb, var(--argo-kind-label) 18%, transparent);
  color: var(--argo-kind-label);
  font-size: 10px;
  white-space: nowrap;
}
/*
 * The pills, pinned to the bottom corner the reference puts them in.
 *
 * Out of flow on purpose. In flow they rode up and down with the body, so a card with a two-line
 * controller sentence carried its age 18px lower than the card beside it — and the age is the one
 * value on these cards a reader compares *between* cards rather than reading on one, which needs a
 * straight edge to run down.
 */
/*
 * The pills hang off the bottom edge, outside the card. `__node-labels` is `bottom: -14px; right: 0`.
 *
 * This is the detail that sets the reference's card height. Ours kept them inside and reserved a
 * 1.35rem band for them, which is why the card could not be 52px — the band *was* the extra 14px,
 * spent on air for most cards. Hanging them under the card gives that back and puts the age and the
 * replica count on the same baseline as the reference, in the gutter between ranks where nothing
 * else is competing for the space.
 *
 * `.cdw-akids` carries a 20px gap for this, matching the reference's `margin: 10px` per node. The
 * two numbers belong together: shrink the gap and the pills land on the next card.
 */
.cdw-anode-pills {
  position: absolute;
  right: 0;
  bottom: -14px;
  display: flex;
  align-items: center;
  gap: 1px;
}
.cdw-anode-pill {
  padding: 0 5px;
  border-radius: 5px;
  background: var(--argo-label-bg);
  border: 1px solid var(--argo-label-border);
  color: var(--argo-label-text);
  font-size: 8.4px;
  line-height: 1.5;
  text-transform: lowercase;
  white-space: nowrap;
}
/*
 * Health and sync, under the name rather than pinned to the top-right corner.
 *
 * That corner now belongs to the menu, and two things in it would collide. Under the name they
 * still line up down the tree, because the name is always the card's first line whatever else
 * the card carries.
 */
.cdw-anode-heart { font-size: 13px; line-height: 1; color: var(--argo-unknown); display: inline-flex; }
.cdw-anode-heart.is-healthy { color: var(--argo-healthy); }
.cdw-anode-heart.is-progressing { color: var(--argo-progressing); }
.cdw-anode-heart.is-degraded { color: var(--argo-degraded); }
.cdw-anode-heart.is-missing { color: var(--argo-missing); }
.cdw-anode-sync { font-size: 13px; line-height: 1; display: inline-flex; color: var(--argo-unknown); }
.cdw-anode-sync.is-ok { color: var(--argo-synced); }
.cdw-anode-sync.is-off { color: var(--argo-outofsync); }

/*
 * The status glyphs themselves. See `status-icon.tsx` for which shape each reading takes and why
 * they are drawn here rather than borrowed from the icon font the reference uses.
 *
 * Sized in `em` and coloured by `currentColor`, so the component carries no size or colour of its
 * own and every call site styles it with the class it already had. `vertical-align` because an
 * inline SVG sits on the text baseline by default and a 13px glyph then hangs below a 13px word.
 */
.cdw-sico { display: block; vertical-align: -0.125em; }
/*
 * Progressing spins, as it does in the reference — `fa-circle-notch fa-spin`.
 *
 * It is the only animation on this page and it earns itself: Progressing is the one reading that
 * means "ask again in a moment", and a still icon for it is indistinguishable from a stuck one.
 */
.cdw-sico.is-spin { animation: cdw-sico-spin 1.6s linear infinite; transform-origin: 50% 50%; }
@keyframes cdw-sico-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  /*
   * Held still rather than switched off. The notch has a gap in it, so a reader who has asked for
   * no motion still sees a glyph that is plainly not the full ring that Unknown draws.
   */
  .cdw-sico.is-spin { animation: none; }
}

/* ------------------------------------------------------------- the floating graph toolbar */

/*
 * Pinned to the canvas's own corner, inside its padding.
 *
 * `position: sticky` and not `absolute`: the canvas scrolls in both axes, and an absolutely
 * positioned child scrolls away with the drawing — which is precisely when a reader reaches for the
 * zoom. Sticky at `top: 0; left: 0` keeps it in the corner of the *viewport onto* the canvas.
 *
 * `width: fit-content` with a negative bottom margin so it occupies no row of its own: without it
 * the bar pushes the whole drawing down by its own height and the tree no longer starts at the top.
 */
.cdw-gtool {
  position: sticky;
  top: 0;
  left: 0;
  z-index: 3;
  display: inline-flex;
  align-items: center;
  gap: 1px;
  width: -moz-fit-content;
  width: fit-content;
  margin-bottom: -2.25rem;
  /*
   * `.graph-options-panel` in the reference: `padding: 5px`, the card's own background, a
   * `1px 1px 3px` gray-5 shadow, and — the part that is easy to miss — **no border-radius**. A
   * square-cornered panel on a canvas of 4px-rounded cards is what makes it read as an instrument
   * sitting on the drawing rather than as another card in it.
   */
  padding: 5px;
  border: 0;
  border-radius: 0;
  background: var(--argo-card);
  box-shadow: 1px 1px 3px #8fa4b1;
}
.cdw-gtool-btn {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  display: grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  padding: 0;
  border: 0;
  border-radius: 4px;
  background: transparent;
  color: var(--text-secondary);
  cursor: pointer;
}
.cdw-gtool-btn:hover:not(:disabled) { background: var(--bg-hover); color: var(--text-primary); }
.cdw-gtool-btn:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -1px; }
/*
 * A spent button dims and keeps its place rather than disappearing.
 *
 * `cursor: default`, not `not-allowed`: there is nothing wrong with pressing Collapse when
 * everything is already collapsed, it simply has nothing left to do, and a slashed circle reads as
 * a refusal.
 */
.cdw-gtool-btn:disabled { opacity: 0.4; cursor: default; }
.cdw-gtool-sep { width: 1px; height: 1.1rem; margin: 0 2px; background: var(--border-hairline); }
.cdw-gtool-level {
  display: grid;
  place-items: center;
  min-width: 3rem;
  height: 1.75rem;
  margin-inline-start: 2px;
  padding: 0 var(--space-2);
  border: 1px solid var(--border-hairline);
  border-radius: 4px;
  background: var(--bg-sunken);
  color: var(--text-primary);
  font-size: 11px;
  font-weight: 600;
}

/* ------------------------------------------------------------------------- the per-node menu */

/*
 * Overlaid on the card's top-right corner, as a sibling rather than a child.
 *
 * A button inside a button is invalid HTML and browsers recover by hoisting the inner one out of
 * the outer — which puts the dots somewhere the CSS did not expect and crosses the two click
 * handlers. The wrapper is the positioning context for both.
 */
/*
 * Centred on the card's right edge, which is where the reference puts it.
 *
 * It was pinned to the top corner. That works on a card of one line and drifts away from the card's
 * optical centre as soon as a controller's sentence takes the body to three — and these cards vary
 * from one line to four, so the column of dots was never straight. `translateY(-50%)` off the
 * midpoint is: the dots now line up down the tree whatever each card carries.
 */
.cdw-nmenu { position: absolute; top: 50%; right: 2px; transform: translateY(-50%); }
.cdw-nmenu-dots {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  display: grid;
  place-items: center;
  width: 1.4rem;
  height: 1.4rem;
  padding: 0;
  border: 0;
  border-radius: 4px;
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
}
.cdw-nmenu-dots:hover { background: var(--bg-hover); color: var(--text-primary); }
.cdw-nmenu-dots:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
.cdw-nmenu-dots[aria-expanded="true"] { background: var(--bg-hover); color: var(--text-primary); }
/*
 * Fixed, and rendered into `document.body` rather than beside the card.
 *
 * The card lives inside `.cdw-atree`, which is `overflow: auto`, inside `.cdw-atree-art`, which
 * carries the zoom as a `transform`. Positioned against the card the panel opened outside the
 * canvas on every card in the lower half and was clipped away entirely — the dots looked dead.
 * Neither `z-index` (clipping is not paint order) nor `position: fixed` alone (a transformed
 * ancestor becomes the containing block for fixed descendants) escapes that; only a portal does.
 *
 * `top` and `left` come from the trigger's own rectangle, set inline by `NodeMenu`.
 */
.cdw-nmenu-panel {
  position: fixed;
  z-index: 60;
  display: grid;
  min-width: 13rem;
  padding: 4px;
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  background: var(--bg-surface);
  box-shadow: 0 6px 18px var(--argo-shadow);
}
.cdw-nmenu-item {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  display: grid;
  gap: 1px;
  padding: 5px var(--space-2);
  border: 0;
  border-radius: 4px;
  background: transparent;
  color: var(--text-primary);
  font: inherit;
  text-align: start;
  cursor: pointer;
}
.cdw-nmenu-item:hover { background: var(--bg-hover); }
.cdw-nmenu-item:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }
.cdw-nmenu-label { font-size: var(--text-sm); font-weight: 600; }
/*
 * The hint is what stops this being three ways to the same place. Each entry lands somewhere
 * different in the panel, and the line under the label is where that is said.
 */
.cdw-nmenu-hint { font-size: 10px; line-height: 1.4; color: var(--text-muted); overflow-wrap: anywhere; }

/* ------------------------------------------------------------------------- the fold handle */

/*
 * On the connector between a card and its children, which is the only placement that reads as
 * acting on the relationship rather than on either end of it.
 *
 * `flex: none` because `.cdw-abranch` is a flex row: without it the handle is squeezed to nothing
 * on a tree wider than the canvas, exactly when folding is what a reader wants.
 */
.cdw-afold {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  flex: none;
  align-self: center;
  min-width: 1.25rem;
  height: 1.25rem;
  margin-inline-start: 4px;
  padding: 0 3px;
  border: 1px solid var(--argo-line);
  border-radius: 999px;
  background: var(--bg-surface);
  color: var(--text-muted);
  font: inherit;
  font-size: 10px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  z-index: 1;
}
.cdw-afold:hover { border-color: var(--border-strong); color: var(--text-primary); }
.cdw-afold:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
/*
 * A folded handle shows how many are behind it, counted through the whole subtree.
 *
 * The direct child count would be a lie on the branch that matters: a Deployment folds away one
 * ReplicaSet and thirty-seven Pods, and "+1" beside the handle invites a reader to leave it folded.
 */
.cdw-afold[aria-expanded="false"] {
  border-color: var(--color-primary-solid);
  background: var(--color-primary-solid);
  color: var(--color-on-primary);
}

/*
 * A node drawn only to carry a match below it.
 *
 * Subordinated rather than hidden, because a match with no owner above it has nowhere to sit in a
 * tree — a crash-looping Pod under a healthy Deployment would be drawn at the root with nothing to
 * say which workload it belongs to.
 *
 * ## Why this is not `opacity` on the card
 *
 * It was, at 0.45, with a comment claiming that cleared 3:1 for the kind label. Measured in a
 * browser over this stylesheet it is **1.83:1** — the label is `--text-muted`, which is 4.83:1 at
 * full strength, so any dimming at all takes it under the floor and even 0.8 only reaches 3.28.
 * There is no opacity that both de-emphasises the card and keeps its text legible, because the one
 * is the other.
 *
 * So the weight comes off the surface and the decoration instead: a sunken ground, a dashed border
 * and dimmed glyphs, with every character at full contrast. That also keeps what the opacity was
 * for — the health mark is decoration here and dims with the rest, so the filter cannot leave a
 * full-strength red heart on a card it was asked to drop.
 */
.cdw-anode.is-context {
  background: var(--bg-sunken);
  border-style: dashed;
  box-shadow: none;
}
.cdw-anode.is-context .cdw-anode-icon,
.cdw-anode.is-context .cdw-anode-marks,
.cdw-anode.is-context .cdw-anode-chip { opacity: 0.55; }
.cdw-anode.is-context:hover,
.cdw-anode.is-context:focus-visible { background: var(--bg-surface); border-style: solid; }
.cdw-anode.is-context:hover .cdw-anode-icon,
.cdw-anode.is-context:hover .cdw-anode-marks,
.cdw-anode.is-context:hover .cdw-anode-chip { opacity: 1; }

/* ------------------------------------------------------------------- what needs attention */

/*
 * The band between the status panel and the resources.
 *
 * Not a `Notice`. A Notice is one tone and one block of prose, and this carries two vocabularies
 * that must not be summed — a broken object and a policy that will block work later — each with its
 * own glyph, its own colour and its own controls. Rendering both as warnings was the first attempt
 * and it made a stalled upgrade and an outage look like the same thing.
 */
.cdw-att {
  display: grid;
  gap: var(--space-2);
  padding: 16px 18px;
  border: 1px solid var(--border-hairline);
  /* The delivery line's card, directly above it: two radii and a shadow on one made them two products. */
  border-radius: var(--radius-xl);
  background: var(--bg-surface);
}
.cdw-att-summary {
  margin: 0;
  font-size: 12px;
  font-weight: 650;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.cdw-att-list { display: grid; gap: var(--space-3); margin: 0; padding: 0; list-style: none; }
/*
 * The glyph column is fixed so every row's text starts on one line, whatever the glyph.
 *
 * The three marks have different widths at the same font size, and left to flow they put each
 * row's first word in a slightly different place — which is exactly the kind of ragged edge that
 * makes a list of two items read as a list of two unrelated things.
 */
.cdw-att-row {
  display: grid;
  grid-template-columns: 1.25rem minmax(0, 1fr);
  gap: var(--space-2);
  align-items: start;
  padding-inline-start: var(--space-3);
  /* A transparent border on every row, coloured per tone, so the text never shifts between them. */
  border-inline-start: 3px solid transparent;
}
.cdw-att-row.is-issue { border-inline-start-color: var(--argo-degraded); }
.cdw-att-row.is-risk { border-inline-start-color: var(--argo-missing); }
.cdw-att-glyph { font-size: 13px; line-height: 1.5; text-align: center; }
.cdw-att-row.is-issue .cdw-att-glyph { color: var(--argo-degraded); }
.cdw-att-row.is-risk .cdw-att-glyph { color: var(--argo-missing); }
.cdw-att-body { display: grid; gap: 3px; min-width: 0; }
.cdw-att-head {
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  flex-wrap: wrap;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text-primary);
}
.cdw-att-chain { overflow-wrap: anywhere; }
.cdw-att-title { letter-spacing: -0.01em; }
.cdw-att-on { font-weight: 500; color: var(--text-muted); font-size: 12px; }
.cdw-att-on-name { color: var(--text-secondary); font-weight: 600; }
.cdw-att-facts {
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: var(--space-2) var(--space-3);
  flex-wrap: wrap;
  font-size: 11px;
  color: var(--text-muted);
}
.cdw-att-count { font-weight: 600; color: var(--text-primary); }
/*
 * The one fact a tree cannot draw: a controller that produced nothing. It carries the tone.
 *
 * `--critical-text`, not `--argo-degraded`. The Argo palette is tuned for marks and borders against
 * a surface, and at 11px bold this text measured 3.04:1 on the band — under the 4.5 floor that
 * applies to anything smaller than 18.66px bold. The text token is 7.28:1 and the same hue.
 */
.cdw-att-flag { font-weight: 600; color: var(--critical-text); }
/*
 * The age goes to the far end of the line, away from the counts.
 *
 * It is the only item on this row that is context rather than a fact to act on, and sitting third
 * in the flow it read as part of the failure — "0 of 1 ready, created no Pod, created 11d ago" is
 * one sentence in which the last clause looks like a cause. `auto` on the inline start separates
 * them; `wrap` on the parent means it drops to its own line rather than overflowing when there is
 * no room, which is the behaviour a long chain name needs.
 */
.cdw-att-age { margin-inline-start: auto; }
.cdw-att-msg {
  margin: 0;
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--text-secondary);
  overflow-wrap: anywhere;
}
/*
 * The disclosure for a controller's longer message.
 *
 * The one this exists for runs to about 1,600 characters, because a SecurityContextConstraints
 * refusal names every provider in the cluster and why each refused. Flat it pushes the tree off the
 * screen; truncated it loses the clauses that say which constraint to change. So: the first line
 * inline, the rest one click away, and never a character edited.
 */
.cdw-att-more > summary { cursor: pointer; display: grid; gap: 2px; }
.cdw-att-more > summary::-webkit-details-marker { display: none; }
.cdw-att-more > summary::marker { content: ""; }
.cdw-att-more-hint {
  font-size: 11px;
  font-weight: 600;
  color: var(--color-primary-text);
  -webkit-text-decoration: underline dotted;
          text-decoration: underline dotted;
  text-underline-offset: 2px;
}
.cdw-att-full {
  margin: var(--space-2) 0 0;
  padding: var(--space-3);
  max-height: 16rem;
  overflow: auto;
  /*
   * Reaching the end of this box must not then start moving the page behind it, which reads as a
   * rendering fault rather than a scroll one. `scroll.test.ts` holds every scroller to it.
   */
  overscroll-behavior-x: contain;
  font-size: 11px;
  line-height: 1.6;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  border: 1px solid var(--border-hairline);
  border-radius: 4px;
  background: var(--bg-sunken);
  color: var(--text-secondary);
}
/*
 * Where the sentence came from: the event's reason, how often, how recently.
 *
 * Above the message rather than after it, because it changes how the message is read. The same
 * refusal recorded once an hour ago and 1,385 times since the deployment was created is the same
 * sentence about two different situations, and a reader who meets the count second has already
 * decided what they think.
 */
.cdw-att-from {
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: var(--space-2) var(--space-3);
  flex-wrap: wrap;
  font-size: 11px;
  color: var(--text-muted);
}
/* `--critical-text` for the same reason as `.cdw-att-flag`: the mark palette measured 2.76:1 here. */
.cdw-att-reason {
  padding: 1px 5px;
  border-radius: 3px;
  background: var(--bg-sunken);
  color: var(--critical-text);
  font-weight: 600;
}
.cdw-att-acts { margin: 2px 0 0; display: flex; gap: var(--space-2); flex-wrap: wrap; }
/*
 * Consequences, set apart from the list above rather than inside it.
 *
 * An unhealthy reading that follows from an issue is named so nothing is hidden, and subordinated so
 * nobody goes and "fixes" it. A disruption budget reporting zero allowed evictions because the
 * workload it covers has no pods is configured correctly; listing it as an issue would send a
 * reader to change the one thing here that is right.
 */
.cdw-att-conseq {
  display: grid;
  gap: 4px;
  margin: 0;
  padding: var(--space-2) 0 0;
  border-top: 1px solid var(--border-hairline);
  list-style: none;
  font-size: 11px;
  line-height: 1.5;
  color: var(--text-muted);
}
.cdw-att-conseq > li { display: grid; grid-template-columns: 1.25rem minmax(0, 1fr); gap: var(--space-2); }

/* ------------------------------------------------- the resource toolbar, rail and canvas column */

/*
 * Rail beside canvas, the reference's layout.
 *
 * The filters were a row above the drawing and the counts are why they moved. A column of readings
 * each carrying a number is a verdict on the namespace that stays on screen while somebody works
 * the canvas; above it, that verdict scrolls away at the moment it is most useful.
 */
.cdw-rwrap {
  display: grid;
  grid-template-columns: 13.5rem minmax(0, 1fr);
  gap: var(--space-4);
  align-items: start;
}
.cdw-rmain { display: grid; gap: var(--space-3); min-width: 0; }
@media (max-width: 860px) {
  /*
   * Stacked, rail first.
   *
   * On a phone the counts are worth more than the drawing — "Degraded 1" is the whole answer for
   * most visits, and the tree below it is for the visits where it is not.
   */
  .cdw-rwrap { grid-template-columns: minmax(0, 1fr); }
}

.cdw-rail {
  display: grid;
  gap: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--border-hairline);
  border-radius: 4px;
  background: var(--bg-surface);
  min-width: 0;
}
.cdw-rail-title {
  margin: 0;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text-primary);
}
.cdw-rail-field { display: grid; gap: 3px; }
.cdw-rail-legend {
  padding: 0;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.cdw-rail-input {
  width: 100%;
  padding: 5px var(--space-2);
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  background: var(--bg-surface);
  color: var(--text-primary);
  font: inherit;
  font-size: var(--text-sm);
}
.cdw-rail-input:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
/*
 * A `fieldset` for each group, because the legend is what names the checkboxes inside it to a
 * screen reader. Browsers give fieldsets a border and padding of their own; both go.
 */
.cdw-rail-group {
  display: grid;
  gap: 2px;
  margin: 0;
  padding: 0;
  border: 0;
  border-top: 1px solid var(--border-hairline);
  padding-top: var(--space-3);
}
.cdw-rail-group > .cdw-rail-legend { padding-bottom: 3px; width: 100%; }
/*
 * The section header is the toggle, as the reference draws it: the label, and a chevron at the far
 * edge. Full width so the whole row is the hit area — a chevron alone is a 12px target.
 */
.cdw-rail-toggle {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  width: 100%;
  padding: 2px 2px 2px 0;
  border: 0;
  border-radius: 3px;
  background: transparent;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
}
.cdw-rail-toggle:hover { color: var(--text-primary); }
.cdw-rail-toggle:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
.cdw-rail-chev { font-size: 9px; line-height: 1; }
/*
 * The count is pushed to the far edge by the name's `1fr`, so the numbers line up down the column.
 * That alignment is the whole readability of the group: a ragged right edge turns a table of counts
 * back into a list of sentences.
 */
.cdw-rail-row {
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-2);
  padding: 3px 2px;
  border-radius: 3px;
  font-size: var(--text-sm);
  color: var(--text-primary);
  cursor: pointer;
}
.cdw-rail-row:hover { background: var(--bg-hover); }
.cdw-rail-row:has(input:focus-visible) { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
.cdw-rail-mark { display: inline-flex; font-size: 12px; line-height: 1; color: var(--argo-unknown); }
.cdw-rail-mark.is-healthy, .cdw-rail-mark.is-synced { color: var(--argo-healthy); }
.cdw-rail-mark.is-progressing { color: var(--argo-progressing); }
.cdw-rail-mark.is-degraded { color: var(--argo-degraded); }
.cdw-rail-mark.is-missing, .cdw-rail-mark.is-out-of-sync { color: var(--argo-missing); }
.cdw-rail-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/*
 * The count is a reading, not a badge.
 *
 * It was a filled pill first and every row then carried a chip, which made the zeros as loud as the
 * ones — and the one thing this column is for is spotting the row that is not zero.
 */
.cdw-rail-count { font-size: 11px; font-weight: 600; color: var(--text-muted); }

.cdw-rtool {
  display: flex;
  align-items: center;
  gap: var(--space-3) var(--space-4);
  flex-wrap: wrap;
}
.cdw-rtool-views { display: flex; }
.cdw-rtool-view {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  padding: 5px var(--space-3);
  border: 1px solid var(--border-strong);
  background: var(--bg-surface);
  color: var(--text-secondary);
  font: inherit;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  cursor: pointer;
}
.cdw-rtool-view:first-child { border-radius: 4px 0 0 4px; }
.cdw-rtool-view:last-child { border-radius: 0 4px 4px 0; border-inline-start-width: 0; }
.cdw-rtool-view:hover { background: var(--bg-hover); color: var(--text-primary); }
.cdw-rtool-view:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; z-index: 1; }
/*
 * The selected view is filled, not merely tinted.
 *
 * `aria-selected` carries it for a screen reader; this carries it for everybody else, and a
 * two-button group needs the contrast to be unmistakable — a one-step tint between two adjacent
 * buttons reads as a hover state that got stuck.
 */
.cdw-rtool-view.is-active {
  background: var(--color-primary-solid);
  border-color: var(--color-primary-solid);
  color: var(--color-on-primary);
}
/* The zoom group, drawn as one segmented control so it reads as one thing acting on the canvas. */
.cdw-rtool-zoom { display: flex; align-items: stretch; }
.cdw-rtool-zbtn {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  min-width: 2rem;
  padding: 4px var(--space-2);
  border: 1px solid var(--border-strong);
  border-inline-start-width: 0;
  background: var(--bg-surface);
  color: var(--text-secondary);
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.4;
  cursor: pointer;
}
.cdw-rtool-zoom > :first-child { border-inline-start-width: 1px; border-radius: 4px 0 0 4px; }
.cdw-rtool-zoom > :last-child { border-radius: 0 4px 4px 0; }
.cdw-rtool-zbtn:hover:not(:disabled) { background: var(--bg-hover); color: var(--text-primary); }
.cdw-rtool-zbtn:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; z-index: 1; }
/*
 * A spent zoom button is dimmed and keeps its place rather than disappearing.
 *
 * `cursor: default`, not `not-allowed`: there is nothing wrong with pressing it, it has simply run
 * out of range, and the slashed circle reads as a refusal.
 */
.cdw-rtool-zbtn:disabled { opacity: 0.45; cursor: default; }
.cdw-rtool-zlevel {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 3.25rem;
  padding: 4px var(--space-2);
  border: 1px solid var(--border-strong);
  border-inline-start-width: 0;
  background: var(--bg-sunken);
  color: var(--text-primary);
  font-size: 12px;
  font-weight: 600;
}
.cdw-rtool-count { margin: 0 0 0 auto; font-size: 11px; color: var(--text-muted); }

/* ------------------------------------------------------------------------ the resource list */

.cdw-rlist-wrap {
  border: 1px solid var(--border-hairline);
  border-radius: 4px;
  background: var(--bg-surface);
  overflow-x: auto;
  /* As above: the table scrolls, the page behind it does not come with it. */
  overscroll-behavior-x: contain;
}
.cdw-rlist { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.cdw-rlist th {
  position: sticky;
  top: 0;
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border-strong);
  background: var(--bg-sunken);
  color: var(--text-muted);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-align: start;
  white-space: nowrap;
}
.cdw-rlist td {
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border-hairline);
  color: var(--text-primary);
  vertical-align: top;
}
.cdw-rlist tbody tr:last-child td { border-bottom: 0; }
.cdw-rlist tbody tr:hover td { background: var(--bg-hover); }
/*
 * Same meaning as `.cdw-anode.is-context`, and the same correction: drawn to carry a match below it,
 * subordinated by its ground rather than by an opacity that would take its text under the contrast
 * floor. `--text-muted` is 4.83:1 here and the row still reads.
 */
.cdw-rlist tbody tr.is-context td { background: var(--bg-sunken); color: var(--text-muted); }
.cdw-rlist tbody tr.is-context td .cdw-rlist-health { opacity: 0.6; }
.cdw-rlist tbody tr.is-context:hover td .cdw-rlist-health { opacity: 1; }
.cdw-rlist-name {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  padding: 0;
  border: 0;
  background: none;
  color: var(--color-primary-text);
  font: inherit;
  text-align: start;
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
  overflow-wrap: anywhere;
}
.cdw-rlist-name:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.cdw-rlist-health { display: inline-flex; align-items: baseline; gap: 5px; white-space: nowrap; }
.cdw-rlist-health.is-healthy { color: var(--argo-healthy); }
.cdw-rlist-health.is-progressing { color: var(--argo-progressing); }
.cdw-rlist-health.is-degraded { color: var(--argo-degraded); }
.cdw-rlist-health.is-missing { color: var(--argo-missing); }
.cdw-rlist-health.is-unknown { color: var(--argo-unknown); }
/*
 * The status column holds a controller's sentence, so it is capped and wraps.
 *
 * Uncapped, the SecurityContextConstraints refusal makes this column 1,600 characters wide and
 * every other column collapses to its content — the table stops being readable down any of them,
 * which is the one thing the list view is for.
 */
.cdw-rlist-status {
  max-width: 28rem;
  font-size: 11px;
  line-height: 1.5;
  color: var(--text-muted);
  overflow-wrap: anywhere;
}

@media (max-width: 720px) {
  /*
   * The panel is a row of cells by design; below this it is four stacked cells, divided the other
   * way.
   *
   * The single column is what makes that sentence true, and it was missing. `auto-fit` collapses on
   * the container's width, not the viewport's, so between roughly 510px and this breakpoint the
   * cells were still laid out two-by-two while these rules had already turned the dividers through
   * ninety degrees — two cells side by side with nothing between them, and a horizontal rule that
   * began halfway across the panel because only the right-hand cell of the first row had a top
   * border. Measured at 600px: two 266px columns, `border-left: 0` on all four.
   */
  .cdw-argo-status { grid-template-columns: minmax(0, 1fr); }
  .cdw-argo-stat { border-left: 0; border-top: 1px solid var(--border-hairline); }
  .cdw-argo-stat:first-child { border-top: 0; }
}
/*
 * `min-width: 0` on the panel and on every section in it.
 *
 * A grid item defaults to `min-width: auto`, so without this any one section's min-content becomes
 * a floor for the whole dialog — and the dialog's body has `overflow-y: auto`, which CSS promotes
 * the other axis to `auto` as well. That is where the horizontal scrollbar under the panel came
 * from: not a rule asking for one, but a child that refused to shrink.
 */
.cdw-res { display: grid; gap: var(--space-3); min-width: 0; }
.cdw-res > * { min-width: 0; }
.cdw-res-facts {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 4px var(--space-4);
  margin: 0;
  font-size: var(--text-sm);
}
.cdw-res-facts dt {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  align-self: center;
}
.cdw-res-facts dd { margin: 0; display: flex; align-items: center; gap: var(--space-2); }
/*
 * Wraps, because the right half is a repository path and the left is a label — at a narrow width
 * they belong on two lines rather than forcing the dialog to the width of both.
 */
.cdw-res-manifest-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  flex-wrap: wrap;
  min-width: 0;
  overflow-wrap: anywhere;
}
.cdw-res-manifest {
  margin: 0;
  padding: var(--space-3);
  font-size: 11px;
  line-height: 1.6;
  background: var(--bg-sunken);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-sm);
  overflow: auto;
  max-height: 20rem;
}

/*
 * Narrow: the row stacks, and it keeps every field.
 *
 * `.cdw-approw-cell { display: none }` used to be the whole of this block, which meant a phone was
 * shown a deployment's name and its two pills and **not which cluster or namespace it writes to**.
 * That is the one fact somebody checking a rollout from a phone is checking, and it was the fact
 * removed. There is room for it: at 900px a row is one column, and a label beside its value is
 * three short lines.
 */
@media (max-width: 900px) {
  .cdw-argo { grid-template-columns: minmax(0, 1fr); }
  .cdw-approw { grid-template-columns: minmax(0, 1fr); gap: var(--space-2); }
  .cdw-approw-cell { display: flex; align-items: baseline; gap: var(--space-2); }
  /* A fixed measure so the three values line up with each other down the stacked row. */
  .cdw-approw-cell .cdw-cell-label { flex: none; width: 5.5rem; }
  .cdw-approw-badges { justify-content: flex-start; }
}

/* ------------------------------------------------------------------ create or open, on the index */

.cdw-choose {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
  gap: var(--space-3);
}
.cdw-choose-card {
  display: grid;
  gap: var(--space-2);
  align-content: start;
  padding: var(--space-4);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-card);
  background: color-mix(in srgb, var(--bg-surface) 94%, transparent);
}
.cdw-choose-card.is-on { border-color: var(--border-strong); }
.cdw-choose-card h2 { margin: 0; font-size: var(--text-md); font-weight: 600; color: var(--text-primary); }
.cdw-choose-card p { margin: 0; }
.cdw-choose-card > .btn { width: -moz-fit-content; width: fit-content; }
.cdw-create { display: grid; gap: var(--space-3); }
.cdw-create-row { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); gap: var(--space-3); }
/* `--danger-text` was never a token in this stylesheet, so this sentence used to inherit its colour. */
.cdw-create-error { margin: 0; font-size: var(--text-xs); color: var(--critical-text); }
.cdw-create-actions { display: flex; align-items: center; gap: var(--space-2); }

/* ------------------------------------------------------------------ the index at /deploy */

/*
 * One column, pinned to the container — `minmax(0, 1fr)`, never the implicit `auto`.
 *
 * An `auto` track is sized by its content, so the widest child decided how wide this page was and
 * every other child was stretched to match it. On the index that child was the folded opener: the
 * verdict sentence is one `nowrap` line with an ellipsis waiting to be used, and because the track
 * grew to fit it instead, the ellipsis never fired and the strip of steps and the two doors were
 * laid out 433px wide inside a 364px page. `.page-region` is `overflow-x: clip`, so the 69px that
 * did not fit was not scrolled to — it was cut off, with no scrollbar to say so.
 *
 * With the track pinned, the opener ellipsises as its own rule always intended and the page fits.
 */
.cdw-index { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-4); min-width: 0; }
.cdw-index-name {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--text-primary);
  text-decoration: none;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 600;
}
.cdw-index-name:hover { text-decoration: none; }
.cdw-index-name:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; border-radius: var(--radius-sm); }
.cdw-index-commit { display: grid; gap: 2px; min-width: 0; }
.cdw-index-msg { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 28rem; }
.cdw-index-deps { display: inline-flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }

/*
 * The row's delete control, and why it is dimmed rather than hidden.
 *
 * `display: none` until hover is the usual way to keep a table quiet, and it removes the control
 * from the keyboard entirely — a button only a mouse can reach is not a button. This stays in the
 * layout and in the tab order at reduced contrast, and comes to full strength on row hover, on row
 * focus, and on its own focus. Destructive colour is held back until the pointer is on the button
 * itself, so a row that is merely hovered does not read as a row about to be deleted.
 */
.cdw-index-actions { display: flex; justify-content: flex-end; }
.cdw-index-del {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  opacity: 0.4;
  transition: opacity 120ms ease, color 120ms ease, background-color 120ms ease, border-color 120ms ease;
}
tr:hover .cdw-index-del,
tr:focus-within .cdw-index-del { opacity: 1; }
.cdw-index-del:hover {
  color: var(--critical-text);
  background: var(--critical-soft);
  border-color: var(--critical);
}
.cdw-index-del:focus-visible {
  opacity: 1;
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  .cdw-index-del { transition: none; }
}

/* The delete confirmation's body. Same rhythm as .cdw-sync so the two gates read as one pattern. */
.cdw-del { display: grid; gap: var(--space-4); min-width: 0; }
.cdw-del-list { margin: var(--space-2) 0 0; padding-left: var(--space-4); display: grid; gap: var(--space-2); }
.cdw-del-confirm { display: grid; gap: var(--space-2); }

/* ------------------------------------------------------------------ the guided strip on the index */

/*
 * Four steps in a row, the current one lit. Derived from what exists (`progressOf`) and gone when
 * the fourth is done, so it never has to be dismissed and never claims a step that is not.
 */
.cdw-steps {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: none;
  /*
   * A grid item's automatic minimum size is its own min-content, so without this the strip refused
   * to go narrower than two steps side by side and simply grew past the column it sits in. Measured
   * at a 430px viewport: 433px of strip inside a 364px page. `.page-region` is `overflow-x: clip`,
   * so that 69px was not scrolled to — it was cut off, silently, with no scrollbar to suggest
   * anything was missing. Clipping is the right default for the region; the fix belongs here.
   */
  min-width: 0;
}
.cdw-step {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-card);
  background: color-mix(in srgb, var(--bg-surface) 94%, transparent);
  min-width: 0;
}
.cdw-step.is-current { border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent); }
.cdw-step.is-later { opacity: 0.6; }
.cdw-step-num {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  font-size: 11px;
  font-weight: 600;
  color: var(--text-secondary);
  background: var(--bg-sunken);
}
.cdw-step.is-done .cdw-step-num { color: var(--ok-text); background: var(--ok-soft); }
.cdw-step.is-current .cdw-step-num { color: var(--accent-text); background: color-mix(in srgb, var(--accent) 16%, transparent); }
.cdw-step-body { display: grid; gap: 2px; min-width: 0; }
.cdw-step-title { font-size: var(--text-sm); font-weight: 600; color: var(--text-primary); }
.cdw-step-text { font-size: var(--text-xs); color: var(--text-muted); }
.cdw-step-act {
  margin-top: var(--space-1);
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--accent-text);
  text-decoration: underline;
  cursor: pointer;
  text-align: left;
  width: -moz-fit-content;
  width: fit-content;
}
.cdw-step-act:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; border-radius: var(--radius-sm); }

/* A badge that is also a link — the awaiting-approval count, which is a place to go. */
.cdw-await-link { text-decoration: none; }
.cdw-await-link:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; border-radius: var(--radius-sm); }

/* ------------------------------------------------------------------ the history at /deploy/<repo>/commits */

.cdw-commits { display: grid; gap: var(--space-4); min-width: 0; }
.cdw-commits-target { flex: 0 1 26rem; min-width: 0; }
.cdw-commit-list { margin: 0; padding: 0; list-style: none; display: grid; gap: var(--space-2); }
.cdw-commit-list.is-compact { gap: var(--space-1); }
.cdw-commit {
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-card);
  background: color-mix(in srgb, var(--bg-surface) 94%, transparent);
  min-width: 0;
}
.cdw-commit.is-open { border-color: color-mix(in srgb, var(--accent) 45%, var(--border-hairline)); }
.cdw-commit.is-here { border-color: color-mix(in srgb, var(--ok) 45%, var(--border-hairline)); }
.cdw-commit-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--space-3);
  align-items: center;
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: default;
}
button.cdw-commit-row { cursor: pointer; }
button.cdw-commit-row:hover { background: var(--bg-hover); }
button.cdw-commit-row:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; border-radius: var(--radius-card); }
.cdw-commit-sha { font-size: 12px; color: var(--accent-text); }
.cdw-commit-main { display: grid; gap: 2px; min-width: 0; }
.cdw-commit-msg { font-size: var(--text-sm); color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cdw-commit-badges { display: inline-flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; justify-content: flex-end; }
.cdw-commit-dep {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-secondary);
}
.cdw-commit-detail {
  display: grid;
  gap: var(--space-3);
  padding: var(--space-3);
  border-top: 1px solid var(--border-hairline);
}
.cdw-sync-files { display: grid; gap: var(--space-2); }
.cdw-sync-files summary { cursor: pointer; }
.cdw-sync-files summary:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; border-radius: var(--radius-sm); }

/* ------------------------------------------------------------------ one deployment: history & rollback */


.cdw-history {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(22rem, 1fr));
  gap: var(--space-4);
  min-width: 0;
}
.cdw-history-col { display: grid; gap: var(--space-3); align-content: start; min-width: 0; }
.cdw-history-col h2 { margin: 0; }
.cdw-sync-list { margin: 0; padding: 0; list-style: none; display: grid; gap: var(--space-2); }
.cdw-sync-row {
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-card);
  background: color-mix(in srgb, var(--bg-surface) 94%, transparent);
  min-width: 0;
}
.cdw-sync-row.is-failed { border-color: color-mix(in srgb, var(--critical) 45%, var(--border-hairline)); }
.cdw-sync-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.cdw-sync-head:hover { background: var(--bg-hover); }
.cdw-sync-head:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; border-radius: var(--radius-card); }
.cdw-sync-body { display: grid; gap: var(--space-2); padding: var(--space-3); border-top: 1px solid var(--border-hairline); }
.cdw-sync-body p { margin: 0; }
.cdw-step-status { margin-left: auto; text-decoration: none; }
.cdw-step-status:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; border-radius: var(--radius-sm); }

@media (max-width: 900px) {
  .cdw-steps { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Two steps need 432px of content box between them, which a phone does not have. */
@media (max-width: 560px) {
  .cdw-steps { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 1200px) {
  /* Below this the editor is narrower than a manifest line, which is the one thing it may not be.
     The rail moves under it; the tree stays, because navigation is what a narrow screen still needs
     beside the code. */
  .cdw-panes { grid-template-columns: 200px minmax(0, 1fr); }
  .cdw-right { grid-column: 1 / -1; border-left: none; border-top: 1px solid var(--border-hairline); }
  .cdw { height: auto; }
}

/* ------------------------------------------------------------------ the Overview's exit row

   Eight destinations that used to be eight inlined panels. See `apps/portal/app/overview-more.tsx`
   for why they left; what matters here is that this strip must not compete with the board above it.

   So: no raised surface, no hero figure, no hue in the chrome. The figures sit at body size and take
   a status colour only when somebody has to act on them. A strip where every row is amber ranks
   nothing, which is the failure this whole change is undoing one level up. */
.ov-exits {
  margin-top: var(--space-8);
}
.ov-exits-heading {
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin: 0;
}
.ov-exits-note {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  margin: var(--space-1) 0 var(--space-3);
}
.ov-exits-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--space-2);
}

/* The row is the link, so the whole target is clickable rather than the label alone. */
.ov-exit {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas:
    "label figure"
    "answers figure";
  align-items: center;
  -moz-column-gap: var(--space-3);
       column-gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--bg-surface);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-card);
  color: var(--color-text);
  text-decoration: none;
  transition: border-color 140ms var(--ease-out), background 140ms var(--ease-out);
}
.ov-exit:hover,
.ov-exit:focus-visible {
  border-color: var(--border-strong);
  background: var(--bg-hover);
}
.ov-exit:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}
.ov-exit-label {
  grid-area: label;
  font-size: var(--text-base);
  font-weight: 600;
  line-height: 1.3;
}
.ov-exit-answers {
  grid-area: answers;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  line-height: 1.4;
}
/* Right-aligned and tabular so eight rows of figures form a readable column rather than a ragged
   edge. `--text-sm`, not a hero step: this strip is demoted on purpose. */
.ov-exit-figure {
  grid-area: figure;
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--color-text-muted);
  white-space: nowrap;
}
.ov-exit-figure--warning { color: var(--severity-warning-fg); }
.ov-exit-figure--critical { color: var(--severity-critical-fg); }
/* Reserved with no text, so a row that counts nothing keeps the column's alignment instead of
   letting its neighbour's figure slide into the gap. */
.ov-exit-figure--none::after {
  content: "";
}

/* The risk row's door to the console. Same register as `.ov-watch-more`, the band's own link:
   a quiet text link rather than a button, because twenty rows of buttons would out-shout the
   scores they sit under. Anchored beside the cause, not in the head — the head is a <button>. */
.risk-open {
  display: inline-block;
  margin-top: var(--space-1);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  text-decoration: none;
}
.risk-open:hover,
.risk-open:focus-visible {
  color: var(--color-text);
  text-decoration: none;
}
.risk-open:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

/* The band's link inherits a link-blue in the light theme that measures 2.70:1 on the band's own
   ground — and since the review beneath it is clamped, this link is now the door to the rest of
   it. Scoped to the band so it wins the cascade against the generic anchor rule, and given the
   band's text colour (16.36:1 light, 15.25:1 dark, measured on this ground). Hover underlines,
   the same quiet-link register as `.risk-open`. */
.ov-watch-body .ov-watch-more {
  color: var(--color-text);
  text-decoration: none;
}
.ov-watch-body .ov-watch-more:hover,
.ov-watch-body .ov-watch-more:focus-visible {
  text-decoration: none;
}

/* The band's open/close control. A quiet text button in the same register as `.ov-watch-more`
   beside it — button chrome reset so it reads as a word, not a control competing with the head. */
.ov-watch-toggle {
  margin-left: auto;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: var(--text-xs);
  font-weight: 500;
  color: var(--color-text);
  cursor: pointer;
  white-space: nowrap;
}
.ov-watch-toggle:hover,
.ov-watch-toggle:focus-visible {
  text-decoration: none;
}
.ov-watch-toggle:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}
/* With the toggle taking `margin-left: auto`, the link beside it only needs a gap. */
.ov-watch-toggle + .ov-watch-more {
  margin-left: var(--space-4);
}

/* NeedsYou, closed: one bar. Count, chips, and the way in — nothing else, on one line. */
/*
 * The collapsed triage band, and the whole band is one button.
 *
 * It held two of them - the counts and a "Show" - both calling the same handler, which is the
 * duplicate-control defect the expanded rows already had removed. As one control the hit area is
 * the band, the accessible name is the sentence a reader would say ("8 things need you, 3 need you
 * now..."), and the chevron at the end is a mark rather than a second target.
 */
.needsyou-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-4) var(--space-5);
  background: var(--bg-surface);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-card);
  margin-bottom: var(--space-4);
  /* It is a `button` now: none of this is inherited. */
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  transition:
    background var(--motion-fast) var(--ease-out),
    border-color var(--motion-fast) var(--ease-out);
}
.needsyou-bar:hover {
  background: var(--bg-hover);
  border-color: var(--border-strong);
}
.needsyou-bar:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}
.needsyou-bar-title {
  font-size: var(--text-base);
  font-weight: 600;
  white-space: nowrap;
}
/* The counts keep the pills' own spacing and add nothing of their own. */
.needsyou-bar-counts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}
/*
 * The band's own right rule, shared with the refresh control above it and the utilisation rail
 * below - a page whose right-hanging marks sit in one column rather than four.
 */
.needsyou-bar-cta {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-left: auto;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  white-space: nowrap;
}
.needsyou-bar:hover .needsyou-bar-cta { color: var(--color-text); }
/* The chevron follows the row on hover, so the whole band reads as one target rather than as a
   caption with a control at the end of it. */
.needsyou-bar:hover .needsyou-chevron { transform: rotate(45deg) translate(1px, -1px); }

/*
 * One severity's count: figure promoted, words demoted, ground tinted to the tier.
 *
 * These replaced three `Badge`s. A badge sets its whole content at caption weight and the same
 * size, so "2" and "need you now" competed and the count — the only part anyone triages on — had to
 * be read out of a phrase.
 *
 * The tint is deliberately weak. Three saturated blocks in a row is a strip that reads as three
 * warnings, which is the opposite of a triage order; the dot beside each figure carries the same
 * hue at full strength so the tier survives the ground being quiet. That also covers the reader for
 * whom a 12% amber and a 12% red are the same wash.
 */
.needsyou-pill {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  padding: 3px 10px 4px;
  border: 1px solid transparent;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--nu-tone) 12%, transparent);
  border-color: color-mix(in srgb, var(--nu-tone) 24%, transparent);
  white-space: nowrap;
  min-width: 0;
}
.needsyou-pill--critical { --nu-tone: var(--critical); }
.needsyou-pill--warning { --nu-tone: var(--warn); }
.needsyou-pill--info { --nu-tone: var(--info); }

.needsyou-pill-dot {
  align-self: center;
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  /* The tier at full strength, over a ground carrying it at 12%. The severity classes are shared
     with `.needsyou-dot`, so the two marks for one tier cannot drift to two colours. */
  background: var(--nu-tone);
}
.needsyou-pill-dot.sev-critical { background: var(--critical); }
.needsyou-pill-dot.sev-warning { background: var(--warn); }
.needsyou-pill-dot.sev-info { background: var(--info); }
.needsyou-pill-num {
  font-size: var(--text-md);
  font-weight: 650;
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--color-text);
}
.needsyou-pill-word {
  font-size: var(--text-sm);
  line-height: 1.2;
  color: var(--color-text-muted);
}

/* ---------------------------------------------------------------------------
   Monitoring groups, and the shift handover.

   Two surfaces added together because they are two halves of the same idea: a group decides which
   clusters a person is shown, and the handover is the first thing they are shown. Both are about
   people rather than machines, which is why neither borrows the dense operational register the
   boards use — they are read once at the start of a shift, not scanned continuously.
   --------------------------------------------------------------------------- */

/* A checkbox grid rather than a multi-select: the unselected options have to stay visible, because
   assigning the wrong cluster narrows somebody's console and nothing tells them. */
.groups-picker {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: var(--space-1);
  max-height: 220px;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--bg-sunken);
}

.groups-option {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  cursor: pointer;
}
.groups-option:hover { background: var(--color-hover); }
.groups-option-sub { color: var(--text-muted); font-size: var(--text-xs); }

.groups-picker-empty,
.groups-empty {
  padding: var(--space-4);
  color: var(--text-muted);
  font-size: var(--text-sm);
  line-height: var(--leading-body);
}

.groups-row-sub { color: var(--text-muted); font-size: var(--text-xs); margin-top: 2px; }

.groups-chips { display: flex; flex-wrap: wrap; gap: var(--space-1); }
.groups-actions { display: flex; gap: var(--space-2); justify-content: flex-end; }

.groups-scope-list {
  margin: 0;
  padding-left: var(--space-5);
  display: grid;
  gap: var(--space-1);
  font-size: var(--text-sm);
  color: var(--color-text);
}

.groups-scope-note {
  margin-top: var(--space-3);
  font-size: var(--text-xs);
  line-height: var(--leading-relaxed);
  color: var(--text-muted);
  max-width: 76ch;
}

/* ------------------------------------------------------------ shift handover */

/*
 * The shift welcome, on one line where it fits.
 *
 * It was three stacked blocks in a space-5 card — greeting, state, and the "since you were here"
 * column — and took roughly a fifth of the first screen to say two sentences. On an overview whose
 * job is the estate, the greeting is the least important thing on it and was the largest.
 *
 * So the greeting and the state sit on one baseline and wrap only when they must, and the card is
 * padded like a header rather than like a hero. Nothing is hidden and no sentence is shortened —
 * the same words, at the size they are worth.
 */
/* `--space-5` across, like every other band on a summary page, so the greeting starts on the same
   left rule as the triage bar and the instrument panel and the footnote ends on the same right one.
   Three bands at three insets (16 / 20 / 16) is what read as the page being out of true. */
/*
 * A tinted surface rather than another white card.
 *
 * The greeting is the page's one warm object and it sat in the same white box as everything under
 * it, so it read as the first row of the dashboard instead of as the thing addressed to the person
 * reading. The wash is `--info` at 6% and fades out by 46% — enough to separate the band from the
 * cards below without becoming a coloured panel behind text, which costs contrast the severity
 * tiers are held to a floor on.
 */
.shift {
  padding: var(--space-4) var(--space-5);
  /*
   * A flat tint, and it was a gradient for about an hour.
   *
   * `tokens.test.ts` refuses a gradient anywhere outside the loading shimmer and two named
   * signed-out surfaces, on the grounds that a gradient behind a measurement is decoration
   * competing with a number — and this band carries three measurements now, so it is exactly the
   * surface the rule is about. The flat wash does the whole job the brief asked of it: it separates
   * the greeting from the white cards under it without becoming a coloured panel behind text.
   */
  background: color-mix(in srgb, var(--info) 5%, transparent);
}

/*
 * What is outstanding, as figures rather than as a sentence.
 *
 * The prose form put "63" in the middle of a clause, so the one part of the line a reader acts on
 * had to be found before it could be read. These promote the count and demote the words qualifying
 * it — the same argument the instrument tiles below make, applied to the band above them.
 *
 * `baseline`, so three figures of different digit counts sit on one rule with the greeting beside
 * them. The separator is drawn by the rule rather than written into the markup, because any of the
 * three can be absent.
 */
.shift-figures {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-4);
  min-width: 0;
}
.shift-figure {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
}
.shift-figure + .shift-figure::before {
  content: "";
  align-self: center;
  width: 3px;
  height: 3px;
  margin-right: var(--space-2);
  border-radius: 50%;
  background: var(--border-strong);
}
.shift-figure-num {
  font-size: var(--text-xl);
  font-weight: 650;
  line-height: 1;
  letter-spacing: -0.02em;
}
.shift-figure-word {
  font-size: var(--text-base);
  line-height: var(--leading-body);
  color: var(--color-text-muted);
}
/* Colour on the figure alone. The words stay in the body register — a whole row set in red is a
   row that cannot say which of the three things is the urgent one. */
.shift-figure--critical .shift-figure-num { color: var(--severity-critical-fg); }
.shift-figure--warning .shift-figure-num { color: var(--severity-warning-fg); }
.shift-figure--info .shift-figure-num { color: var(--color-text); }

/*
 * Stacked below the phone breakpoint, and the separators go with the row.
 *
 * Wrapping alone was not enough: `+ ::before` draws a dot before every figure but the first, so a
 * row that wraps to three lines puts a leading bullet on lines two and three — a separator between
 * two things that are no longer beside each other, which reads as a list marker nobody asked for.
 * Stacking explicitly is also the better reading at this width: three figures under a greeting are
 * a short list, and a list is what they become.
 */
@media (max-width: 640px) {
  .shift-figures {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-1);
  }
  .shift-figure + .shift-figure::before {
    display: none;
  }
  /* The greeting is `nowrap` so it stays on one line beside the figures at desktop widths. Below
     this there is nothing beside it, and a name long enough to exceed the card would otherwise
     push the whole page sideways. */
  .shift-greeting {
    white-space: normal;
  }
}

.shift-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
}

/* Row, not column: the greeting is a phrase and the state is a sentence, and they read as one line
   until the width runs out. `baseline` so the two sizes sit on the same rule rather than centring
   the smaller one against the larger. */
.shift-hello {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-3);
  min-width: 0;
  flex: 1 1 32rem;
}

/* The one line in the product allowed to be warm. Larger than the sentence beside it and lighter
   than a hero figure: this is a greeting, not a measurement. */
.shift-greeting {
  font-family: var(--font-sans);
  font-size: var(--text-xl);
  line-height: var(--leading-heading);
  letter-spacing: -0.01em;
  color: var(--color-text);
  white-space: nowrap;
}

/* Written separately from the greeting, and never merged into it: a warm hello must not end up
   asserting that a bad morning is a good one. Beside it, not under it. */
.shift-state {
  margin-top: 0;
  font-size: var(--text-base);
  line-height: var(--leading-body);
  color: var(--text-secondary);
  max-width: 78ch;
}

/*
 * Two facts about the last shift, and they are footnotes. Kept on one row while there is width for
 * it, with a separator drawn by the rule below rather than by a character in the markup — the two
 * spans are written by different conditions and either can be absent.
 */
.shift-since {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: baseline;
  gap: 2px var(--space-2);
  text-align: right;
  font-size: var(--text-xs);
  color: var(--text-muted);
}
.shift-since > span + span::before {
  content: "·";
  margin-right: var(--space-2);
  color: var(--border-strong);
}
.shift-who { color: var(--text-secondary); }

.shift-dark {
  margin-top: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  background: var(--severity-warning-bg, var(--warn-soft));
  color: var(--severity-warning-fg, var(--warn-text));
  font-size: var(--text-sm);
  line-height: var(--leading-body);
}

/*
 * The ledger's own styles — the two columns, the event list, the notes and the composer — were here
 * and went with the markup that used them. Kept out rather than left behind: a stylesheet that
 * describes components the product no longer renders is the CSS half of dead code, and the class
 * scan in `apps/portal/app/alert-surface.test.ts` only checks the direction that catches a missing
 * rule, never an orphaned one.
 */

/* What is outstanding on a cluster card, named rather than left as a subtraction of the bars below.
   Chips, not a sentence: the list is 0-5 short facts and a reader scans for whether there are any
   at all before reading which. Status colour only — the chrome stays grey, per the design blacklist. */
.cc-pending {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
}
/*
 * The chips carry their own severity now.
 *
 * The row held three items and all three meant "something to look at", so one amber style was
 * honest. It now also holds critical alerts, failed pods and failed changes beside "12 updates
 * available", and rendering those identically is the product flattening its findings into one shade
 * of concern — which is how a colour stops carrying information.
 *
 * Warning stays the base, so a chip added without a tone lands on the safer of the two rather than
 * claiming to be critical.
 */
.cc-pending-item {
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--severity-warning-bg);
  color: var(--severity-warning-fg);
  font-size: var(--text-xs);
  font-weight: 500;
  white-space: nowrap;
}

.cc-pending-critical {
  background: var(--severity-critical-bg);
  color: var(--severity-critical-fg);
}
/* "Nothing pending" and "cannot tell" are both absences, and neither is a warning. Muted rather
   than green: a green chip would read as a positive finding, and the second one is not one. */
.cc-pending-none {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

/* ---------------------------------------------------------------------------
   The cluster card's drill-down panel.

   A chip on the card is a count; this is what it counted. Deliberately plain — the panel's whole job
   is a scannable list of names and reasons, and every row already competes with the one above it.
   --------------------------------------------------------------------------- */

/* Chips that open something look pressable. The ones that do not stay `<span>`s and keep the flat
   look, so the difference is visible before the click rather than discovered by it. */
/*
 * A chip that does something looks like it does something, at rest.
 *
 * Everything else in this rule is a `<button>` reset — the four declarations below exist to make a
 * button look like the `<span>` beside it. That left the row's own stated contract unmet: the chips
 * with nothing behind them stay spans rather than becoming disabled buttons *so that the difference
 * is visible before the click*, and it was not visible at all. `cursor: pointer` appears on hover,
 * which is after the reader has already decided to click; the rest are invisible. A reader who
 * clicked "12 updates available" — styled identically to the eight actionable chips around it —
 * got nothing, and had no way to know beforehand.
 *
 * An inset shadow rather than a border, for the reason the tab indicator gives: it paints inside
 * the border box, so it costs no layout and cannot shift a row of chips by a pixel. `currentColor`
 * so it follows each chip's own severity instead of adding a third colour to a row that is already
 * carrying two.
 */
.cc-pending-open {
  border: none;
  font-family: var(--font-sans);
  cursor: pointer;
  transition: filter var(--motion-fast) var(--ease-out), box-shadow var(--motion-fast) var(--ease-out);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 30%, transparent);
}
.cc-pending-open:hover,
.cc-pending-open:focus-visible {
  filter: brightness(0.94);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 55%, transparent);
}

/*
 * The chips that navigate are anchors, and the global `a:hover` is wrong for them twice over.
 *
 * It adds an underline — a chip is a shape, and underlining the text inside one reads as a link
 * that has escaped its pill. And it repaints the text `--hover-accent-text`, which would take a
 * critical chip's red away at the exact moment the pointer is on it: the colour is the severity, so
 * losing it on hover loses the finding.
 *
 * Both need to outrank `a:hover` (0,1,1), which is why the tone class is named alongside the
 * element rather than left to `.cc-pending-critical` (0,1,0) — that loses to it.
 */
a.cc-pending-open,
a.cc-pending-open:hover,
a.cc-pending-open:focus-visible {
  text-decoration: none;
}
a.cc-pending-critical:hover,
a.cc-pending-critical:focus-visible {
  color: var(--severity-critical-fg);
}
a.cc-pending-warning:hover,
a.cc-pending-warning:focus-visible {
  color: var(--severity-warning-fg);
}

/* ----------------------------------------------------------------------------
   The drill-down panel behind a cluster card's failure count
   ----------------------------------------------------------------------------

   A reader clicks "9 pods failed" and gets the nine. What they do with that list is
   scan it: find the one they recognise, read why, and leave for the page that can
   do something about it. Every rule below serves scanning, and the shape it
   replaced did not.

   It was a flex row with `align-items: baseline`, a body holding
   `namespace/name` in one string, and the word "Open" as an underlined link. Three
   things went wrong with that, all visible in a screenshot of the operators panel:

     - **Nothing lined up down the list.** `namespace/name` puts the repeating half
       of the string first, so `prometheus-k8s-0` and `image-pruner-…` began at
       different columns forty rows apart and the eye had no edge to run down. The
       namespace is now its own dimmed line and every name starts at the same x.
     - **`baseline` is not an alignment for a row whose two sides have different
       numbers of lines.** It pins the action to the first line box of a body that
       may be four lines tall, and moves whenever the body's first line wraps. The
       grid below assigns each cell a column and starts them all at the top, so the
       action sits still.
     - **The detail ran the full 860px of a wide dialog.** The monitoring operator's
       message is four lines of dense prose at that measure and a wall at any width;
       it is capped at a readable measure and given the line-height to go with it.

   The timestamp column is `cc-panel-list-timed` rather than always-on: four of the
   nine kinds have no honest time to show (see `ProblemItem.when`), and a reserved
   column those lists cannot fill is 132px of dead space beside every row. The class
   goes on the list, not the row, so the column is the same width in every row of
   the list it does appear in — which is the entire point of a column.
   ---------------------------------------------------------------------------- */

.cc-panel-count {
  margin: 0 0 var(--space-3);
  padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--color-border);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

.cc-panel-empty {
  font-size: var(--text-sm);
  line-height: var(--leading-relaxed);
  color: var(--text-muted);
  max-width: 60ch;
}

.cc-panel-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.cc-panel-item {
  display: grid;
  /* Body, then the action. The middle track appears only on a timed list, below. */
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: start;
  -moz-column-gap: var(--space-4);
       column-gap: var(--space-4);
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--bg-sunken);
  transition:
    border-color var(--motion-fast) var(--ease-out),
    background var(--motion-fast) var(--ease-out);
}

/* The row is not clickable — the button in it is — so the hover is a hairline and
   not a fill: enough to hold the reader's place down a fifty-row list, not enough
   to promise a click the row does not answer. */
.cc-panel-item:hover {
  border-color: var(--color-border-strong);
  /* Mixed rather than `--bg-hover`, which is the same value as `--bg-sunken` in the dark
     palette — a hover state that resolves to the resting background in the default theme
     is a hover state only the light theme has. */
  background: color-mix(in srgb, var(--color-text) 5%, var(--bg-sunken));
}

.cc-panel-list-timed .cc-panel-item {
  grid-template-columns: minmax(0, 1fr) 132px auto;
}

/* Explicit tracks, so a row whose `when` is absent from a list that otherwise has
   them does not slide its action into the timestamp's column. `-2 / -1` is the last
   track whichever template is in force. */
.cc-panel-body {
  grid-column: 1;
  min-width: 0;
}
.cc-panel-when {
  grid-column: 2;
}
.cc-panel-action {
  grid-column: -2 / -1;
}

/* The namespace, above the name and out of its way.
   Mono to match the name it qualifies — the two are one path, read together — and
   muted because it is the half that repeats. */
.cc-panel-context {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 16px;
  color: var(--text-muted);
  overflow-wrap: anywhere;
}

/* Mono, because these are object names the reader will compare against `oc get` output — and a
   proportional face makes two similar pod names hard to tell apart at a glance. */
.cc-panel-title {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  /* Fixed rather than inherited: this line sets where the action beside it sits, and
     a line-height that changes with the body's makes that alignment a coincidence. */
  line-height: 20px;
  font-weight: 500;
  color: var(--color-text);
  overflow-wrap: anywhere;
}

.cc-panel-detail {
  margin-top: var(--space-1);
  /* A measure, not the width of the dialog. The monitoring operator's message is four
     sentences of `context deadline exceeded` and reads as one block at full width. */
  max-width: 86ch;
  font-size: var(--text-xs);
  line-height: var(--leading-relaxed);
  color: var(--text-muted);
  overflow-wrap: anywhere;
}

/* Right-aligned against the action column, so the times form an edge of their own
   rather than ragging out of the middle of the row. */
.cc-panel-when {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
  text-align: right;
}

.cc-panel-when-label {
  font-size: var(--text-xs);
  line-height: 16px;
  color: var(--text-muted);
}

.cc-panel-when-value {
  font-size: var(--text-xs);
  line-height: 16px;
  color: var(--color-text);
  /* Tabular, so "4 hours ago" and "11 hours ago" do not shuffle the column under a
     poll that rewrites them every thirty seconds. */
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.cc-panel-action {
  display: flex;
  justify-content: flex-end;
  /* Centred against the row, not pinned to its top.
     `.cc-panel-item` sets `align-items: start` so the body's three lines stack from the top, which
     is right for text and wrong for the one control beside it: on a row carrying a context line, a
     name and a two-line detail, the button sat level with the first line and read as belonging to
     it rather than to the row. `align-self` overrides that for this cell alone, so the text keeps
     the alignment it needs. */
  align-self: center;
}

/* A bordered control rather than an underlined word.
   The panel's footer holds a real `Button`; a bare link beside it read as body text
   that happened to be blue, and it was the only thing in the row that does anything. */
.cc-panel-go {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 32, not 24. This is the only thing in the row that does anything, and at 24px with 11px type it
     was the smallest control on the panel — under every tap-target guideline there is, and visually
     subordinate to the text it acts on. 32px with 12px type reads as a button at a glance and still
     fits a row whose body is a single line. */
  min-height: 32px;
  padding: 0 var(--space-4);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--color-text);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition:
    background var(--motion-fast) var(--ease-out),
    border-color var(--motion-fast) var(--ease-out);
}
.cc-panel-go:hover,
.cc-panel-go:focus-visible {
  background: var(--color-surface-raised);
  border-color: var(--color-text);
  color: var(--color-text);
}

/* Narrow: the timestamp gives up its column and becomes a line under the detail.
   Three columns in 400px leaves the body about twenty characters wide, which turns
   every pod name into a five-line stack. */
@media (max-width: 680px) {
  .cc-panel-list-timed .cc-panel-item {
    grid-template-columns: minmax(0, 1fr) auto;
  }
  /* Rows are pinned rather than left to auto-placement: the timestamp taking a whole
     row of its own otherwise pushes the action down to a third row, and the control
     ends up below the thing it acts on instead of beside it. */
  .cc-panel-body,
  .cc-panel-action { grid-row: 1; }
  .cc-panel-when {
    grid-row: 2;
    grid-column: 1 / -1;
    flex-direction: row;
    align-items: baseline;
    justify-content: flex-start;
    gap: var(--space-1);
    margin-top: var(--space-2);
    text-align: left;
  }
}

/* ============================================================================
   Objective flow — what was promised, what measures it, and where that stops
   ============================================================================

   The objectives page used to be a table, and a table is the wrong shape for the
   question a reader arrives with. "99.5% / 28d / 0.4x / 12.1d" is four numbers in
   a row with no statement of how they are connected, and the connection is the
   whole point: a subject carries a promise, the promise is measured by a signal,
   and the signal buys a budget. Three of those four can exist without the third,
   and when the signal is missing the number at the end is not a measurement — it
   is a default. A row cannot show a chain stopping. A flow can.

   So each objective is drawn as four plates on one track, left to right, and the
   track is cut with a buffer stop where nothing measures it. That is the same
   vocabulary as the application flow board (`.appflow-*`) and deliberately so —
   a reader who has learned that a severed edge with a bar across it means
   "nothing arrives here" should not have to learn a second dialect one page over.

   ## The 3D is two rects and one line, not a shadow

   `tokens.test.ts` bans `box-shadow` for in-page separation and it is right to.
   The extrusion here is geometric instead: a riser rect offset three pixels right
   and four down, the face rect over it, and a 1px gloss line just inside the top
   edge. Offset on two axes rather than only downward — a shadow underneath alone
   reads as a card with a heavy bottom border, and it is the sliver on two
   adjacent sides that makes the eye resolve a solid slab lit from the top left.
   It reads at any zoom and costs no blur.

   The riser is neutral rather than tinted by the plate's own tone. That was
   tried, measured in both themes, and abandoned; the rule on `.sloflow-riser`
   records what failed and why. */

.sloflow {
  overflow-x: auto;
  /* Contained on x, per scroll.test.ts: nothing here intends to move sideways, so
     a board scrolled to its right edge must not hand the rest of the wheel to the
     page and slide the whole layout. */
  overscroll-behavior-x: contain;
  margin-top: var(--space-3);
  padding-bottom: var(--space-2);
}

.sloflow svg {
  display: block;
  /* Never below the width the four lanes were laid out at. Squeezing the track
     shorter does not make it readable, it makes the labels collide; the scroll
     container above is the answer on a narrow screen. */
  min-width: 880px;
}

.sloflow-lane-label {
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  fill: var(--color-text-subtle);
}

.sloflow-lane-rule {
  stroke: var(--color-border);
  stroke-width: 1;
}

/*
 * The shadow the plate casts. Neutral, and derived from the page ground.
 *
 * Two tinted versions were tried against the harness and measured, and both failed in one theme:
 *
 *  - mixed toward `--bg-page`, the riser came out *lighter* than the white surface in light mode —
 *    a halo, not a shadow, and faint enough at 38% to read as a rendering seam;
 *  - mixed toward `--color-border-strong`, it failed the other way, because in the dark theme that
 *    token (#5C5C64) is lighter than the surface it would sit under (#101010).
 *
 * The reason no tint survives is `--accent`, which is #5B8DEF in light and **#F0F0F1 in dark**. Any
 * mix carrying a meaningful share of a near-white tone lands above the surface, so a tone-tinted
 * shadow cannot be darker than its own plate in both themes — the property that makes it read as
 * depth at all. Darkening the page ground works in both, because the ground is by definition behind
 * everything: #ABADAF under a white plate, #070707 under a dark one.
 *
 * The tone is not lost; it is on the face's stroke, where it is meant to be read.
 */
.sloflow-riser {
  fill: color-mix(in srgb, var(--bg-page) 72%, #000000);
  stroke: none;
}

.sloflow-face {
  fill: var(--color-surface);
  stroke: var(--color-border);
  stroke-width: 1;
}

/* The tone is carried on the group and read by the face's stroke, and only there.
   It used to feed the riser as well through a custom property; that declaration
   is deleted rather than left in place unread, because a property nothing reads
   is a control that only looks like one. */
/* No state to report, so no colour claiming there is one. */
.sloflow-plate.tone-plain .sloflow-face { stroke: var(--color-border); }
.sloflow-plate.tone-ok .sloflow-face { stroke: var(--ok); }
.sloflow-plate.tone-warn .sloflow-face { stroke: var(--warn); }
.sloflow-plate.tone-critical .sloflow-face { stroke: var(--critical); }
.sloflow-plate.tone-accent .sloflow-face { stroke: var(--accent); }
/* Dashed, and that is the one state carrying meaning rather than emphasis: a
   plate nothing measures is drawn as an outline that is not closed. */
.sloflow-plate.tone-muted .sloflow-face {
  stroke: var(--color-border-strong);
  stroke-dasharray: 3 3;
}

.sloflow-gloss {
  fill: none;
  stroke: color-mix(in srgb, var(--color-surface) 55%, #FFFFFF);
  stroke-width: 1;
}

.sloflow-kind {
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  fill: var(--color-text-subtle);
}

.sloflow-name {
  font-size: 12px;
  font-weight: 600;
  fill: var(--color-text);
}

.sloflow-meta {
  font-size: 10px;
  fill: var(--color-text-muted);
}

.sloflow-num {
  font-size: 16px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  fill: var(--color-text);
}

.sloflow-edge {
  fill: none;
  stroke: var(--color-border-strong);
  stroke-width: 1.5;
}

.sloflow-arrow { fill: var(--color-border-strong); }

/* The chain stopping. Dashed to the buffer, then a bar across the track and no
   arrowhead — the arrowhead is what says "and it continues", so removing it is
   half the statement and drawing the stop is the other half. */
.sloflow-edge.is-stopped {
  stroke: var(--warn);
  stroke-dasharray: 4 4;
}

.sloflow-stop {
  stroke: var(--warn);
  stroke-width: 2.5;
  stroke-linecap: round;
}

/* (0,3,0) — above the tone rules at (0,2,0), so hover wins in every state
   without !important. The pairing is the one `.appflow-hop-box` got wrong once. */
.sloflow-row:hover .sloflow-face { stroke: var(--accent); }
.sloflow-row { cursor: default; }
.sloflow-row.is-clickable { cursor: pointer; }

/* The strip under the diagram: one line per proposal with its own Install. Kept
   out of the SVG because a button inside foreignObject is a focus-order and
   scaling problem for something that is ordinary DOM everywhere else. */
.sloflow-actions {
  display: grid;
  gap: var(--space-3);
  margin-top: var(--space-4);
}

.sloflow-action {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
  padding: var(--space-4);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
}

.sloflow-action-body {
  flex: 1 1 auto;
  min-width: 0;
  display: grid;
  gap: var(--space-2);
}

.sloflow-action-head {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  flex-wrap: wrap;
}

.sloflow-action-title {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text);
}

.sloflow-action-terms {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  overflow-wrap: anywhere;
}

.sloflow-action-why {
  font-size: var(--text-xs);
  line-height: var(--leading-body);
  color: var(--color-text-muted);
  border-left: 2px solid var(--border-hairline);
  padding-left: var(--space-3);
}

/* The signal's own sentence, in the tone of whether there is one. Warning rather
   than critical: an objective nothing measures is not broken, it is unfinished. */
.sloflow-action-signal {
  font-size: var(--text-xs);
  line-height: var(--leading-body);
  color: var(--color-text-muted);
}
.sloflow-action-signal.is-blocked { color: var(--warn-text); }

.sloflow-legend {
  display: flex;
  gap: var(--space-4);
  flex-wrap: wrap;
  align-items: center;
  margin-top: var(--space-3);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

.sloflow-legend-item {
  display: inline-flex;
  gap: var(--space-2);
  align-items: center;
}

.sloflow-legend-swatch {
  width: 18px;
  height: 0;
  border-top: 2px solid var(--color-border-strong);
}
.sloflow-legend-swatch.is-stopped {
  border-top-style: dashed;
  border-top-color: var(--warn);
}

/* The one-gesture accept, above the per-proposal list. The sentence beside it is
   load-bearing: "set all" on a page about promises reads as though it might go
   and do something to the clusters, and it does not. */
.sloflow-bulk {
  display: flex;
  gap: var(--space-3);
  align-items: center;
  flex-wrap: wrap;
  padding: var(--space-4);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  background: var(--bg-sunken);
}

/* ---------------------------------------------------------------------------------------------
 * The assistant's fix suggestions.
 *
 * Deliberately the only block in the answer that reads as a *thing to press* rather than a thing to
 * read: a left rail in the accent, a heavier surface, and the button at full weight. Everything else
 * in the panel — the answer, the tool strip, the evidence drawer — is prose and stays quiet, so the
 * one element that files a change against a customer's cluster should not look like the rest of it.
 *
 * Appended at the end of the file rather than filed beside the other `.sre-*` rules, because those
 * were being edited in another session at the time and a merge in the middle of a 15,000-line
 * stylesheet is worth avoiding for the sake of alphabetical tidiness.
 * ------------------------------------------------------------------------------------------- */

.sre-fixes {
  display: grid;
  gap: var(--space-3);
  margin-top: var(--space-4);
  min-width: 0;
}

.sre-fixes-head {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.sre-fix {
  display: grid;
  gap: var(--space-2);
  min-width: 0;
  padding: var(--space-3);
  padding-left: calc(var(--space-3) - 3px);
  border: 1px solid var(--border-hairline);
  border-left: 3px solid var(--accent-text);
  border-radius: var(--radius-md);
  background: var(--bg-sunken);
}

.sre-fix-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.sre-fix-why {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

/* The `oc` command it replaces. Long, and it must never push the panel sideways. */
.sre-fix code {
  overflow-wrap: anywhere;
}

/* ============================================================================
   The hop menu and the hop info panel — right-click on the application flow
   ============================================================================

   Both are true overlays: they float over the board rather than separating a
   region of it, so `--elev-overlay` is the sanctioned elevation here. The v4
   ban on shadows is about in-page separation, which is why `--elev-rest` and
   `--elev-hover` are `none` in both themes and why `.card` is asserted to carry
   a border and no shadow — an overlay is the stated exemption, and without one
   a floating panel and the diagram beneath it read as the same plane.

   Both are `position: fixed`. `.appflow-canvas` is a horizontal scroller, so an
   absolutely-positioned child would need correcting for `scrollLeft` on every
   scroll and would still be clipped at the container's edges. Viewport
   coordinates come straight off the pointer event and need no correction. */

.appflow-menu {
  position: fixed;
  z-index: var(--z-modal);
  min-width: 190px;
  padding: var(--space-1);
  background: var(--bg-surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  box-shadow: var(--elev-overlay);
  display: flex;
  flex-direction: column;
  gap: 1px;
}

/* Which hop this menu belongs to. A menu that opens over a dense board with no
   heading leaves the reader unsure which of twenty plates they hit. */
.appflow-menu-head {
  padding: var(--space-2) var(--space-3) var(--space-1);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-muted);
  border-bottom: 1px solid var(--border-hairline);
  margin-bottom: var(--space-1);
  overflow-wrap: anywhere;
}

.appflow-menu-item {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  border: 0;
  background: none;
  text-align: left;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--color-text);
  cursor: pointer;
}
.appflow-menu-item:hover,
.appflow-menu-item:focus-visible {
  background: var(--bg-hover);
}

/* ── the info panel ──────────────────────────────────────────────────────
   Bottom-right rather than centred, and that is the point: the reader arrived
   at a diagram that had already worked something out, so the answer has to sit
   beside the picture rather than over it. `BreakBanner` in the same file was
   rewritten for exactly this reason. */
.appflow-info {
  /* `left`/`top` come from `useAnchored`, which puts the panel at the pointer that opened it and
     clamps it into the viewport. It used to be pinned `right`/`bottom` to the window corner, which
     on a wide board is the far side of the screen from the hop that was clicked. */
  position: fixed;
  z-index: var(--z-modal);
  width: min(380px, calc(100vw - var(--space-6)));
  max-height: min(70vh, 560px);
  display: flex;
  flex-direction: column;
  background: var(--bg-surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  box-shadow: var(--elev-overlay);
  overflow: hidden;
}

/*
 * Top-aligned, not centred, and that is the whole header fix.
 *
 * The name wraps — `frontend-application.apps.openshift.learning.com` takes three lines in a 380px
 * panel, measured — and centring put the status badge and the close button at the vertical middle
 * of a 74px header, floating a long way below the line they belong to. Reading the panel, the eye
 * goes to the kind, then to a badge sitting nowhere near it.
 *
 * Top alignment pins both to the first line whatever the name does, so a one-line hop and a
 * three-line one have their badge in the same place.
 */
.appflow-info-head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--border-hairline);
}


.appflow-info-title {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.appflow-info-kind {
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.appflow-info-name {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text);
  overflow-wrap: anywhere;
}

/*
 * A square target rather than a glyph with padding round it. At `padding: 4px 8px` the button
 * measured 26x18 — under the 24px minimum a pointer target wants, and wider than it is tall, so the
 * × sat visibly off-centre against the badge beside it. A fixed 24x24 grid centres the glyph on
 * both axes and gives the same box whatever the font does with ×.
 */
.appflow-info-close {
  flex: none;
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  border: 0;
  background: none;
  color: var(--text-muted);
  width: 24px;
  height: 24px;
  display: grid;
  place-items: center;
  font-size: 17px;
  line-height: 1;
  padding: 0;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.appflow-info-close:hover,
.appflow-info-close:focus-visible {
  background: var(--bg-hover);
  color: var(--color-text);
}

/* Scrolls inside itself. A hop with a long phase reason and a 253-character
   object id must not push the panel past the bottom of the viewport, where its
   own close button would be the thing off screen. */
.appflow-info-body {
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--space-3) var(--space-4) var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.appflow-info-row {
  display: grid;
  grid-template-columns: 96px 1fr;
  gap: var(--space-3);
  align-items: baseline;
}
@media (max-width: 420px) {
  .appflow-info-row { grid-template-columns: 1fr; gap: 2px; }
}

/*
 * One line, always. A label that wraps to two lines against a one-line value hangs below it and the
 * row reads bottom-heavy — measured at 26px against every other label's 13px, which is what made
 * the panel look crooked even though every column edge was exact. The labels are short enough to
 * fit 96px; `nowrap` is the assertion that they stay that way, and a new one that does not fit will
 * show as an ellipsis rather than silently bending the row.
 */
.appflow-info-k {
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.appflow-info-v {
  font-size: var(--text-xs);
  line-height: var(--leading-body);
  color: var(--color-text);
  overflow-wrap: anywhere;
}

/* An absent measurement, set apart from a present one. The board's whole posture
   is that "nothing measured this" and "this measured zero" are different answers,
   and a reader skimming the panel has to be able to tell them apart at a glance. */
.appflow-info-none {
  color: var(--text-muted);
  font-style: italic;
}

.appflow-info-reason { color: var(--warn-text); }

.appflow-info-id {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--text-muted);
  overflow-wrap: anywhere;
}

/* ---------------------------------------------------------------------------------------------
 * The assistant's answer, now that it has shapes other than a paragraph.
 *
 * `.sre-answer` itself is untouched — it is a flex column with `--text-base` and `--space-3` between
 * children, and `chat-readability.test.ts` pins both. These are the children it did not have before.
 * ------------------------------------------------------------------------------------------- */

.sre-answer-list {
  margin: 0;
  padding-left: var(--space-5);
  max-width: 68ch;
  display: grid;
  gap: var(--space-2);
}

/* Bullets sit outside the measure, so a wrapped item's second line aligns with its first word
   rather than with the marker. */
.sre-answer-list li {
  padding-left: var(--space-1);
  overflow-wrap: anywhere;
}

/* An identifier from a cluster: a pod name, a field, a command. Marked, not shouted — the answer is
   mostly identifiers, and a heavy treatment on each turns the paragraph into a ransom note. */
.sre-answer code {
  font-family: var(--font-mono);
  font-size: 0.92em;
  padding: 0 3px;
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
  border: 1px solid var(--border-hairline);
  overflow-wrap: anywhere;
}

.sre-answer strong { font-weight: 600; color: var(--text-primary); }

/*
 * "How to fix" and "In detail" — the two parts of the reply the panel used to drop.
 *
 * Under the answer and above the tool strip, each with a small label so a reader can tell the steps
 * from the account behind them at a glance. The label is the quiet uppercase used for every section
 * heading in this panel, so it reads as structure rather than as a second answer.
 */
.sre-part {
  margin-top: var(--space-4);
  display: grid;
  gap: var(--space-2);
  min-width: 0;
}

.sre-part-title {
  margin: 0;
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-secondary);
}

/* The steps are the actionable half, so they keep the answer's size rather than the detail's. */
.sre-part-steps {
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  color: var(--text-primary);
}
.sre-part-steps code {
  font-family: var(--font-mono);
  font-size: 0.92em;
  padding: 0 3px;
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
  border: 1px solid var(--border-hairline);
  overflow-wrap: anywhere;
}
.sre-part-steps strong { font-weight: 600; }

/* ------------------------------------------------------------------ Code & deploy: review
 *
 * Branches, tags, protection, comparison, search and merge requests. Every colour here comes from a
 * token; the only hues are the four the design system already reserves for status, and they are used
 * for exactly that — a diff's added and removed lines, and a check that passed or did not.
 */

/* The tab bar carried by every repository page. */
.cdw-repotabs {
  display: flex;
  gap: var(--space-1);
  align-items: center;
  flex-wrap: wrap;
  border-bottom: 1px solid var(--border-hairline);
  margin-bottom: var(--space-4);
}
.cdw-repotab {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  color: var(--text-secondary);
  text-decoration: none;
  font-size: var(--text-sm);
  /* The active tab is marked by a rule that continues the tab bar's own, so the two read as one
   * line rather than as a box sitting on top of it. */
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}
.cdw-repotab:hover { color: var(--text-primary); background: var(--bg-hover); text-decoration: none; }
.cdw-repotab.is-on { color: var(--text-primary); border-bottom-color: var(--text-primary); }
.cdw-repotab:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }

/* A titled block. Used by every page in this group, so the spacing between them is decided once. */
.cdw-panel {
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  margin-bottom: var(--space-4);
  background: var(--bg-surface);
}
.cdw-panel-title {
  font-size: var(--text-sm);
  font-weight: 600;
  margin: 0 0 var(--space-3);
  color: var(--text-primary);
}
.cdw-panel h4 { font-size: var(--text-sm); margin: var(--space-3) 0 var(--space-2); }

/* Form rows. `align-items: flex-end` is what puts a button on the baseline of the inputs beside it —
 * and `Field`'s own inline bottom margin is why the button needs its own, since an inline style
 * cannot be overridden from a stylesheet (trap 30). */
.cdw-formrow {
  display: flex;
  gap: var(--space-3);
  align-items: flex-end;
  flex-wrap: wrap;
  margin-bottom: var(--space-3);
}
.cdw-formrow > button { margin-bottom: var(--space-4); }
/*
 * A list's own toolbar: the filter on the left, the action that adds to the list on the right, both
 * on one centre line. Not `.cdw-formrow`, whose buttons drop a field label's height to sit level with
 * labelled inputs — beside a segmented filter that left the button a step above it.
 */
.cdw-listbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin: var(--space-4) 0;
}
.cdw-formcol { display: flex; flex-direction: column; gap: var(--space-2); }
.cdw-checkrow {
  display: flex;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-bottom: var(--space-3);
  font-size: var(--text-sm);
  color: var(--text-secondary);
}
.cdw-checkrow label { display: inline-flex; align-items: center; gap: var(--space-2); }
.cdw-formerror { color: var(--critical-text); font-size: var(--text-sm); margin: var(--space-2) 0; }
.cdw-textarea {
  width: 100%;
  background: var(--bg-sunken);
  color: var(--text-primary);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-sm);
  padding: var(--space-2);
  font-family: inherit;
  font-size: var(--text-sm);
  resize: vertical;
}
.cdw-textarea.mono { font-family: var(--font-mono); }
.cdw-textarea:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }

/* The branch selector above the editor. */
.cdw-branchbar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border-hairline);
  flex-wrap: wrap;
}
.cdw-branchpick { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-sm); }
.cdw-branchwarn { color: var(--warn-text); font-size: var(--text-sm); }

/* --------------------------------------------------------------------------------- the diff */

.cdw-diff {
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-sm);
  overflow-x: auto;
  background: var(--bg-sunken);
}
.cdw-hunk + .cdw-hunk {
  /* Hunks are separated stretches of one file. The rule between them is the "and then, further
   * down" that a unified diff otherwise leaves the reader to infer from the line numbers. */
  border-top: 1px solid var(--border-hairline);
}
.cdw-hunk-head {
  padding: var(--space-1) var(--space-3);
  color: var(--text-muted);
  background: var(--bg-hover);
  font-size: var(--text-xs);
}
.cdw-diffrow {
  display: grid;
  /* Two gutters of line numbers, a marker, the text, and a comment affordance that only takes room
   * when it is there. */
  grid-template-columns: 48px 48px 16px 1fr auto;
  align-items: start;
  font-size: var(--text-xs);
  line-height: 1.6;
}
.cdw-diffrow.is-add { background: color-mix(in srgb, var(--ok-text) 12%, transparent); }
.cdw-diffrow.is-del { background: color-mix(in srgb, var(--critical-text) 12%, transparent); }
.cdw-diffnum {
  text-align: right;
  padding-right: var(--space-2);
  color: var(--text-muted);
  -webkit-user-select: none;
     -moz-user-select: none;
          user-select: none;
}
.cdw-diffmark { color: var(--text-muted); -webkit-user-select: none; -moz-user-select: none; user-select: none; }
.cdw-difftext { white-space: pre; padding-right: var(--space-3); color: var(--text-primary); }
.cdw-diffcomment {
  opacity: 0;
  border: 0;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  padding: 0 var(--space-2);
}
.cdw-diffrow:hover .cdw-diffcomment { opacity: 1; }
.cdw-diffcomment:focus-visible { opacity: 1; outline: 2px solid var(--focus-ring); }

/* ------------------------------------------------------------------------- review discussion */

.cdw-thread {
  border-left: 2px solid var(--border-strong);
  margin: var(--space-2) 0 var(--space-2) var(--space-6);
  padding-left: var(--space-3);
}
/* A resolved thread is dimmed, never hidden: the argument is why the line reads as it does. */
.cdw-thread.is-resolved { opacity: 0.6; }
.cdw-note { margin-bottom: var(--space-2); }
.cdw-note-head {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  font-size: var(--text-xs);
  color: var(--text-secondary);
}
.cdw-note-body { font-size: var(--text-sm); white-space: pre-wrap; color: var(--text-primary); }
.cdw-thread-acts { display: flex; gap: var(--space-2); align-items: flex-start; flex-wrap: wrap; }
.cdw-composer { display: flex; flex-direction: column; gap: var(--space-2); margin: var(--space-2) 0; }
.cdw-suggestion {
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-sm);
  padding: var(--space-2);
  margin-top: var(--space-2);
  background: var(--bg-sunken);
}
.cdw-suggestion-head { display: flex; gap: var(--space-2); align-items: center; font-size: var(--text-xs); color: var(--text-secondary); }
.cdw-suggestion pre { margin: var(--space-2) 0; white-space: pre-wrap; font-size: var(--text-xs); }

/* ------------------------------------------------------------------------- the request page */

.cdw-mrgrid { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: var(--space-4); align-items: start; }
@media (max-width: 900px) { .cdw-mrgrid { grid-template-columns: minmax(0, 1fr); } }
.cdw-mrstate { display: flex; gap: var(--space-2); align-items: center; margin-bottom: var(--space-3); }
.cdw-mrdesc { white-space: pre-wrap; color: var(--text-secondary); margin-bottom: var(--space-4); }
.cdw-mracts { display: flex; flex-direction: column; gap: var(--space-2); margin-top: var(--space-3); align-items: stretch; }

.cdw-checks { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.cdw-checks li { display: grid; grid-template-columns: 16px auto 1fr; gap: var(--space-2); font-size: var(--text-sm); align-items: baseline; }
.cdw-check-mark { font-weight: 700; }
.cdw-checks li.is-pass .cdw-check-mark { color: var(--ok-text); }
.cdw-checks li.is-fail .cdw-check-mark { color: var(--critical-text); }
/* The reasons a merge is refused, under the button that is refusing. Every one of them names a
 * different thing to do, which is why they are a list and not a padlock. */
.cdw-blocked { margin: var(--space-2) 0 0; padding-left: var(--space-5); font-size: var(--text-xs); color: var(--text-secondary); }
.cdw-conflicts { margin-top: var(--space-3); font-size: var(--text-sm); }
.cdw-conflict-text {
  white-space: pre-wrap;
  font-size: var(--text-xs);
  background: var(--bg-sunken);
  padding: var(--space-2);
  border-radius: var(--radius-sm);
  max-height: 300px;
  overflow: auto;
}

.cdw-approvals, .cdw-people, .cdw-commitlist { list-style: none; padding: 0; margin: 0; font-size: var(--text-sm); }
.cdw-approvals li, .cdw-people li, .cdw-commitlist li { padding: var(--space-1) 0; }
.cdw-approvals li.is-current { color: var(--text-primary); }
/* An approval that was overtaken stays on the page and reads as overtaken. It is still true that
 * this person approved those bytes; dropping the row would read as them changing their mind. */
.cdw-approvals li.is-stale { color: var(--text-muted); text-decoration: line-through; }

/* -------------------------------------------------------------------- changed files, compare */

.cdw-changes { display: flex; flex-direction: column; gap: var(--space-2); }
.cdw-changefile { border: 1px solid var(--border-hairline); border-radius: var(--radius-sm); overflow: hidden; }
.cdw-changehead {
  display: flex;
  gap: var(--space-3);
  align-items: center;
  width: 100%;
  padding: var(--space-2) var(--space-3);
  background: var(--bg-surface);
  border: 0;
  cursor: pointer;
  color: var(--text-primary);
  font-size: var(--text-sm);
  text-align: left;
}
.cdw-changehead:hover { background: var(--bg-hover); }
.cdw-changehead:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }
.cdw-changekind { font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted); min-width: 64px; }
.cdw-changekind.is-added { color: var(--ok-text); }
.cdw-changekind.is-deleted { color: var(--critical-text); }
.cdw-changekind.is-modified { color: var(--info-text); }
.cdw-comparecols { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4); }
@media (max-width: 900px) { .cdw-comparecols { grid-template-columns: 1fr; } }

/* ------------------------------------------------------------------------------ code search */

.cdw-hits { display: flex; flex-direction: column; gap: var(--space-3); }
.cdw-hit { border: 1px solid var(--border-hairline); border-radius: var(--radius-sm); padding: var(--space-2) var(--space-3); }
.cdw-hit-head { display: flex; gap: var(--space-3); align-items: baseline; flex-wrap: wrap; font-size: var(--text-sm); }
.cdw-hit-line {
  margin: var(--space-2) 0 0;
  font-size: var(--text-xs);
  white-space: pre-wrap;
  color: var(--text-secondary);
  overflow-x: auto;
}

/* The search entry point on the Code & deploy index. Not a sidebar destination — see the comment
 * beside it: the sidebar is a list of places, and this is something done to the repositories. */
.cdw-searchlink { margin: 0 0 var(--space-4); }

/* ---------------------------------------------------------------------------------------------
 * The composer, after its two buttons were removed.
 *
 * Enter already sent and Shift+Enter already made a newline, so `Ask` was a second control for a key
 * the box was listening to anyway. `Investigate` moved onto the answer, where the reader has
 * something to judge before deciding they want more.
 *
 * The row becomes a column so the one remaining line — what a keyboard cannot tell you — sits under
 * the box instead of squeezing it. Declared here rather than edited in place above: this file was
 * being written in another session throughout, and an override at the end is the change least likely
 * to land in the middle of somebody else's edit.
 * ------------------------------------------------------------------------------------------- */

.sre-composer {
  flex-direction: column;
  align-items: stretch;
}

.sre-hint {
  align-self: flex-end;
  font-size: var(--text-xs);
  color: var(--text-muted);
  -webkit-user-select: none;
     -moz-user-select: none;
          user-select: none;
}

/*
 * Escalating to the loop, on the answer it applies to.
 *
 * Quiet on purpose. It is not the next thing most readers want — most answers settle the question —
 * and drawn at full weight it would compete with the fix suggestions above it, which are the
 * controls that actually change something.
 */
.sre-investigate {
  align-self: flex-start;
  margin-top: var(--space-3);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-full);
  border: 1px dashed var(--color-border);
  background: transparent;
  color: var(--text-secondary);
  font: inherit;
  font-size: var(--text-xs);
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--ease-out), color var(--motion-fast) var(--ease-out);
}
.sre-investigate:hover:not(:disabled) { border-color: var(--accent-text); color: var(--accent-text); }
.sre-investigate:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.sre-investigate:disabled { cursor: default; opacity: 0.6; }

/* ---------------------------------------------------------------------------------------------
 * The suggestion strip, bounded.
 *
 * `.sre-thread` is `flex: 1 1 auto` and this is its sibling, so an unbounded strip does not overflow
 * — it *takes* the thread's height. On the live cluster six chips naming pods
 * (`system-app-6cd5f75658-qqpkj in 3scale-sample`) wrapped to four rows and squeezed the thread
 * until the opening card was cut through the middle of its last line, which read as a clipping bug
 * and was really this.
 *
 * Two changes together: the chips now name the workload rather than the pod, which roughly halves
 * them, and this cap means no answer can ever be pushed off screen by a suggestion about a question
 * nobody has asked yet. The cap scrolls rather than hides, so a fourth fault is still reachable.
 * ------------------------------------------------------------------------------------------- */

.sre-examples {
  flex: none;
  max-height: 5.5rem;
  overflow-y: auto;
  align-content: flex-start;
}

/* A chip is one line. A wrapped one doubles the strip's height for a single suggestion, and the
   question is still readable truncated because its first three words carry the fault. */
.sre-examples .sre-example {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

/* ------------------------------------------------------ Code & deploy: drift and sync policy
 *
 * The reconciliation half. Colour is used for exactly one thing here — a verdict — and every hue
 * comes from the tokens the design system already reserves for status.
 */

.cdw-policy { display: flex; flex-direction: column; gap: var(--space-3); }
.cdw-policyrow { display: grid; grid-template-columns: 220px minmax(0, 1fr); gap: var(--space-3); align-items: start; }
@media (max-width: 700px) { .cdw-policyrow { grid-template-columns: minmax(0, 1fr); } }
/* A viewer sees the value in words rather than a switch that looks operable and is not. */
.cdw-policyread { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-sm); }

.cdw-drifthead { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); }
.cdw-driftcounts { display: flex; gap: var(--space-2); flex-wrap: wrap; margin: var(--space-3) 0; }

.cdw-driftlist { list-style: none; padding: 0; margin: var(--space-3) 0 0; display: flex; flex-direction: column; gap: var(--space-1); }
.cdw-driftrow { border: 1px solid var(--border-hairline); border-radius: var(--radius-sm); overflow: hidden; }
/* The left edge carries the verdict, so a column of rows reads as a column of verdicts without
 * having to read any of the words. */
.cdw-driftrow.is-out-of-sync { border-left: 3px solid var(--warn-text); }
.cdw-driftrow.is-missing { border-left: 3px solid var(--critical-text); }
.cdw-driftrow.is-extra { border-left: 3px solid var(--info-text); }
.cdw-driftrow.is-synced { border-left: 3px solid var(--ok-text); }
.cdw-driftrow.is-unknown { border-left: 3px solid var(--border-strong); }

.cdw-drifthead-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-2) var(--space-3);
  background: transparent;
  border: 0;
  color: var(--text-primary);
  font-size: var(--text-sm);
  text-align: left;
  cursor: pointer;
}
.cdw-drifthead-row[disabled] { cursor: default; }
.cdw-drifthead-row:not([disabled]):hover { background: var(--bg-hover); }
.cdw-drifthead-row:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }
.cdw-driftdetail { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/*
 * Why a deployment's folder yields no documents, on the resource tab.
 *
 * Deliberately not the drift row above it. That one ellipsises a detail onto a single line, which is
 * right for "replicas: 2 against 3" and wrong here — the details are sentences saying what to commit
 * next, and a truncated instruction is worse than none. These wrap, and the path stays on its own
 * line so it can be long without pushing the sentence anywhere.
 */
.cdw-srclist { list-style: none; padding: 0; margin: var(--space-3) 0 0; display: flex; flex-direction: column; gap: var(--space-2); }
.cdw-srcproblem {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border-hairline);
  border-left: 3px solid var(--critical-text);
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
}
.cdw-srcpath { font-size: var(--text-xs); color: var(--text-secondary); }
.cdw-srcdetail { color: var(--text-primary); line-height: 1.5; overflow-wrap: anywhere; }

.cdw-difftable { padding: var(--space-2) var(--space-3) var(--space-3); background: var(--bg-sunken); }
.cdw-diffline {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 3fr) minmax(0, 3fr);
  gap: var(--space-3);
  font-size: var(--text-xs);
  padding: var(--space-1) 0;
  align-items: baseline;
}
@media (max-width: 800px) { .cdw-diffline { grid-template-columns: minmax(0, 1fr); } }
/* A silenced difference is dimmed, never hidden: a rule nobody can see working is a rule nobody
 * can tell is too wide, and too wide is the direction this setting fails in. */
.cdw-diffline.is-ignored { opacity: 0.55; }
.cdw-diffpath { color: var(--text-secondary); overflow-wrap: anywhere; }
.cdw-diffwant { color: var(--ok-text); overflow-wrap: anywhere; }
.cdw-diffhave { color: var(--warn-text); overflow-wrap: anywhere; }

/* ---------------------------------------------------------------------- deployment projects */

.cdw-projgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: var(--space-3); }
.cdw-projlists { display: flex; flex-direction: column; gap: var(--space-1); font-size: var(--text-xs); color: var(--text-secondary); }
.cdw-projlists b { color: var(--text-primary); font-weight: 600; }

/* A project's sync windows, and the admin's inline form for adding one. */
.cdw-syncwin { display: grid; gap: var(--space-2); margin-top: var(--space-3); padding-top: var(--space-3); border-top: 1px solid var(--color-border); }
.cdw-syncwin h4 { margin: 0; }
.cdw-syncwin .cdw-window .cdw-details-note { flex: 1 1 12rem; }
.cdw-syncwin-form { display: grid; gap: var(--space-3); padding: var(--space-3); border-radius: var(--radius-md); background: var(--bg-hover); }
.cdw-syncwin-row { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: flex-end; }
.cdw-syncwin-field { display: grid; gap: 4px; font-size: 12px; color: var(--text-muted); min-width: 0; }
.cdw-syncwin-grow { flex: 1 1 14rem; }
.cdw-syncwin-days { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.cdw-syncwin-day {
  padding: 4px 10px;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--bg-surface);
  color: var(--text-muted);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.cdw-syncwin-day[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-text); }
.cdw-syncwin-day:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; }
.cdw-syncwin-days .cdw-details-note { margin: 0; }
.cdw-syncwin-check { display: flex; align-items: flex-start; gap: var(--space-2); font-size: 12px; line-height: 1.5; color: var(--text-secondary); }
.cdw-syncwin-check input { margin-top: 2px; }

/* ------------------------------------------------------- Code & deploy: the bar above the editor
 *
 * Replaces `.cdw-branchbar`, which held only the branch picker, and replaces the right-hand column
 * that held the commit and deployment panes. Everything here is one line of facts and two buttons:
 * which branch, which commit and what it said, how much is deployed, then Commit and Deploy.
 */
.cdw-topbar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border-hairline);
  background: var(--bg-page);
  /* One line. The facts truncate rather than wrapping the buttons onto a second row, which would
   * move them under the cursor's resting place every time a commit message got longer. */
  flex-wrap: nowrap;
  min-width: 0;
  overflow: hidden;
}
.cdw-topfact {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-2);
  font-size: var(--text-xs);
  color: var(--text-secondary);
  min-width: 0;
}
.cdw-topfact.is-warn { color: var(--warn-text); }
.cdw-topfact.is-bad { color: var(--critical-text); }
/* The commit subject is the one part allowed to be long, so it is the one part that truncates. */
.cdw-topmsg {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 42ch;
}
.cdw-topspacer { flex: 1 1 auto; min-width: var(--space-2); }

/* ------------------------------------------------- AI SRE console: following the end of the thread
 *
 * The thread used to be scrolled to its end after every turn, unconditionally. That is right exactly
 * while the reader is already at the end, and wrong the rest of the time: somebody scrolled up to
 * re-read the previous answer's evidence was thrown to the bottom the moment the next answer landed.
 *
 * `<Conversation>` from `components/ui/conversation.tsx` owns that behaviour now, and its scroll
 * button owns the way back. Both are drawn in Tailwind utilities, so almost nothing is needed here —
 * only the one property below, which Tailwind's `flex-1` does not imply and the panel cannot work
 * without.
 */
.sre-stream {
  /* `<Conversation>` already carries `relative flex-1 overflow-y-auto`. What `flex-1` cannot supply
     is this: a column flex item defaults to `min-height: auto`, which refuses to shrink below its
     content, so without it the thread grows the panel instead of scrolling and the composer is
     pushed off the bottom. Stated here rather than as a utility at the call site so it cannot be
     dropped by somebody tidying class names. */
  min-height: 0;
  min-width: 0;
  position: relative;
}

/* The fix panel on an incident: the change this product would make, before anybody commits to it.

   One column and `min-width: 0`, for the reason given over `.runbook-body` — the operation's target
   and the `oc` line are both long, unbroken, and arrive from a cluster, so a track that sizes to its
   content pushes the modal wider than the viewport instead of wrapping. */
.fix-panel {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-3);
  min-width: 0;
}
.fix-panel-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}
/* `namespace/name`, in the mono face the rest of the console addresses objects in, so it reads as an
   identifier rather than as prose. Wraps anywhere: a generated pod name has no spaces to break at. */
.fix-panel-target {
  font-family: var(--font-mono);
  font-size: var(--text-md);
  overflow-wrap: anywhere;
  min-width: 0;
}
/* The catalogue's sentence for why this operation addresses this symptom. Set at the same size as the
   evidence above it: this is the argument for the change, not a caption on it. */
.fix-panel-why {
  font-size: var(--text-md);
  line-height: var(--leading-relaxed);
  text-wrap: pretty;
  margin: 0;
}
/* Which alert this was derived from. Quieter than the argument, because it is provenance rather than
   reasoning — but present, so the reader can go and check it. */
.fix-panel-evidence {
  font-size: var(--text-sm);
  color: var(--text-muted);
  line-height: var(--leading-relaxed);
  text-wrap: pretty;
  margin: 0;
}
.fix-panel-oc-label {
  font-size: var(--text-sm);
  color: var(--text-muted);
  margin: 0;
}

/* The hand-off, when there is no fileable change: a sentence naming the object and a link to it.
   Column rather than a row — the sentence wraps to several lines on a narrow modal and a link sitting
   beside it would be pushed off the measure. */
.fix-panel-handoff {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-2);
  justify-items: start;
  min-width: 0;
}
.fix-panel-handoff-link {
  font-size: var(--text-md);
  font-weight: 600;
}

/* Investigate and Ask your model, side by side as two cards of one shape (`fix-panel.tsx`
   `NextMoves`): the title, what it does, and the button at the foot — the description grows so the
   two buttons line up whatever the length of the sentence above them. */
.fix-panel-next {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  gap: var(--space-3);
  min-width: 0;
}
.fix-panel-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
  padding: var(--space-4);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-lg);
  background: var(--bg-sunken);
  min-width: 0;
}
.fix-panel-card > .fix-panel-evidence { flex: 1 1 auto; margin: 0; }
.fix-panel-card[data-wide="yes"] { grid-column: 1 / -1; align-items: stretch; }

/* One candidate fix inside the incident's fix panel.

   Bordered and indented so three of them read as three separable answers rather than one wall: the
   reader is choosing between them, and a list that runs together is one they have to re-parse to
   compare. `min-width: 0` for the same reason as `.fix-panel` — the `oc` line inside is long and
   unbroken, and its own scroll container must be allowed to be narrower than its content. */
.fix-option {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-2);
  min-width: 0;
  padding: var(--space-3);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
}
.fix-option-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}
/* The catalogue's short name for the change. The operation key beside it is the identifier; this is
   the sentence, so it is set in the reading face rather than the mono one. */
.fix-option-label {
  font-family: var(--font-sans);
  font-size: var(--text-md);
}

/* "Where to run these", above the fixes rather than below them.

   Given a surface of its own so it reads as the answer to a different question from the cards under
   it — what the fix is, then where it goes. Without the tint it is three more lines of prose at the
   top of a panel the reader is already scrolling past. */
.fix-panel-handoff-top {
  padding: var(--space-3);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  background: var(--bg-sunken);
}
.fix-panel-handoff-title {
  font-family: var(--font-sans);
  font-size: var(--text-md);
}

/* The left column of the fix window: the form, the commands, and the way to a shell.

   Owns its own scrolling through `.advice-split-answer` above it, so the terminal beside it keeps
   the full height of the dialog while this column scrolls. */
.fix-window {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-4);
  min-width: 0;
}
/* The bar across the top of the fix window: which object, and the way to a prompt.

   Sticky against `.advice-split-answer`, which is the scroll container — everything under it is
   remedy cards with cautions and command blocks, and both of these are things the reader needs while
   they are down there. The shell control in particular was at the foot of that column, which is the
   mistake `monitoring/advice.tsx` documents: the one control whose job is to be findable, placed
   where it can only be reached by scrolling past everything else.

   An opaque background is not decoration here. A transparent sticky element lets the scrolled text
   run underneath it, which is worse than no bar at all. */
.fix-window-bar {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--border-hairline);
  background: var(--bg-surface);
}
/* The object being changed, in the mono face because it is a name a cluster gave it and the reader is
   about to type it. Takes the free space so the control beside it sits at the right edge. */
.fix-window-bar-target {
  flex: 1 1 auto;
  min-width: 0;
  overflow-wrap: anywhere;
  font-size: var(--text-sm);
}

/* The fixes, named, in the panel behind the window.

   Names and blanks only. This list used to be the full cards — the same `why`, caution and command
   the window renders — which made the button open a worse copy of what was already on screen. What
   the reader needs before deciding to open it is what is in there. */
.fix-panel-list {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
  min-width: 0;
}
.fix-panel-list-item {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  flex-wrap: wrap;
  min-width: 0;
}

/* -------------------------------------------------------------------------------------------
 * The composer on the first screen.
 *
 * Declared at the end for the same reason the column override above is: this file is being edited
 * in other sessions, and an append is the change least likely to land inside somebody else's.
 *
 * Full width and pill-shaped, because on this screen the box IS the screen — it sits under the
 * greeting with nothing else competing for the eye, rather than being the last strip of a panel
 * full of transcript. It goes back to the bottom, at the thread's own size, as soon as there is a
 * first answer above it.
 *
 * `max-height` with `overflow-y: auto` is what keeps a single-row pill honest: a pasted stack trace
 * scrolls inside the box instead of growing it until the greeting is pushed off the top.
 * ------------------------------------------------------------------------------------------- */

.sre-composer--hero { width: 100%; }

.sre-composer--hero .sre-input {
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-full);
  border-color: var(--border-strong);
  font-size: var(--text-md);
  line-height: 1.4;
  max-height: 40vh;
  overflow-y: auto;
}
.sre-composer--hero .sre-input:focus-visible { outline-offset: -2px; }

/* ==========================================================================
   Scripted terminal  (packages/ui/src/terminal.tsx)
   ==========================================================================

   A decorative figure, not a session. `bastion/terminal.tsx` is the real shell and it is xterm on
   its own stylesheet, so nothing here can reach it — that separation is the point rather than an
   accident of naming, and `data-scripted` on the root is what a reader or a test checks.

   The screen is `--bg-sunken` rather than a hardcoded black. A terminal-looking black panel was the
   first draft and it was wrong in the light theme for the reason the bastion file spends a paragraph
   on: a ground that ignores the theme makes every colour on it a guess. Here there are no ANSI
   colours to preserve, so the product's own sunken surface is simply correct in both themes.

   Monospace throughout, from `--font-mono`: this is the measurement voice, and a proportional
   terminal reads as a screenshot of prose.
   ========================================================================== */

.term {
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-lg);
  background: var(--bg-surface);
  overflow: hidden;
}

.term-bar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border-hairline);
  background: var(--bg-surface);
}

/* The three dots are the one piece of pure skeuomorphism here, and they earn it: they are what makes
   the block read as a terminal at a glance without a caption saying so. Sized to be recognisable and
   not to invite a click — they are `aria-hidden` and have no handler. */
.term-dot {
  width: 10px;
  height: 10px;
  border-radius: var(--radius-full);
  flex: 0 0 auto;
}
.term-dot-1 { background: var(--critical); }
.term-dot-2 { background: var(--warn); }
.term-dot-3 { background: var(--ok); }

.term-title {
  margin-left: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

.term-screen {
  padding: var(--space-3) var(--space-4);
  background: var(--bg-sunken);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  line-height: 1.6;
  /* Wide output scrolls inside the figure. A terminal that widens its own page is the one bug this
     block could introduce into every layout that hosts it. */
  overflow-x: auto;
}

.term-line {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  white-space: pre;
}

.term-prompt {
  color: var(--text-muted);
  flex: 0 0 auto;
  /* Deliberately a bare `$`. No host, no user, no `#` — see the component's own note about never
     rendering something a reader could mistake for an open root session. */
  -webkit-user-select: none;
     -moz-user-select: none;
          user-select: none;
}

.term-cmd .term-text { color: var(--text-primary); }
.term-out .term-text { color: var(--text-secondary); }

.term-idle { min-height: 1.6em; }

/* A solid caret, not a blinking one.
   
   The first draft blinked it, argued as a fourth exception to "nothing auto-animates in a loop"
   on the grounds that the typing really is in flight. That argument does not survive contact with
   the rule: the product sanctions exactly three loops — the shimmer, the spinner and the live-pill
   dot — and the whole value of a list that short is that it stays short. It is also load-bearing
   for a scan, since `marketing.test.ts` slices the stylesheet from its section banner to the end of
   the file and rejects `infinite`, so anything appended below that point inherits the ban.
   
   Nothing is lost. The caret exists only while a command is mid-type and is gone the moment the
   sequence finishes, so its presence already carries "still typing" without motion. */
.term-caret {
  display: inline-block;
  width: 0.55em;
  height: 1.05em;
  background: var(--text-primary);
  transform: translateY(0.15em);
}

/* ------------------------------------------------------------------ deployment pipeline diagnosis
 *
 * The Diagnose tab on a deployment: six stages between a commit and a running object, with the one
 * that stopped marked. Tone carries meaning here and the four states are deliberately not three —
 * `waiting` is the pipeline working as designed and needing a person, so it must not read as a
 * fault, and `unknown` is an honest answer about stale evidence rather than a failure. Colouring
 * either of them red is how a board teaches its readers to ignore red.
 */
.cdw-diagnose { display: flex; flex-direction: column; gap: var(--space-4); }

.cdw-diagnose-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
}
.cdw-diagnose-actions { display: flex; gap: var(--space-2); flex: none; }
.cdw-diagnose-summary { margin: 0; font-size: 14px; font-weight: 600; color: var(--text-primary); }
.cdw-diagnose-note { margin: 4px 0 0; font-size: 11px; color: var(--text-muted); }

.cdw-diagnose-answer {
  border: 1px solid var(--border-hairline);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-md);
  background: var(--bg-sunken);
  padding: var(--space-4);
}
.cdw-diagnose-answer h3 { margin: 0 0 var(--space-2); font-size: 13px; line-height: 1.45; }
.cdw-diagnose-answer p { margin: 0 0 var(--space-2); font-size: 12px; line-height: 1.6; color: var(--text-muted); }
.cdw-diagnose-answer ol { margin: 0; padding-left: 18px; font-size: 12px; line-height: 1.6; }
.cdw-diagnose-answer li { margin-bottom: 4px; }
.cdw-diagnose-answer-error { margin: 0; font-size: 12px; color: var(--warn-text); }

/*
 * The object-level findings, above the stage walk.
 *
 * Carries the critical edge the blocked stage already uses rather than a colour of its own: on this
 * panel a colour is a verdict, and a finding and a blocked stage are the same verdict about
 * different things. What separates them is weight — a finding is the thing to act on, so it gets the
 * tinted surface and the stages stay flat beneath it.
 */
.cdw-rc { display: flex; flex-direction: column; gap: var(--space-3); }
.cdw-rc-card {
  border: 1px solid color-mix(in srgb, var(--critical) 45%, transparent);
  background: color-mix(in srgb, var(--critical) 6%, var(--bg-surface));
  border-radius: 8px;
  padding: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}
.cdw-rc-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2) var(--space-3); }
.cdw-rc-title { margin: 0; font-size: 13px; font-weight: 600; line-height: 1.45; color: var(--text-primary); }
.cdw-rc-object { font-size: 11px; color: var(--text-muted); margin-left: auto; overflow-wrap: anywhere; }
.cdw-rc-detail { margin: 0; font-size: 12px; line-height: 1.6; color: var(--text-primary); }
/* A heading that labels three lines does not need to look like a heading. */
.cdw-rc-label {
  margin: var(--space-2) 0 0;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.cdw-rc-evidence { margin: 0; padding-left: 16px; font-size: 11px; line-height: 1.65; color: var(--text-muted); }
.cdw-rc-evidence li { overflow-wrap: anywhere; }
.cdw-rc-edits { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.cdw-rc-edit {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-3);
  padding: var(--space-2);
  border-radius: 6px;
  background: var(--bg-surface);
  border: 1px solid var(--border-hairline);
  min-width: 0;
}
.cdw-rc-file { font-size: 12px; font-weight: 600; overflow-wrap: anywhere; }
.cdw-rc-key { font-size: 11px; color: var(--text-primary); }
.cdw-rc-produces { font-size: 11px; color: var(--text-muted); overflow-wrap: anywhere; }
.cdw-rc-missing { font-size: 11px; color: var(--warn-text); }
.cdw-rc-chain { margin: 0; font-size: 11px; color: var(--text-muted); overflow-wrap: anywhere; }
.cdw-rc-note { margin: 0; font-size: 11px; line-height: 1.6; color: var(--text-muted); }
/* A button that reads as a link, because it opens a drawer rather than navigating. */
.cdw-rc-link {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  /* The same token `a` takes. `--accent-text` is text *on* an accent — near-white — and would have
     rendered this invisible in light mode. */
  color: var(--color-primary);
  text-decoration: underline;
  cursor: pointer;
}
.cdw-rc-link:hover { text-decoration: none; }

.cdw-stages { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }

.cdw-stage {
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  background: var(--bg-surface);
}
.cdw-stage-head { display: flex; align-items: baseline; gap: var(--space-2); }
.cdw-stage-mark { font-size: 13px; line-height: 1; flex: none; width: 14px; text-align: center; }
.cdw-stage-name { font-size: 12px; font-weight: 600; text-transform: capitalize; }
.cdw-stage-state { font-size: 11px; color: var(--text-muted); margin-left: auto; }
.cdw-stage-question { margin: 2px 0 0 22px; font-size: 11px; color: var(--text-muted); }
.cdw-stage-detail { margin: var(--space-2) 0 0 22px; font-size: 12px; line-height: 1.55; }
.cdw-stage-evidence { margin: var(--space-2) 0 0 22px; padding-left: 16px; font-size: 11px; line-height: 1.6; color: var(--text-muted); }
.cdw-stage-fix { margin: var(--space-2) 0 0 22px; font-size: 12px; line-height: 1.55; color: var(--text-primary); }

/* The stage that stopped is the one thing on this panel a reader should find without searching. */
.cdw-stage-blocked { border-color: color-mix(in srgb, var(--critical) 45%, transparent); background: color-mix(in srgb, var(--critical) 6%, var(--bg-surface)); }
.cdw-stage-blocked .cdw-stage-mark, .cdw-stage-blocked .cdw-stage-state { color: var(--critical-text); }

.cdw-stage-waiting { border-color: color-mix(in srgb, var(--warn) 45%, transparent); }
.cdw-stage-waiting .cdw-stage-mark, .cdw-stage-waiting .cdw-stage-state { color: var(--warn-text); }

.cdw-stage-unknown { border-style: dashed; }
.cdw-stage-unknown .cdw-stage-mark, .cdw-stage-unknown .cdw-stage-state { color: var(--text-muted); }

.cdw-stage-ok .cdw-stage-mark { color: var(--ok-text); }

/* Not reached is not a verdict, so it recedes rather than reporting anything. */
.cdw-stage-skipped { opacity: 0.55; }
.cdw-stage-skipped .cdw-stage-mark { color: var(--text-muted); }

/* ------------------------------------------------------------- Code & deploy: the two doors
 *
 * The landing asks which of the two things this section holds the reader came for. Both doors are
 * the same size on purpose: the repositories used to fill the page while the deployments board was
 * a sentence inside a card, which made the board — the thing somebody checking a rollout wants —
 * read as a footnote. One is a button because its content opens in place; the other is a link
 * because that board is a page of its own. They are styled identically so the difference in
 * mechanism is invisible, which is the point.
 */
.cdw-doors {
  display: grid;
  /*
   * `min(280px, 100%)` rather than a bare 280px: a bare track minimum is a floor the container
   * cannot go under, so on a narrow page the two doors stopped shrinking and overflowed the column
   * instead — clipped, not scrolled, exactly as the steps strip above was. With the `min()` the
   * floor stands down once the container is narrower than it, which is the only case it was
   * hurting.
   */
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
  gap: var(--space-4);
  margin: var(--space-5) 0;
  min-width: 0;
}

.cdw-door {
  display: grid;
  min-width: 0;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    "kicker figure"
    "title  figure"
    "note   note";
  align-items: start;
  gap: 2px var(--space-4);
  text-align: left;
  width: 100%;
  padding: var(--space-5);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-lg);
  background: var(--bg-surface);
  color: inherit;
  text-decoration: none;
  cursor: pointer;
  font: inherit;
  transition: border-color 120ms ease, background 120ms ease, transform 120ms ease;
}
.cdw-door:hover,
.cdw-door:focus-visible {
  border-color: var(--accent);
  background: var(--bg-hover);
  transform: translateY(-1px);
  /* The Deployments door is a link, and the global `a:hover` underlines and recolours every line of
     a link's text. A whole card underlined reads as broken; the border and lift already say "open". */
  color: inherit;
  text-decoration: none;
}

.cdw-door-kicker { grid-area: kicker; font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-muted); }
.cdw-door-title { grid-area: title; font-size: 18px; font-weight: 600; line-height: 1.3; }
/*
 * The count, as the thing the card is actually reporting.
 *
 * It was `--text-muted`, which made the largest mark on the door the faintest one on it: a 28px
 * numeral set quieter than the 11px kicker above it. The title says what is being counted and sits
 * immediately to its left, so the number is not decoration — it is the one measurement here, and a
 * measurement set below the prose around it inverts the hierarchy of the card it anchors.
 */
.cdw-door-figure {
  grid-area: figure;
  font-size: 28px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.01em;
  color: var(--text-primary);
}
.cdw-door-note { grid-area: note; margin-top: var(--space-3); font-size: 12px; line-height: 1.6; color: var(--text-muted); }

/* Back out of a chosen door without a page load, so the choice is cheap to change. */
.cdw-backlink { margin: 0 0 var(--space-3); }
.cdw-linkbtn {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--accent-text);
  cursor: pointer;
  text-decoration: none;
}
.cdw-linkbtn:hover, .cdw-linkbtn:focus-visible { text-decoration: none; }

/*
 * The kind row in the dependency map's legend.
 *
 * Its own flex row rather than more items in the state row: the two are different channels, and a
 * reader scanning for "what does that shape mean" should not have to step over four colour swatches
 * to find it. `currentColor` on the marks means they take the legend's own ink and carry no state,
 * which is the point — a shape in the legend that was green would read as "a healthy Service".
 */
.depmap-legend-kinds {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  color: var(--text-muted);
  /*
   * Its own line, forced.
   *
   * The parent is a wrapping flex row, so without this the six kind marks ran on from the four
   * health swatches and the legend became one dense strip of ten items — the reader had to find the
   * boundary between two channels that have nothing to do with each other. `flex-basis: 100%` makes
   * the break a property of the row rather than a guess about how much space is left.
   */
  flex: 1 0 100%;
  /* A hairline above, so the separation is visible even when the row above happens to end short. */
  padding-top: var(--space-2);
  border-top: 1px solid var(--color-border);
}

.depmap-legend-mark {
  width: 16px;
  height: 16px;
  flex: none;
  /* Baseline-aligned with the label beside it; SVG sits on the text baseline by default and hangs
     a few pixels low at this size. */
  vertical-align: -3px;
  margin-right: 6px;
}

/* ==========================================================================
   The dependency map's node card
   ==========================================================================

   This replaced a pair of brackets around a name. The brackets said one thing — state, in their
   colour — and every other fact was behind a hover. The card holds the three a reader always wants:
   what it is called, what kind of thing it is, and whether it is working.

   Only an emphatic object gets one. A map can carry ninety objects and a card each would be a wall,
   so the quiet text label is still what the rest are drawn as; that split is the one `graph.tsx` has
   always made and the reason a dense map stays readable.

   Every colour here is a token or `currentColor`. The border and the dot take the state tone from
   the element, because state is data and this stylesheet does not decide it.
   ========================================================================== */

.depmap-card {
  /* Translucent rather than opaque: an edge passing behind a card should still be faintly visible,
     which is what keeps the graph reading as one connected surface rather than as cards on top of a
     drawing. `--map-card` is the canvas ground at a fraction. */
  fill: color-mix(in srgb, var(--map-ground, #0b0f16) 82%, transparent);
  stroke-width: 1.2;
  stroke-opacity: 0.55;
}

/* The plate behind the kind mark. Quieter than the card's own border so the mark reads as the
   subject and the plate as its setting. */
.depmap-card-plate {
  fill: color-mix(in srgb, currentColor 8%, transparent);
  stroke-width: 1;
  stroke-opacity: 0.28;
}

.depmap-card-name {
  fill: var(--map-ink);
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.depmap-card-meta {
  fill: var(--map-ink-dim, var(--map-ink));
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  opacity: 0.68;
}

.depmap-card-dot {
  /* No stroke. A ringed dot at r=4 is two marks fighting inside eight pixels. */
  stroke: none;
}

/* A selected card lifts its border out of the hairline register; the group's own `is-selected`
   handling already carries the rest. */
.depmap-chip.is-selected .depmap-card {
  stroke-opacity: 1;
  stroke-width: 1.8;
}

/* ══════════════════════════════════════════════════════════════════════════
   The dependency map as an instrument

   The map used to be a drawing inside an ordinary card: a card head carrying a
   title and five wrapped buttons, a canvas, a legend band, and a paragraph.
   Everything except the canvas was page furniture, and together it was about a
   third of the height of the one thing a reader opened the page for.

   What is below turns the card into a console. The command row, the legend, the
   zoom dock and the minimap all belong to the map and all of them float over it
   or sit tight against it, so the drawing gets the space back. The panels are
   opaque rather than glassy on purpose — the edges underneath are the animated
   ones, and a translucent panel over moving lines makes both unreadable.

   ## Why the chrome commits to dark and the inspector does not

   `.depmap-canvas` already commits: it is a picture of light, and light needs a
   dark ground to be light against. The command row and the floating panels are
   part of that picture's frame, so they commit with it.

   The inspector does not. It holds a key/value list, a badge, a notice and a
   model's written briefing — product components, themed for the page — and a
   dark panel would render every one of them as dark ink on a dark ground for a
   light-theme reader. It is a panel of text docked over an instrument, not part
   of the instrument, and it follows the theme like every other panel of text.
   ══════════════════════════════════════════════════════════════════════════ */

.depmap-stage {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
}

/*
 * The surface: the box the drawing and everything floating over it live in.
 *
 * `position: relative` is the whole reason it exists — the dock, the legend, the
 * minimap, the hover panel and the inspector are all absolute against this, and
 * against this rather than against the card so they stay inside the drawing when
 * the card has padding. `overflow: hidden` is what keeps a panel from hanging
 * over the card's rounded corner.
 */
.depmap-surface {
  --depmap-inspector-w: 336px;
  position: relative;
  display: flex;
  flex-direction: column;
  min-height: 0;
  min-width: 0;
  overflow: hidden;
  border: 1px solid var(--map-edge);
  border-radius: var(--radius-lg);
  background: var(--map-ground);
}

/* A drag is a drag on the drawing, and the cursor has to say so before it starts. */
.depmap-canvas,
.chain-canvas {
  cursor: grab;
  touch-action: none;
}

.depmap-canvas.is-panning,
.chain-canvas.is-panning {
  cursor: grabbing;
}

/*
 * The canvas already carries its own radius and ground. Inside the surface both
 * are the surface's job, and a second radius one pixel inside the first draws a
 * visible double edge at each corner.
 */
.depmap-surface .depmap-canvas,
.depmap-surface .chain-canvas {
  border-radius: 0;
}

/* ------------------------------------------------------------ the command row */

.depmap-bar {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--map-edge);
  border-radius: var(--radius-lg);
  /*
   * A flat ground. This carried a "lit top edge" — the map's blue at 7% fading out by 52%, the way
   * a machined panel catches a light above it — and it looked good, but this bar states the
   * cluster's figures (6 nodes, 435 pods, 102 namespaces) and blacklist 6 is about exactly that
   * surface. The panel edge and the ground tone carry the separation on their own.
   */
  background: var(--map-ground);
  color: var(--map-ink);
}

.depmap-bar-where {
  display: grid;
  grid-template-areas:
    "live trail"
    "live focus";
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  -moz-column-gap: var(--space-3);
       column-gap: var(--space-3);
  flex: 1 1 260px;
  min-width: 0;
}

/*
 * The live dot: one of the three looping animations this product allows, and it
 * means what the other two mean — something is in flight. Here it is the cluster
 * still reporting. It stops dead under reduced motion rather than pausing, for
 * the reason the failure blink does: half the frames of a pulse are faint, and a
 * status light frozen on a faint frame is a status light nobody can read.
 */
.depmap-bar-live {
  grid-area: live;
  position: relative;
  width: 9px;
  height: 9px;
  border-radius: var(--radius-full);
  background: var(--map-ok);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--map-ok) 18%, transparent);
}

.depmap-bar-live.is-warn {
  background: var(--map-warn);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--map-warn) 18%, transparent);
}

.depmap-bar-live.is-critical {
  background: var(--map-critical);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--map-critical) 20%, transparent);
}

.depmap-bar-live.is-unknown {
  background: var(--map-unknown);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--map-unknown) 18%, transparent);
}

.depmap-bar-live::after {
  content: "";
  position: absolute;
  inset: -4px;
  border-radius: var(--radius-full);
  border: 1px solid currentColor;
  color: inherit;
  opacity: 0;
  animation: depmap-ping 2.6s var(--ease-out) infinite;
}

@keyframes depmap-ping {
  0% { transform: scale(0.7); opacity: 0.55; }
  70% { transform: scale(1.5); opacity: 0; }
  100% { transform: scale(1.5); opacity: 0; }
}

.depmap-bar-trail {
  grid-area: trail;
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  flex-wrap: wrap;
}

.depmap-bar-crumb {
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  font-size: var(--text-xs);
  color: var(--map-ink-dim);
  cursor: pointer;
  border-radius: var(--radius-sm);
}

.depmap-bar-crumb:hover:not(:disabled) {
  color: var(--map-ink);
  text-decoration: none;
}

.depmap-bar-crumb:disabled {
  cursor: default;
}

/* The cluster is the root of every trail, so it is the one crumb that is always
   there — a shade brighter, because it is the thing the rest is inside. */
.depmap-bar-crumb.is-root {
  color: var(--map-ink);
  font-weight: 600;
}

.depmap-bar-sep {
  color: var(--map-ink-dim);
  opacity: 0.5;
  font-size: var(--text-xs);
}

.depmap-bar-focus {
  grid-area: focus;
  margin: 2px 0 0;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--map-ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.depmap-bar-figures {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin: 0;
  flex: 0 1 auto;
}

.depmap-bar-figure {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.depmap-bar-figure dt {
  margin: 0;
  font-size: var(--text-sm);
  font-weight: 650;
  line-height: 1.1;
  color: var(--map-ink);
  font-variant-numeric: tabular-nums;
}

.depmap-bar-figure dd {
  margin: 0;
  font-size: 11px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--map-ink-dim);
  white-space: nowrap;
}

.depmap-bar-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-left: auto;
}

.depmap-bar-btn {
  border: 1px solid var(--map-panel-edge);
  background: color-mix(in srgb, var(--map-ink) 5%, transparent);
  color: var(--map-ink);
  font: inherit;
  font-size: var(--text-xs);
  font-weight: 550;
  padding: 6px 12px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  white-space: nowrap;
  transition: background 120ms var(--ease-out), border-color 120ms var(--ease-out);
}

.depmap-bar-btn:hover:not(:disabled) {
  background: color-mix(in srgb, var(--map-ink) 11%, transparent);
  border-color: color-mix(in srgb, var(--map-infra) 55%, transparent);
}

.depmap-bar-btn:disabled {
  opacity: 0.4;
  cursor: default;
}

.depmap-bar-btn.is-on {
  background: color-mix(in srgb, var(--map-infra) 22%, transparent);
  border-color: color-mix(in srgb, var(--map-infra) 62%, transparent);
}

/*
 * `Segmented` is the product's control and is themed for a page surface, so on
 * this committed-dark row it would render a light chip. Overridden rather than
 * replaced: two buttons with `aria-pressed` would look identical and be a second
 * implementation of a control this product already has one of.
 */
.depmap-bar .seg {
  background: color-mix(in srgb, var(--map-ink) 5%, transparent);
  border-color: var(--map-panel-edge);
}

.depmap-bar .seg-item {
  color: var(--map-ink-dim);
}

.depmap-bar .seg-item:hover {
  color: var(--map-ink);
}

.depmap-bar .seg-item-on {
  background: color-mix(in srgb, var(--map-infra) 26%, transparent);
  color: var(--map-ink);
}

/* ------------------------------------------------------------- the zoom dock */

.depmap-dock {
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 5px;
  border: 1px solid var(--map-panel-edge);
  border-radius: var(--radius-md);
  background: var(--map-panel);
  box-shadow: 0 10px 28px -14px rgba(0, 0, 0, 0.8);
}

.depmap-dock-btn {
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  border: 0;
  background: none;
  border-radius: var(--radius-sm);
  color: var(--map-ink-dim);
  cursor: pointer;
}

.depmap-dock-btn svg {
  width: 16px;
  height: 16px;
}

.depmap-dock-btn:hover:not(:disabled) {
  background: color-mix(in srgb, var(--map-ink) 10%, transparent);
  color: var(--map-ink);
}

.depmap-dock-btn:disabled {
  opacity: 0.32;
  cursor: default;
}

.depmap-dock-rule {
  width: 16px;
  height: 1px;
  background: var(--map-panel-edge);
  margin: 2px 0;
}

.depmap-dock-zoom {
  font-size: 10px;
  letter-spacing: 0.02em;
  color: var(--map-ink-dim);
  padding-bottom: 2px;
}

/* --------------------------------------------------------------- the minimap */

.depmap-minimap {
  position: absolute;
  right: var(--space-3);
  bottom: var(--space-3);
  z-index: 2;
  padding: 5px;
  border: 1px solid var(--map-panel-edge);
  border-radius: var(--radius-md);
  background: var(--map-panel);
  box-shadow: 0 10px 28px -14px rgba(0, 0, 0, 0.8);
}

.depmap-minimap-canvas {
  display: block;
  width: 148px;
  height: 92px;
  cursor: crosshair;
  touch-action: none;
}

.depmap-minimap-dot {
  opacity: 0.55;
}

.depmap-minimap-dot.is-focus {
  opacity: 1;
}

.depmap-minimap-window {
  fill: color-mix(in srgb, var(--map-ink) 12%, transparent);
  stroke: var(--map-ink);
  stroke-width: 1.5px;
  vector-effect: non-scaling-stroke;
}

/* ---------------------------------------------------------------- the legend */

/*
 * Sized to stay out of the way. The first version was 420 by 144 and covered a whole row of boxes in
 * the chain view, where the bottom left of the canvas is content rather than ground — a legend that
 * hides the picture is worse than no legend.
 */
.depmap-key {
  position: absolute;
  left: var(--space-3);
  bottom: var(--space-3);
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 5px;
  max-width: min(360px, calc(100% - var(--space-6)));
  padding: 7px 10px 8px;
  border: 1px solid var(--map-panel-edge);
  border-radius: var(--radius-md);
  background: var(--map-panel);
  box-shadow: 0 10px 28px -14px rgba(0, 0, 0, 0.8);
}

.depmap-key-toggle {
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  font-size: 10px;
  font-weight: 650;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--map-infra);
  cursor: pointer;
  white-space: nowrap;
}

.depmap-key-toggle:hover {
  text-decoration: none;
}

.depmap-key-group {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px var(--space-3);
}

/* A hairline between the channels, so line style, colour and shape do not read as
   one run of ten interchangeable keys. */
.depmap-key-group + .depmap-key-group {
  padding-top: 6px;
  border-top: 1px solid color-mix(in srgb, var(--map-panel-edge) 70%, transparent);
}

.depmap-key-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
  font-size: 11px;
  color: var(--map-ink-dim);
}

.depmap-key-line {
  width: 26px;
  height: 8px;
  flex: none;
  stroke: var(--map-ok);
  stroke-width: 1.6px;
  fill: none;
}

.depmap-key-line.is-declared {
  stroke-dasharray: 4 3;
  stroke: var(--map-ink-dim);
}

.depmap-key-dot {
  width: 8px;
  height: 8px;
  flex: none;
  border-radius: var(--radius-full);
}

.depmap-key-mark {
  width: 14px;
  height: 14px;
  flex: none;
  color: var(--map-ink-dim);
}

/* --------------------------------------------------------------- the tooltip */

/*
 * `pointer-events: none` is not cosmetic. The panel appears beside the cursor,
 * and a panel that then intercepts the cursor makes the chip underneath
 * un-clickable at exactly the moment the reader has decided to click it.
 */
.depmap-tip {
  position: absolute;
  z-index: 3;
  pointer-events: none;
  width: 236px;
  transform: translate(-50%, calc(-100% - 12px));
  padding: 9px 11px 10px;
  border: 1px solid var(--map-panel-edge);
  border-radius: var(--radius-md);
  background: var(--map-panel);
  box-shadow: 0 18px 40px -18px rgba(0, 0, 0, 0.9);
  color: var(--map-ink);
}

.depmap-tip.is-below {
  transform: translate(-50%, 12px);
}

.depmap-tip-head {
  display: flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
}

.depmap-tip-dot {
  width: 8px;
  height: 8px;
  flex: none;
  border-radius: var(--radius-full);
}

.depmap-tip-name {
  font-size: var(--text-xs);
  font-weight: 650;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.depmap-tip-kind {
  margin-top: 1px;
  font-size: 10px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--map-ink-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.depmap-tip-facts {
  margin: 8px 0 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.depmap-tip-fact {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  font-size: 11px;
}

.depmap-tip-fact dt {
  margin: 0;
  color: var(--map-ink-dim);
}

.depmap-tip-fact dd {
  margin: 0;
  color: var(--map-ink);
  text-align: right;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.depmap-tip-why {
  margin: 8px 0 0;
  font-size: 11px;
  line-height: 1.4;
  color: var(--map-ink-dim);
}

.depmap-tip-open {
  margin: 8px 0 0;
  padding-top: 7px;
  border-top: 1px solid color-mix(in srgb, var(--map-panel-edge) 70%, transparent);
  font-size: 10px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--map-ink-dim);
}

/* ------------------------------------------------------------- the inspector */

.depmap-inspector {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 4;
  width: min(var(--depmap-inspector-w), 100%);
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--bg-surface);
  border-left: 1px solid var(--color-border);
  box-shadow: -24px 0 48px -28px rgba(0, 0, 0, 0.85);
}

.depmap-inspector-head {
  flex: 0 0 auto;
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--color-border);
}

.depmap-inspector-id {
  min-width: 0;
  flex: 1 1 auto;
}

.depmap-inspector-kind {
  margin: 0;
  font-size: 10px;
  font-weight: 650;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.depmap-inspector-name {
  margin: 1px 0 0;
  font-size: var(--text-sm);
  font-weight: 650;
  line-height: 1.3;
  /* Kubernetes names are long and arbitrary; breaking anywhere beats a scrollbar
     across a panel whose whole job is to identify one object. */
  overflow-wrap: anywhere;
}

.depmap-inspector-ns {
  margin: 2px 0 0;
  font-size: var(--text-xs);
  color: var(--text-muted);
  overflow-wrap: anywhere;
}

.depmap-inspector-close {
  flex: none;
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  border: 0;
  background: none;
  border-radius: var(--radius-sm);
  font-size: 18px;
  line-height: 1;
  color: var(--text-muted);
  cursor: pointer;
}

.depmap-inspector-close:hover {
  background: var(--bg-hover);
  color: var(--color-text);
}

.depmap-inspector-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--space-4);
}

.depmap-inspector-foot {
  flex: 0 0 auto;
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--color-border);
}

/* The dock and the minimap move out from under the panel rather than being
   hidden by it: they are how the reader keeps looking while the panel is open,
   which is the entire reason the panel is docked and not a dialog. */
.depmap-surface.has-inspector .depmap-dock,
.depmap-surface.has-inspector .depmap-minimap {
  right: calc(var(--depmap-inspector-w) + var(--space-3));
}

/* ----------------------------------------------------------------- the caveat */

.depmap-caveat {
  margin: 0;
  font-size: var(--text-xs);
  line-height: 1.5;
  color: var(--text-muted);
}

/* --------------------------------------------------------------- the core */

/*
 * The core, as one element.
 *
 * The drop shadow is on the group rather than on the body, so the plate, the
 * rings and the sphere are lit by one light. Shadowing them separately makes
 * four objects at four depths, which is exactly the flat-sticker look the
 * layering exists to avoid.
 */
.depmap-core {
  filter: drop-shadow(0 6px 22px rgba(0, 0, 0, 0.55));
}

/* The surface the core stands on. Without it a lit circle on a dark field reads
   as a hole in the canvas rather than as an object above it. */
.depmap-core-plate {
  opacity: 0.12;
}

.depmap-core-ring {
  fill: none;
  stroke-width: 1;
  stroke-opacity: 0.22;
}

.depmap-core-ring.is-faint {
  stroke-opacity: 0.1;
}

/*
 * The one element on this canvas that encodes nothing.
 *
 * Argued in the same terms as the dot grid it sits on: at this opacity it is
 * ground rather than a mark, and the test for that is whether removing it
 * changes a single reading. What it buys is that an idle console looks powered
 * rather than frozen — which matters here because the rest of the motion on
 * this canvas is data, so a cluster with no measured traffic is otherwise a
 * still picture that reads as a page that has stopped updating.
 */
.depmap-core-orbit {
  fill: none;
  stroke-width: 1;
  stroke-opacity: 0.3;
  stroke-dasharray: 3 12;
}

.depmap-core-orbit.is-turning {
  /* `fill-box` puts the origin at the circle's own centre, so the geometry stays
     in `geometry.ts` instead of being copied into this file as a pixel pair. */
  transform-box: fill-box;
  transform-origin: center;
  animation: depmap-orbit 54s linear infinite;
}

@keyframes depmap-orbit {
  to { transform: rotate(360deg); }
}

.depmap-core-status {
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.03em;
}

@media (prefers-reduced-motion: reduce) {
  /*
   * Removed, not paused — the rule this file already applies to the failure
   * blink. A paused pulse stops on whichever frame it was on, and half the
   * frames of a pulse are nearly transparent.
   */
  .depmap-core-orbit.is-turning,
  .depmap-bar-live::after {
    animation: none;
  }

  .depmap-bar-live::after {
    opacity: 0;
  }
}

/* ------------------------------------------------------------- full window */

.depmap-card.is-full .depmap-stage {
  flex: 1 1 auto;
  min-height: 0;
}

.depmap-card.is-full .depmap-surface {
  flex: 1 1 auto;
  min-height: 0;
}

.depmap-card.is-full .depmap-bar,
.depmap-card.is-full .depmap-caveat {
  flex: 0 0 auto;
}

/* ------------------------------------------------------------- narrow windows */

/*
 * Under about a tablet the floating instruments stop being free.
 *
 * The corners of a radial graph are reliably empty at a laptop width and are
 * not at 640px: the canvas is the same drawing scaled down, so the chips move
 * inward and the panels start covering them. The minimap goes first — it is the
 * one control that duplicates another control — then the legend folds into its
 * own title, which is what the toggle is for.
 */
@media (max-width: 900px) {
  .depmap-minimap {
    display: none;
  }

  .depmap-surface.has-inspector .depmap-dock {
    right: var(--space-3);
  }

  /*
   * A sheet across the bottom rather than a rail down the side.
   *
   * A 336px rail beside a 600px canvas leaves neither of them usable, and a
   * full-width panel covers the map completely — which is the one thing the
   * docked inspector exists not to do. Taking the lower two thirds keeps the
   * core and the objects around it on screen, which is what the reader is
   * comparing the panel against.
   */
  .depmap-inspector {
    top: auto;
    left: 0;
    right: 0;
    bottom: 0;
    width: auto;
    max-height: 66%;
    border-left: 0;
    border-top: 1px solid var(--color-border);
    box-shadow: 0 -24px 48px -28px rgba(0, 0, 0, 0.85);
  }

  /*
   * The dock lies down when a sheet is open.
   *
   * A column of four buttons is 156 units tall, and the strip of canvas left
   * above a 66% sheet on a short viewport is less than that — so the bottom half
   * of the dock ends up behind the panel, which is the shape of control that
   * looks broken rather than absent. A row is 40 tall and fits whatever is left.
   */
  .depmap-surface.has-inspector .depmap-dock {
    flex-direction: row;
  }

  .depmap-surface.has-inspector .depmap-dock-rule,
  .depmap-surface.has-inspector .depmap-dock-zoom {
    display: none;
  }

  .depmap-surface.has-inspector .depmap-key {
    display: none;
  }
}

@media (max-width: 620px) {
  .depmap-key {
    display: none;
  }

  .depmap-bar-figures {
    gap: var(--space-3);
  }

  .depmap-bar-actions {
    margin-left: 0;
    width: 100%;
  }
}

/* ================================================================== sideways containment
 *
 * Eleven regions scroll horizontally and none of them contained on that axis, so reaching the right
 * end of a wide diff, a YAML line or a terminal row handed the wheel to the page and the whole
 * layout slid under the reader. That presents as "why is my dashboard drifting" — a rendering fault
 * to look at, a scroll one in fact, and invisible in review because it only happens at the end of a
 * scroll nobody performs while checking a screenshot.
 *
 * `scroll.test.ts` has held this rule since it was written; these rules were added after it and
 * outside it. One grouped declaration rather than eleven edits: the rule is "a region that scrolls
 * on x contains on x", and it reads as a rule when it is written once.
 *
 * Vertical is deliberately untouched. Containing on y is what froze the dashboard the first time
 * this was attempted, and it is the subject of that file's second test.
 * ====================================================================================== */

.yv-crumb,
.yv-code,
.yg-wrap,
.cdw-diff,
.cdw-atree,
.cdw-res-manifest,
.cdw-conflict-text,
.cdw-hit-line,
.term-screen {
  overscroll-behavior-x: contain;
}

/* ================================================================== the estate constellation
 *
 * The operator console's landing picture. Every label on it is one real thing the fleet contains,
 * counted by the same `/api/overview` aggregate the cards below are drawn from — so the picture and
 * the numbers cannot disagree.
 *
 * ## It commits to dark in both themes
 *
 * The same decision `.depmap-canvas` makes, and for the same reason: what is being drawn is *light*
 * — a lit core, glowing threads — and light needs a dark ground to be light against. On the light
 * theme's near-white page the identical bloom renders as a grey smudge, and the thing the picture
 * is about becomes the least visible part of it. So the palette is scoped here rather than read
 * from the page's tokens, and the component uses these by name.
 * ====================================================================================== */

.cons {
  --cons-ground: #05070a;
  --cons-grid: rgba(120, 200, 160, 0.10);
  --cons-ink: #e9f3ec;
  --cons-dim: #7d8f86;

  /* The estate's state, and the hue the whole picture takes from it. */
  --cons-ok: #3fdd93;
  --cons-warn: #ffc247;
  --cons-down: #ff5f70;
  --cons-hot: var(--cons-ok);

  position: relative;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--cons-ground);
}

.cons-warn {
  --cons-hot: var(--cons-warn);
}
.cons-down {
  --cons-hot: var(--cons-down);
}

.cons-canvas {
  display: block;
  width: 100%;
  height: auto;
  /*
   * A ceiling, not a height. As a `max` it binds only when it must: a short window scales the
   * drawing down rather than letting it run past the fold, and a tall one lets it take its own
   * aspect ratio instead of being letterboxed into a fixed box.
   */
  max-height: clamp(300px, 68vh, 700px);
}

/* ---- the core */

/*
 * The core, as one element.
 *
 * The shadow is on the group rather than on the body, so the wash, the three latitudes and the rim
 * are lit by one light. Shadowing them separately makes four objects at four depths, which is the
 * flat-sticker look the layering exists to avoid.
 */
.cons-core {
  filter: drop-shadow(0 0 30px color-mix(in srgb, var(--cons-hot) 22%, transparent));
}

.cons-core-lat {
  fill: none;
  stroke: var(--cons-hot);
  stroke-opacity: 0.34;
  stroke-width: 1;
}
.cons-core-rim {
  fill: none;
  stroke: var(--cons-hot);
  stroke-opacity: 0.55;
  stroke-width: 1.4;
}

.cons-eyebrow {
  fill: var(--cons-ink);
  font-family: var(--font-mono);
  font-size: 15px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  opacity: 0.66;
}

.cons-headline {
  fill: var(--cons-ink);
  font-size: 46px;
  font-weight: 600;
  letter-spacing: -0.03em;
}

/* ---- the threads */

.cons-threads {
  /* One filter over the group rather than one per path: twenty-six offscreen passes a frame is
     what makes a glow expensive, and the effect is identical. */
  filter: drop-shadow(0 0 6px color-mix(in srgb, var(--cons-hot) 34%, transparent));
}

.cons-thread {
  fill: none;
  stroke: var(--cons-ok);
  stroke-width: 1;
  stroke-opacity: 0.42;
}
.cons-thread.is-warn {
  stroke: var(--cons-warn);
  stroke-opacity: 0.72;
}
.cons-thread.is-down {
  stroke: var(--cons-down);
  stroke-opacity: 0.85;
  stroke-width: 1.4;
}

/* ---- the labels */

/*
 * Type on this canvas is in viewBox units and the canvas is always rendered smaller than its
 * viewBox — 1600 wide fitted into roughly 1100 is a scale of about 0.69. So every size here is
 * larger than its CSS-pixel equivalent, which is the correction the dependency map's stylesheet
 * documents at length after shipping a map with legible geometry and unreadable labels.
 */
.cons-text {
  fill: var(--cons-dim);
  font-family: var(--font-mono);
  font-size: 17px;
  letter-spacing: 0.1em;
}
.cons-label.is-lit .cons-text {
  fill: var(--cons-ink);
}
.cons-label.is-warn .cons-text {
  fill: var(--cons-warn);
}
.cons-label.is-down .cons-text {
  fill: var(--cons-down);
}

.cons-bracket {
  fill: none;
  stroke: var(--cons-ink);
  stroke-opacity: 0.42;
  stroke-width: 1.2;
}
.cons-label.is-warn .cons-bracket {
  stroke: var(--cons-warn);
  stroke-opacity: 0.8;
}
.cons-label.is-down .cons-bracket {
  stroke: var(--cons-down);
  stroke-opacity: 0.9;
}

.cons-mark {
  fill: var(--cons-ok);
}
.cons-label.is-warn .cons-mark {
  fill: var(--cons-warn);
}
.cons-label.is-down .cons-mark {
  fill: var(--cons-down);
}

/* ---- the caption */

.cons-caption {
  margin: 0;
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--color-border);
  background: var(--cons-ground);
  font-size: var(--text-xs);
  line-height: 1.5;
  color: var(--cons-dim);
}
.cons-omitted {
  color: var(--cons-ink);
}

/* ---- nothing reporting */

/*
 * A new deployment has no customers, no clusters and no agents, and the constellation of that is a
 * lit ball with nothing around it — which reads as a drawing that failed rather than as a fleet
 * that has not started. Words instead, on the same ground so the page does not jump.
 */
.cons-empty {
  padding: clamp(40px, 6vw, 72px) var(--space-5);
  text-align: center;
}
.cons-empty-title {
  margin: 0 0 var(--space-2);
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--cons-ink);
}
.cons-empty-note {
  margin: 0 auto;
  max-width: 56ch;
  font-size: var(--text-sm);
  line-height: 1.55;
  color: var(--cons-dim);
}

/* ------------------------------------------------- deployment details, results and inspection
 *
 * Four surfaces that answer "what is this, exactly" rather than "is it green": the Details tab,
 * the per-object result of a sync, the live half of the resource panel, and the board's estate
 * summary. All four are definition lists, because every one of them is a set of facts with names
 * and not a control — a facet is a checkbox and these must not look like one.
 */
.cdw-details { display: flex; flex-direction: column; gap: var(--space-5); }
.cdw-details-group { border: 1px solid var(--border-hairline); border-radius: var(--radius-md); padding: var(--space-4) var(--space-5); background: var(--bg-surface); }
.cdw-details-group h3 { margin: 0 0 var(--space-3); font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-muted); }

.cdw-details-facts {
  display: grid;
  grid-template-columns: minmax(130px, max-content) 1fr;
  gap: var(--space-2) var(--space-5);
  margin: 0;
  font-size: 12px;
  line-height: 1.6;
}
.cdw-details-facts dt { color: var(--text-muted); }
.cdw-details-facts dd { margin: 0; }
.cdw-details-ignores { list-style: none; margin: 0; padding: 0; }
/* The one value on this tab that is a problem rather than a setting. */
.cdw-details-flag { color: var(--warn-text); }
.cdw-details-note { margin: var(--space-2) 0 0; font-size: 12px; line-height: 1.55; color: var(--text-muted); }

/* Sync windows on a deployment: one row each, the kind as a tag, open ones at full strength. */
.cdw-windows { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.cdw-window { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px var(--space-2); }
.cdw-window[data-open="no"] .cdw-window-when { color: var(--text-muted); }
.cdw-window-kind {
  padding: 1px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  background: var(--ok-soft);
  color: var(--ok-text);
}
.cdw-window-kind[data-kind="deny"] { background: var(--warn-soft); color: var(--warn-text); }
.cdw-window-when { font-size: var(--text-sm); color: var(--text-primary); }
.cdw-window .cdw-details-note { margin: 0; }

/* A deployment's own Helm values. */
.cdw-values {
  display: block;
  width: 100%;
  margin-top: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-md);
  background: var(--color-background);
  color: var(--text-primary);
  font-size: 12.5px;
  line-height: 1.5;
  resize: vertical;
  -moz-tab-size: 2;
    -o-tab-size: 2;
       tab-size: 2;
}
.cdw-values:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 1px; }
.cdw-values-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); margin-top: var(--space-2); }
.cdw-values-actions .cdw-details-note { margin: 0; flex: 1 1 18rem; }
.cdw-values-read { margin: var(--space-3) 0 0; padding: var(--space-3); border-radius: var(--radius-md); background: var(--bg-hover); font-size: 12.5px; white-space: pre-wrap; overflow-wrap: anywhere; }

/* The operation itself, above the objects it touched. */
.cdw-sync-op {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 2px var(--space-4);
  margin: var(--space-3) 0;
  font-size: 11px;
}
.cdw-sync-op dt { color: var(--text-muted); }
.cdw-sync-op dd { margin: 0 0 var(--space-2); }

/* What the cluster replied for one object, and when. */
.cdw-step-result { font-size: 11px; color: var(--text-muted); }
.cdw-step-when { font-size: 11px; color: var(--text-muted); margin-left: auto; flex: none; }

.cdw-res-live, .cdw-res-events { margin-top: var(--space-4); }
.cdw-res-eventlist { list-style: none; margin: var(--space-2) 0 0; padding: 0; display: flex; flex-direction: column; gap: 2px; font-size: 11px; }
/*
 * Every track can shrink to nothing, which the previous three could not.
 *
 * It was `minmax(120px, max-content) 1fr max-content`, and each of those carried an unshrinkable
 * minimum: 120px for the reason, `max-content` for the age (a `max-content` track's *minimum* is
 * also max-content), and for the message `1fr` means `minmax(auto, 1fr)` — whose auto minimum is
 * the min-content of the text. Measured, that floor was 422px, and the row simply overflowed any
 * container narrower than it.
 *
 * The part that made it look intermittent is that the floor is a function of the **data**. The
 * message column's minimum is the longest run the engine cannot break, so a short event fits a
 * 560px dialog and a 2,184-character admission refusal does not — same stylesheet, same width, one
 * of them with a horizontal scrollbar and its own text running off the right edge.
 *
 * `minmax(0, …)` on all three removes the floor entirely, and the wrapping below is then what
 * decides where the text goes rather than the grid.
 */
.cdw-res-eventlist li {
  display: grid;
  grid-template-columns: minmax(0, max-content) minmax(0, 1fr) minmax(0, max-content);
  gap: var(--space-3);
  align-items: baseline;
  padding: 4px 0;
  border-bottom: 1px solid var(--border-hairline);
}
/*
 * A grid item will not shrink below its own min-content unless told to, so every one of them is
 * told to. Without this the tracks above can collapse and the *items* still push the row wide.
 */
.cdw-res-eventlist li > * { min-width: 0; }
.cdw-res-eventlist li:last-child { border-bottom: 0; }
.cdw-res-event-reason { font-weight: 600; overflow-wrap: anywhere; }
/*
 * Two levers, not one, and they do different jobs.
 *
 * `overflow-wrap: anywhere` breaks a run that has nowhere else to break *and* reduces the element's
 * min-content, which is what lets the track above collapse. `word-break: break-word` is the older
 * spelling and is here for engines that ship one and not the other — this is a cluster's own error
 * text, so a run with no break opportunity in it is the normal case rather than the odd one.
 */
.cdw-res-event-msg { color: var(--text-muted); overflow-wrap: anywhere; word-break: break-word; min-width: 0; }
/* The age is the one column allowed to keep its line — it is six characters and a unit. */
.cdw-res-event-when { color: var(--text-muted); white-space: nowrap; }
/* A Warning event is the line somebody opened this panel to find. */
.cdw-res-eventlist li.is-warning .cdw-res-event-reason { color: var(--warn-text); }

/*
 * Narrow: the event stacks instead of being squeezed into three columns.
 *
 * Three tracks that *can* collapse is not the same as three tracks worth having. Below this the
 * message column is narrower than a dozen characters and the text becomes a vertical ribbon, which
 * is legible in the sense that every character is on screen and unreadable in every other sense.
 * Stacked, the reason leads, the message gets the full width, and the age sits under it.
 *
 * The age moves to the top line beside the reason rather than onto a line of its own, because the
 * pair "what happened / when" is one reading and splitting it costs a row for six characters.
 */
@media (max-width: 560px) {
  .cdw-res-eventlist li {
    grid-template-columns: minmax(0, 1fr) minmax(0, max-content);
    gap: 2px var(--space-2);
  }
  .cdw-res-event-msg { grid-column: 1 / -1; }
}

/*
 * The correction, under the events that justify it.
 *
 * Set apart from the rest of the panel rather than continuing it, because everything above is a
 * reading of what is and this is the one block that proposes a change. A reader scrolling to the
 * events and stopping there has to be able to see at a glance that something below is different in
 * kind, which is what the rule and the raised ground do.
 */
.cdw-fix {
  display: grid;
  gap: var(--space-2);
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border-hairline);
}
.cdw-fix-why { margin: 0; font-size: 12px; line-height: 1.6; color: var(--text-secondary); overflow-wrap: anywhere; }
/*
 * The objection list sets the dialog's minimum width if it is not allowed to break.
 *
 * Its entries are dotted field paths in a monospace face —
 * `spec.containers[0].securityContext.allowPrivilegeEscalation` — and a browser offers no break
 * opportunity at a dot or a bracket. Measured, one of those is a 422px unbreakable run, so the
 * whole panel could not be narrower than that and the dialog grew a horizontal scrollbar with its
 * own text running off the right edge.
 *
 * `anywhere` rather than `break-word` as the primary, because only `anywhere` also lowers the
 * element's min-content — which is the number that was doing the damage, not the visible wrap.
 */
.cdw-fix-unresolved {
  margin: var(--space-2) 0 0;
  padding-left: var(--space-4);
  display: grid;
  gap: 2px;
  font-size: 11px;
  min-width: 0;
  overflow-wrap: anywhere;
  word-break: break-word;
}
.cdw-fix-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); flex-wrap: wrap; }
/*
 * The diff, which is only ever deletions.
 *
 * One column, not two: this fix removes keys and adds nothing, so a before-and-after would be a
 * pane of content beside a pane of blank lines. The line numbers are the document's own, so a
 * reader can open the file in the editor and land on them.
 */
.cdw-fix-diff {
  list-style: none;
  margin: 0;
  padding: var(--space-2) 0;
  display: grid;
  gap: 1px;
  font-size: 11px;
  line-height: 1.6;
  border: 1px solid var(--border-hairline);
  border-radius: 4px;
  background: var(--bg-sunken);
  /*
   * A removed line is a line somebody wrote, so it is never wrapped or truncated — it scrolls.
   * `overscroll-behavior-x` because reaching the right end of it must not then start moving the
   * modal behind it sideways, which reads as a rendering fault rather than a scroll one.
   */
  overflow-x: auto;
  overscroll-behavior-x: contain;
}
.cdw-fix-diff li {
  display: grid;
  grid-template-columns: 3rem 1rem 1fr;
  align-items: baseline;
  gap: var(--space-2);
  padding: 0 var(--space-3);
}
.cdw-fix-ln { color: var(--text-muted); text-align: right; -webkit-user-select: none; -moz-user-select: none; user-select: none; }
.cdw-fix-minus { color: var(--critical-text); text-align: center; -webkit-user-select: none; -moz-user-select: none; user-select: none; }
/*
 * The added line, in a diff that is usually all deletions.
 *
 * Only the chart fix adds anything — one word, `auto` to `force` — and it has to be legible as the
 * replacement for the line above it rather than as another removal. The number column is left empty
 * on an added row: it has no line number in the document the numbers are counted in.
 */
.cdw-fix-plus { color: var(--ok-text); text-align: center; -webkit-user-select: none; -moz-user-select: none; user-select: none; }
.cdw-fix-diff li.is-added .cdw-fix-code { color: var(--text-primary); }
.cdw-fix-code { white-space: pre; color: var(--text-secondary); }
.cdw-fix-acts { margin: var(--space-2) 0 0; display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
/* What pressing it does, beside the button rather than after the reader has pressed it. */
.cdw-fix-consequence { font-size: 11px; line-height: 1.5; color: var(--text-muted); flex: 1 1 16rem; min-width: 0; }
.cdw-fix-summary { margin: 0; font-size: 13px; font-weight: 600; line-height: 1.55; overflow-wrap: anywhere; }
/* Where a measurement came from, under the measurement. Quiet, because it is provenance, not content. */
.cdw-fix-from { margin: var(--space-2) 0 0 22px; font-size: 11px; line-height: 1.5; color: var(--text-muted); }
.cdw-fix > .cdw-fix-from { margin-left: 0; }

/*
 * A derived fix: where to change it, the change, what the cluster receives, what was checked.
 *
 * The location comes first and is a link, because it is the one thing every outcome has — a fault
 * with no derivable value (a memory limit, a crash loop) still has a line, and a reader who
 * disagrees with the proposed value still needs it. `fix-card.tsx` has the longer argument.
 */
.cdw-fixat { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-1); }
.cdw-fixat li { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-1) var(--space-3); min-width: 0; }
.cdw-fixat-link { font-size: 12px; font-weight: 600; color: var(--color-primary); overflow-wrap: anywhere; }
.cdw-fixat-key { font-size: 11px; color: var(--text-primary); }
.cdw-fix-file { display: grid; gap: var(--space-2); min-width: 0; }
.cdw-fix-edits { margin: 0; padding-left: var(--space-4); font-size: 11px; line-height: 1.6; color: var(--text-secondary); overflow-wrap: anywhere; }
.cdw-fix-rendered { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-1); font-size: 11px; line-height: 1.55; min-width: 0; }
.cdw-fix-rendered li { display: flex; flex-wrap: wrap; gap: 0 var(--space-2); overflow-wrap: anywhere; min-width: 0; }
.cdw-fix-rendered-vals { flex-basis: 100%; color: var(--text-secondary); overflow-wrap: anywhere; }
.cdw-fix-checks { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-1); font-size: 11px; line-height: 1.55; color: var(--text-secondary); }
.cdw-fix-checks li { display: grid; grid-template-columns: 1rem minmax(0, 1fr); gap: var(--space-2); overflow-wrap: anywhere; }
.cdw-fix-check-mark { text-align: center; }
.cdw-fix-checks li.is-passed .cdw-fix-check-mark { color: var(--ok-text); }
.cdw-fix-checks li.is-failed .cdw-fix-check-mark { color: var(--critical-text); }
/* In the resource panel the fix sits under the events, set apart by the rule the card would draw. */
.cdw-res-fix { display: grid; gap: var(--space-2); margin-top: var(--space-4); padding-top: var(--space-4); border-top: 1px solid var(--border-hairline); }
.cdw-res-fix > .cdw-fix { margin-top: 0; padding-top: 0; border-top: 0; }

/*
 * Every red object, and why. One row per object: the name (which opens it), what its red means,
 * and the sentence. Stacked on a narrow screen, where three columns would leave the sentence a
 * word wide.
 */
.cdw-rc-objects { display: grid; gap: var(--space-1); min-width: 0; }
.cdw-rc-objects ul { list-style: none; margin: var(--space-1) 0 0; padding: 0; display: grid; gap: var(--space-2); }
.cdw-rc-objects li {
  display: grid;
  grid-template-columns: minmax(0, 16rem) auto minmax(0, 1fr);
  gap: var(--space-1) var(--space-3);
  align-items: baseline;
  font-size: 11px;
  line-height: 1.55;
}
@media (max-width: 720px) { .cdw-rc-objects li { grid-template-columns: minmax(0, 1fr); } }
.cdw-rc-objects .cdw-rc-link { text-align: left; overflow-wrap: anywhere; }
.cdw-rc-role {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 1px 6px;
  border-radius: 999px;
  white-space: nowrap;
  border: 1px solid var(--border-hairline);
  color: var(--text-muted);
}
.cdw-rc-role.is-fault { color: var(--critical-text); border-color: color-mix(in srgb, var(--critical) 45%, transparent); }
.cdw-rc-role.is-superseded { color: var(--ok-text); }
.cdw-rc-role.is-unexplained { color: var(--warn-text); }
.cdw-rc-because { color: var(--text-secondary); overflow-wrap: anywhere; }

/* The fix, named in the attention band under the issue it answers. */
.cdw-att-fix { margin: var(--space-1) 0 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-1) var(--space-3); font-size: 12px; line-height: 1.55; overflow-wrap: anywhere; min-width: 0; }
.cdw-att-fix.is-quiet { color: var(--text-secondary); font-size: 11px; }
.cdw-att-fix-label { font-size: 10px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ok-text); }

/* The line a fix link landed on, in the editor: a tint across it and a bar in the gutter. */
.cdw-reveal-line { background: color-mix(in srgb, var(--color-primary) 16%, transparent); }
.cdw-reveal-gutter { border-left: 3px solid var(--color-primary); }

/*
 * The estate the board covers, above the facets that cut into it.
 *
 * Dressed as a facet card because it is a sibling of two facet cards, and it was not. It had one
 * declaration — a bottom margin — so the rail read as one bare block of text with two bordered
 * cards under it, and its label started 13px to the left of theirs: three blocks in a 220px column,
 * none of them agreeing where the column's left edge was.
 *
 * The margin went with it, and that is the second bug, not tidying. `.cdw-argo-rail` is a grid with
 * `gap: var(--space-4)`, and a grid gap does not collapse with a margin — it adds. So the rail
 * measured 32px between the summary and the first facet and 16px between the two facets, a rhythm
 * that breaks exactly once, at the top, where it is most visible.
 */
.cdw-summary {
  display: grid;
  gap: 2px;
  padding: var(--space-3);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-card);
  background: color-mix(in srgb, var(--bg-surface) 94%, transparent);
}
.cdw-summary-facts {
  display: grid;
  grid-template-columns: 1fr max-content;
  gap: 2px var(--space-3);
  margin: var(--space-2) 0 0;
  font-size: 12px;
}
.cdw-summary-facts dt { color: var(--text-muted); }
.cdw-summary-facts dd { margin: 0; font-variant-numeric: tabular-nums; }

/* ===========================================================================
 * The change detail dialog — /actions
 *
 * Six classes behind one rule: an approval screen is read top to bottom once,
 * by somebody who has to decide something, and the order of the answers is the
 * design. The dialog used to open with a nine-cell metadata grid, which put the
 * least decision-relevant facts (attempts, applied-at, an opaque policy id) in
 * the first thing the eye lands on and pushed the reason for the change below
 * the fold. These styles exist to make the new order readable, not to decorate.
 *
 * Appended at the end of the file deliberately. `styles.css` is edited by more
 * than one author at a time here, and a new block placed mid-file is a merge
 * conflict waiting to happen — while at the end it also wins the cascade
 * outright, which is what a set of brand-new class names wants anyway.
 * ======================================================================== */

/* The strip that replaced the opening grid: state, danger, object, age. Four
 * facts that qualify every sentence under them, on one line. */
.chg-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--color-border);
  margin-bottom: var(--space-2);
}

/* `min-width: 0` so a long namespace/name truncates rather than pushing the
 * badges off the row — a dialog is a fixed width and the target is the one
 * field on this line with no bound on its length. */
.chg-strip-target {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* Every section on the dialog. The heading carries the weight; the gap below it
 * is what stops eight stacked sections reading as one wall. */
.chg-block {
  margin-top: var(--space-5);
}

.chg-block > h4 {
  margin: 0 0 var(--space-2);
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.chg-block > p {
  margin: 0 0 var(--space-2);
}

/* The cause: what made this change happen.
 *
 * The one panel on the dialog given a surface of its own, because it is the one
 * whose absence was the actual defect — the screen named the alert or forecast
 * behind a change nowhere at all, and the id survived only inside the prose of
 * the rationale. The left rule is the whole treatment; a coloured card would be
 * louder than the severity badges it sits above and this theme has no accent to
 * spend on it. */
.chg-cause {
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-text-subtle);
  border-radius: var(--radius-md);
  background: var(--color-sunken);
}

.chg-cause-kind {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  margin-bottom: var(--space-1);
}

/* The alert's or forecast's own headline, at reading size rather than heading
 * size: it is a sentence from the cluster, not a label this product wrote. */
.chg-cause-title {
  margin: 0 0 var(--space-2);
  font-size: var(--text-md);
  line-height: 1.45;
  color: var(--color-text);
}

/* The notice inside the cause card has the card's padding around it already. */
.chg-cause > .notice {
  margin-bottom: var(--space-2);
}

.chg-cause > p:last-child {
  margin-bottom: 0;
}

/* ===========================================================================
 * Portal type and rhythm — every page
 *
 * Promoted from a one-page trial once the trial had been rendered and read
 * beside an unchanged page. It hangs off `.page-region`, the one element every
 * customer page renders inside, so a page written tomorrow inherits it without
 * opting in — the opposite of the marker the trial used, which every new page
 * would have had to remember and none would.
 *
 * Everything here is a token or a property on a class that already existed.
 * No page markup changed to receive any of it.
 *
 * ## What is deliberately NOT taken from the skill
 *
 * The skill is written for landing pages and says so — "not dashboards, not
 * data tables, not multi-step product UI". Four of its mandates are refused
 * here on purpose: ambient/diffuse shadows (this file forbids a shadow
 * outside `.elev-modal`/`.elev-overlay`), mesh gradients and glowing orbs
 * (they would undo the monochrome theme this product was just given), the
 * removal of the dashboard sidebar (every sidebar entry is held against an
 * SRE tool by `sre-views.test.ts`), and `picsum.photos` placeholder imagery
 * (fabricated data in a runtime path, and a CDN dependency in a product that
 * installs into disconnected clusters — CLAUDE.md trap 26).
 *
 * What IS taken: typographic presence, tabular figures, optical rhythm,
 * tracking discipline, orphan control, varied radii, and real hover states.
 * ======================================================================== */

/*
 * Outfit, self-hosted, for exactly the reason Inter and Geist are.
 *
 * The skill names Outfit, Satoshi and Cabinet Grotesk. Outfit is the only one
 * of the three on Google Fonts under the OFL with a variable cut, which is
 * what makes it vendorable as two woff2 files beside the others. Satoshi and
 * Cabinet Grotesk are Fontshare — free, but not redistributable in a customer
 * bundle on the same terms.
 *
 * 47 KB for both subsets. Licence sits beside the files.
 */
@font-face {
  font-family: "Outfit";
  src: url(/customerportal/_next/static/media/Outfit-Variable-latin.e96e1529.woff2) format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Outfit";
  src: url(/customerportal/_next/static/media/Outfit-Variable-latin-ext.ee756101.woff2) format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/*
 * Tokens, not rules, for anything the components set inline.
 *
 * `PageHeader` renders its `<h1>` with `style={css({ fontSize: "var(--text-xl)" })}`, and `css()`
 * in this design system is the identity function — a real inline style that beats every selector
 * here short of `!important` (CLAUDE.md trap 30). The size is therefore raised by redefining the
 * token the inline style already reads, which is the one lever that reaches it.
 */
.page-region {
  --font-sans: "Outfit", "Geist", ui-sans-serif, system-ui, sans-serif;
  /*
   * The page title is NOT resized here, and that is a decision rather than an omission.
   *
   * The skill asks for display presence on headings, and this product has a deliberate ceiling in
   * the other direction: `PageHeader` caps its title at `--text-xl` because nothing in the app
   * chrome may be larger, and the Overview — the one summary surface allowed a bigger heading —
   * opts out by rendering a header of its own rather than by raising the shared one.
   * `overview-hero.test.ts` pins both halves, and its wording is explicit about the failure mode:
   * "the shared header must not have been quietly raised for everybody to make one page look right".
   *
   * That is exactly what a `--text-page-title` override would have been. Forty operational pages
   * share that header; the trial that made a 30px title look good was one page with a two-word
   * title. The ceiling is the considered rule and it wins.
   */
}

/* Negative tracking on large type, positive on small caps — both are the skill's rule and neither
   is set inline, so both are reachable from here. `balance` stops a two-word title breaking 1+1. */
.page-region .page-header h1 {
  letter-spacing: -0.03em;
  text-wrap: balance;
}

.page-region .hero-eyebrow {
  font-size: 11px;
  letter-spacing: 0.16em;
}

/* Orphan control on the one block of prose on the page. `pretty` leaves the line breaks alone and
   only refuses to strand the last word, which is what `balance` would over-correct on a paragraph. */
.page-region .hero-detail,
.page-region .page-header p {
  text-wrap: pretty;
  max-width: 68ch;
}

/*
 * The figures, given the weight of data rather than of caption.
 *
 * They were label and value on one line at the same size, which reads as a sentence. Stacked, with
 * the value above its label and the value in the display face at tabular width, they read as
 * figures — and because every column stacks identically the baselines line up across the row,
 * which is the skill's "align shared elements across side-by-side items".
 */
.page-region .hero-figures {
  -moz-column-gap: var(--space-10);
       column-gap: var(--space-10);
  row-gap: var(--space-4);
}
.page-region .hero-figure {
  flex-direction: column-reverse;
  align-items: flex-start;
  gap: 2px;
}
.page-region .hero-figure dd {
  font-family: var(--font-sans);
  font-size: 26px;
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--text-primary);
}
.page-region .hero-figure dt {
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/*
 * Varied radius: the card is the container and stays soft, the table inside it is an inner surface
 * and goes tighter. A single radius on both is the thing that makes nested boxes read as one flat
 * sticker (the skill's "uniform border-radius on everything").
 */
.page-region > .card {
  border-radius: var(--radius-xl);
  /* Optical padding: the eye reads a box as lopsided when top and bottom are equal, because the
     content's own descenders and the last row's baseline sit high. Bottom carries slightly more. */
  padding-bottom: var(--space-2);
}
.page-region .tbl {
  border-radius: var(--radius-md);
}

/* Numbers in a data table are compared down the column, so they have to occupy equal width per
   digit. `tabular-nums` is the whole of it; the column already exists. */
.page-region .tbl tbody td {
  font-variant-numeric: tabular-nums;
}

/*
 * A real hover state on a row that is entirely clickable.
 *
 * Every row here opens a dialog, and nothing said so — the pointer changed over two of the seven
 * cells. The transition is on `background-color` alone and short: a row highlight that eases in
 * over 300ms feels like lag on a table somebody is scanning down.
 */
.page-region .tbl tbody tr {
  transition: background-color 120ms cubic-bezier(0.32, 0.72, 0, 1);
}
.page-region .tbl tbody tr:hover {
  background: var(--bg-hover, rgba(127, 127, 127, 0.06));
}

/* ======================================================================================
   TraceMesh — the application graph as the product.
   ======================================================================================

   ## Why this is appended rather than filed beside a related block

   `styles.css` is ~25,000 lines and concurrent sessions edit it. A span-based insertion here once
   swallowed three rules another author had added minutes earlier (`.alerts-tabrow`,
   `.alerts-cluster`, `.alerts-cluster-label`), because the edit walked from a preceding comment to a
   following selector and took everything between. Appending cannot do that.

   ## Why this honours the theme instead of being dark-only

   The brief asks for a dark interface (§28). This product has a real light theme with a tested
   toggle, and hardcoding dark here would give one page that ignores it — which reads as a bug, not a
   style. So every colour is a token or a `color-mix()` of tokens, and both themes fall out of one set
   of rules.

   ## The look: a workbench, and only trouble glows

   The page is one framed workbench — toolbar, drawing, inspector, timeline — rather than five bands
   stacked down the page, because the drawing is the product and it was getting whatever height the
   other bands left over. Inside the frame the canvas is the darkest surface on the page, a well
   with a dot field, so the graph reads as the thing being looked *into* and every control around it
   as furniture.

   Nodes are matte: a dark body, a ring in their health colour, and the score as an orbit around
   them. The only element on the canvas that emits light is the halo behind an unwell node. A reader
   opening this page is asking "where do I look", and the answer has to arrive before they have read
   a label — which works only while healthy nodes stay quiet, so nothing else is allowed to glow.

   ## The colour budget

   Health owns colour. Kind owns shape (`shapes.ts`), volume owns width and motion (`encode.ts`).
   Nothing else may take a hue: a canvas where amber might mean "queue" or might mean "degraded"
   cannot be scanned, only decoded. `--ok`, `--warn`, `--critical` are the product's own status tokens,
   so a node here is the same colour as the chip the alerts page gives the same service.
   ====================================================================================== */

/* The one place a band becomes a colour. Everything below reads these. */
.tm-workbench,
.tm-canvas,
.tm-inspector,
.tm-investigation,
.tm-timeline,
.tm-minimap,
.tm-legend {
  --tm-ok: var(--ok);
  --tm-warn: var(--warn);
  --tm-critical: var(--critical);
  /* `unknown` is a *muted* grey, not a light one: it must read as "no reading", which is visually
     quieter than healthy rather than louder. A bright grey competes with the bands that matter. */
  --tm-unknown: var(--color-text-subtle);
  /* The well the graph sits in: the page colour, one step *darker* than the frame around it. */
  --tm-ground: var(--color-background);
  /* A node's body. Matte, so the ring and the halo carry all of the colour. */
  --tm-body: var(--color-surface);
  /* The neutral line every unremarkable edge is drawn in. */
  --tm-wire: color-mix(in srgb, var(--color-text) 24%, transparent);
  /* Floating panels over the drawing. One recipe, so the four corners read as one set. */
  --tm-float-bg: var(--color-surface);
  --tm-float-border: var(--color-border);
  --tm-float-shadow: 0 1px 0 rgb(0 0 0 / 0.12), 0 10px 30px -8px rgb(0 0 0 / 0.45);
}

/* -------------------------------------------------------------------------------- workbench */

/*
 * The frame: toolbar, stage, timeline.
 *
 * `--tm-canvas-h` is set here, once, because the canvas and the inspector beside it both read it — an
 * inspector that sized itself would be a second answer to "how tall is the stage", and the wrong one
 * would be the one that scrolls the page.
 *
 * Tall, but bounded by what is left of the window rather than by a fraction of it. `62vh` ignored
 * everything above the canvas: measured on a 1680x1000 window the canvas began 413px down and ran
 * 620px, so it was cut off at the bottom and the timeline started at y=1065 — a scrubber nobody could
 * see without scrolling, on a surface whose point is reading the graph *against* the timeline.
 * Subtracting the chrome keeps both on screen. Re-measure the subtraction whenever the header above
 * the workbench changes height.
 */
.tm-workbench {
  --tm-canvas-h: clamp(24rem, calc(100vh - 27.5rem), 58rem);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  background: var(--color-surface);
  /*
   * `clip`, not `hidden`. A `hidden` box is still a scroll container, so focusing a control inside
   * it can scroll the *frame* — measured on a phone, where the search pill sits in flow: the frame
   * scrolled itself 231px and the whole toolbar slid out of sight above its own border, with nothing
   * on screen to scroll it back. `clip` rounds the corners the same way and cannot be scrolled.
   */
  overflow: clip;
  min-width: 0;
}

/* ------------------------------------------------------------------------------- the toolbar */

.tm-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
  padding: var(--space-2) var(--space-3);
  border-block-end: 1px solid var(--color-border);
}

/* A quiet caption before each control group, so "Services" and "Traffic" are not two rows of
   look-alike buttons with nothing to say which is the scale and which is the encoding. */
.tm-bar-label {
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-subtle);
}

.tm-levels,
.tm-modes {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}

/*
 * A segmented control: one sunken track, the chosen segment raised out of it.
 *
 * One recipe for both groups, because they are the same kind of control — a single choice among a
 * few — and two styles would suggest two behaviours.
 */
.tm-seg {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 2px;
  border-radius: var(--radius-sm);
  background: var(--color-sunken);
  border: 1px solid var(--color-border);
}

.tm-level-btn,
.tm-mode-btn {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  border: 1px solid transparent;
  background: transparent;
  color: var(--color-text-subtle);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  line-height: 1;
  block-size: var(--size-control-sm);
  padding: 0 10px;
  border-radius: 8px;
  cursor: pointer;
  white-space: nowrap;
  transition: color var(--motion-fast) var(--ease-out), background var(--motion-fast) var(--ease-out);
}

.tm-level-btn:hover,
.tm-mode-btn:hover { color: var(--color-text); }

.tm-level-btn[data-on="yes"],
.tm-mode-btn[data-on="yes"] {
  background: var(--color-surface);
  border-color: var(--color-border-strong);
  color: var(--color-text);
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.25);
}

.tm-level-btn:focus-visible,
.tm-mode-btn:focus-visible,
.tm-scope:focus-visible,
.tm-btn:focus-visible,
.tm-dock-btn:focus-visible,
.tm-legend-toggle:focus-visible,
.tm-cmd-pill:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 1px;
}

/* The drill-in scope, as a removable chip beside the scale it narrows. */
.tm-scope {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text);
  font: inherit;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  block-size: var(--size-control-sm);
  padding: 0 var(--space-2);
  border-radius: var(--radius-full);
  cursor: pointer;
}

.tm-scope:hover { border-color: var(--color-border-strong); }
.tm-scope-label { font-family: var(--font-sans); color: var(--color-text-subtle); }
.tm-scope-x { color: var(--color-text-subtle); font-size: 0.875rem; line-height: 1; }
.tm-scope:hover .tm-scope-x { color: var(--color-text); }

.tm-tool-right {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-inline-start: auto;
}

.tm-tool-label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-subtle);
}

.tm-hint {
  /* Inline in the opener's aside, beside the measurement time — see `page.tsx`. */
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.tm-btn {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  block-size: var(--size-control-sm);
  padding: 0 var(--space-3);
  border-radius: 8px;
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--motion-fast) var(--ease-out), border-color var(--motion-fast) var(--ease-out);
}

.tm-btn:hover { background: var(--color-hover); border-color: var(--color-border-strong); }
.tm-btn[data-on="yes"] { background: var(--color-sunken); border-color: var(--color-border-strong); }
.tm-btn:disabled { opacity: 0.5; cursor: not-allowed; }

/* The one filled button on the surface, for the action a panel exists to offer. */
.tm-btn-primary {
  background: var(--color-primary-solid);
  border-color: var(--color-primary-solid);
  color: var(--color-on-primary);
}

.tm-btn-primary:hover { background: var(--color-primary-hover); border-color: var(--color-primary-hover); }

/* Investigate carries a red dot because it exists only when something is failing — the button is
   itself a finding, and the dot is the same colour as the halo it explains. */
.tm-btn-investigate {
  border-color: color-mix(in srgb, var(--tm-critical) 45%, var(--color-border));
}

.tm-btn-dot {
  inline-size: 6px;
  block-size: 6px;
  border-radius: 50%;
  background: var(--tm-critical);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--tm-critical) 22%, transparent);
}

/* --------------------------------------------------------------------------------- the stage */

/*
 * The body under the toolbar. The stage and timeline sit flush in it; anything else `AsyncView`
 * puts here — loading, error, the empty explanation — gets breathing room instead of touching the
 * frame.
 */
.tm-body { min-width: 0; }
.tm-body:not(:has(.tm-stage)) { padding: var(--space-6); }

/*
 * Canvas and inspector as a grid, with the inspector's column collapsing to nothing when closed.
 *
 * `minmax(0, 1fr)` on the canvas column, not `1fr`: a grid item's automatic minimum size is its
 * content, and an `<svg>` with a wide `viewBox` reports a large intrinsic width — so a bare `1fr`
 * lets the canvas push the inspector off the page. The `0` floor pins it to the container instead.
 */
.tm-stage {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: stretch;
}

.tm-stage[data-inspector="yes"] {
  grid-template-columns: minmax(0, 1fr) minmax(0, 26rem);
}

@media (max-width: 1100px) {
  /* The inspector goes under the canvas rather than squeezing it: below about 22rem the figures wrap
     and the health bars stop being readable, which is the whole content of the panel. */
  .tm-stage[data-inspector="yes"] { grid-template-columns: minmax(0, 1fr); }
}

.tm-canvas-wrap {
  position: relative;
  min-width: 0;
  /*
   * The well: the page colour, flat. The SVG's own ground is transparent and the dot field is drawn
   * over this. It once carried a faint radial wash, which is the atmosphere-behind-data the spec
   * forbids (`tokens.test.ts`, "no gradient outside the loading shimmer") — the halos on unwell
   * nodes are what should draw the eye, and a lit ground competes with them.
   */
  background: var(--tm-ground);
  overflow: clip;
}

.tm-canvas {
  display: block;
  width: 100%;
  height: var(--tm-canvas-h);
  cursor: grab;
  touch-action: none;
}

.tm-canvas[data-panning="yes"] { cursor: grabbing; }

.tm-ground { fill: transparent; }
.tm-grid-dot { fill: color-mix(in srgb, var(--color-text) 13%, transparent); }
.tm-arrowhead { fill: var(--tm-wire); }

/* ------------------------------------------------------------------------------------ edges */

.tm-edge-line {
  stroke: var(--tm-wire);
  stroke-linecap: round;
  transition: stroke-width var(--motion-base) ease, stroke var(--motion-base) ease;
}

.tm-edge { transition: opacity var(--motion-slow) var(--ease-out); }

/* A dashed link was never measured. This is the only channel separating "nobody measured this call"
   from "this call is idle", and without it the two are the same hair-thin line. */
.tm-edge[data-unmeasured="yes"] .tm-edge-line {
  stroke-dasharray: 4 6;
  stroke: color-mix(in srgb, var(--color-text) 16%, transparent);
}

.tm-edge[data-tone="degraded"] .tm-edge-line { stroke: color-mix(in srgb, var(--tm-warn) 75%, transparent); }
.tm-edge[data-tone="critical"] .tm-edge-line { stroke: color-mix(in srgb, var(--tm-critical) 80%, transparent); }
.tm-edge[data-tone="unknown"] .tm-edge-line { stroke: color-mix(in srgb, var(--tm-unknown) 40%, transparent); }

.tm-edge-label {
  fill: var(--color-text-subtle);
  font-family: var(--font-mono);
  /* World units, not screen pixels: text inside the `<svg>` scales with the viewBox, so these are
     sized against `WORLD_W` of 1680 against a ~1070px canvas — a factor of about 0.64. 15 here is
     ~10px on screen. They read as absurd next to the rest of the stylesheet and are correct. */
  font-size: 15px;
  text-anchor: middle;
  pointer-events: none;
  paint-order: stroke;
  stroke: var(--tm-ground);
  stroke-width: 4px;
  stroke-linejoin: round;
}

/* Traffic as light moving along the wire, in the wire's own tone: a failing call sends red. */
.tm-particle { fill: var(--color-text); opacity: 0.9; pointer-events: none; }
.tm-edge[data-tone="degraded"] .tm-particle { fill: var(--tm-warn); }
.tm-edge[data-tone="critical"] .tm-particle { fill: var(--tm-critical); }

/* `prefers-reduced-motion` is also honoured in JS, where particles are not rendered at all. This is
   the belt to that braces: a reader who asked for no motion gets none even if a code path slips. */
@media (prefers-reduced-motion: reduce) {
  .tm-particle { display: none; }
  .tm-edge-line { transition: none; }
}

/* ------------------------------------------------------------------------------------ nodes */

.tm-node {
  cursor: pointer;
  --band: var(--tm-unknown);
  transition: opacity var(--motion-slow) var(--ease-out);
}

.tm-node[data-band="healthy"] { --band: var(--tm-ok); }
.tm-node[data-band="degraded"] { --band: var(--tm-warn); }
.tm-node[data-band="critical"] { --band: var(--tm-critical); }

/* Matte: the body is the surface colour with a breath of the band in it, and the ring says the rest. */
.tm-node-shape {
  fill: color-mix(in srgb, var(--band) 14%, var(--tm-body));
  stroke: var(--band);
  stroke-width: 2.5;
  transition: fill var(--motion-base) ease, stroke var(--motion-base) ease, stroke-width var(--motion-fast) ease;
}

/* An unmeasured node is drawn with less conviction than a measured one: a thinner, fainter ring. */
.tm-node[data-band="unknown"] .tm-node-shape {
  fill: var(--tm-body);
  stroke: color-mix(in srgb, var(--tm-unknown) 60%, transparent);
  stroke-width: 1.75;
}

/* Hollow for the two kinds the reader cannot restart — an external dependency and an address. */
.tm-node-hollow {
  fill: var(--tm-ground);
  stroke-dasharray: 6 4;
}

.tm-node:hover .tm-node-shape { stroke-width: 4; }

/* The focus ring is on the group, because the group is what is tabbable. Without this the whole
   graph is keyboard-reachable and invisibly so, which is worse than not being reachable. */
.tm-node:focus { outline: none; }
.tm-node:focus-visible .tm-node-shape {
  stroke: var(--color-focus-ring);
  stroke-width: 4;
}

/* The halo behind an unwell node — see the header. A flat translucent disc in the band colour, not a
   gradient: the spec reserves gradients for a chart's area fill. `fill-opacity` rather than `opacity`,
   so the breathing animation below can still vary `opacity` on top of it. */
.tm-node-glow { fill: var(--band); fill-opacity: 0.14; pointer-events: none; }

/* A critical halo breathes, slowly. Degraded holds still: two things pulsing at once is an alarm
   panel, and only the worse of them has earned the motion. */
.tm-node[data-band="critical"] .tm-node-glow {
  animation: tm-breathe 3.2s ease-in-out infinite;
  transform-box: fill-box;
  transform-origin: center;
}

@keyframes tm-breathe {
  0%, 100% { opacity: 0.6; transform: scale(0.92); }
  50% { opacity: 1; transform: scale(1.06); }
}

/* The score orbit: a hairline track and the band-coloured share of it. */
.tm-node-orbit { pointer-events: none; }
.tm-orbit-track { fill: none; stroke: color-mix(in srgb, var(--color-text) 9%, transparent); stroke-width: 2; }
.tm-orbit-arc { fill: none; stroke: var(--band); stroke-width: 1.5; stroke-linecap: round; opacity: 0.55; }
/* A short orbit is the finding, so an unwell node's arc is drawn at full strength. */
.tm-node[data-band="degraded"] .tm-orbit-arc,
.tm-node[data-band="critical"] .tm-orbit-arc { stroke-width: 2.5; opacity: 1; }

.tm-node-ring {
  fill: none;
  stroke: var(--color-text);
  stroke-width: 1.5;
  opacity: 0.85;
}

/* The blamed node, drawn as a slowly turning dashed ring rather than a hard one, so it does not
   read as a selection — and so it reads as "look here" even on a graph where many nodes are red. */
.tm-node-blame {
  fill: none;
  stroke: var(--tm-critical);
  stroke-width: 2;
  stroke-dasharray: 3 7;
  stroke-linecap: round;
  animation: tm-turn 14s linear infinite;
  transform-box: fill-box;
  transform-origin: center;
}

@keyframes tm-turn {
  to { transform: rotate(360deg); }
}

@keyframes tm-pulse {
  0%, 100% { opacity: 0.35; }
  50% { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .tm-node-blame,
  .tm-node[data-band="critical"] .tm-node-glow { animation: none; }
  .tm-node, .tm-edge { transition: none; }
}

.tm-node-name {
  fill: var(--color-text);
  /* World units — see `.tm-edge-label`. 18 is ~11.5px on screen at the default camera. */
  font-size: 18px;
  font-weight: 500;
  letter-spacing: 0.01em;
  text-anchor: middle;
  pointer-events: none;
  /* A halo, so a name over a dense patch of edges stays readable without a background rectangle —
     which would occlude the very links the reader is tracing. */
  paint-order: stroke;
  stroke: var(--tm-ground);
  stroke-width: 5px;
  stroke-linejoin: round;
}

.tm-node-figure {
  fill: var(--color-text-subtle);
  font-family: var(--font-mono);
  font-size: 15px;
  text-anchor: middle;
  pointer-events: none;
  paint-order: stroke;
  stroke: var(--tm-ground);
  stroke-width: 4px;
  stroke-linejoin: round;
}

.tm-node[data-band="critical"] .tm-node-figure { fill: var(--critical-text); }
.tm-node[data-band="degraded"] .tm-node-figure { fill: var(--warn-text); }

.tm-bar-track { fill: color-mix(in srgb, var(--color-text) 10%, transparent); }
.tm-bar-fill { fill: var(--band); opacity: 0.75; }

/* --------------------------------------------------------------------------- causal overlay */

/* A wide, soft band along the failure's path, with a fine dashed core marching from cause to effect. */
.tm-causal-line {
  stroke: var(--tm-critical);
  stroke-width: 9;
  opacity: 0.16;
  stroke-linecap: round;
  pointer-events: none;
}

.tm-causal-flow {
  stroke: var(--tm-critical);
  stroke-width: 2;
  stroke-dasharray: 2 10;
  stroke-linecap: round;
  opacity: 0.9;
  pointer-events: none;
  animation: tm-march 1.1s linear infinite;
}

@keyframes tm-march {
  to { stroke-dashoffset: -24; }
}

@media (prefers-reduced-motion: reduce) {
  .tm-causal-flow { animation: none; }
}

.tm-causal-badge {
  fill: var(--color-surface);
  stroke: color-mix(in srgb, var(--tm-critical) 70%, transparent);
  stroke-width: 1.25;
}

.tm-causal-text {
  fill: var(--critical-text);
  font-family: var(--font-mono);
  font-size: 14px;
  text-anchor: middle;
  dominant-baseline: middle;
  pointer-events: none;
}

/* ---------------------------------------------------------------------- floating furniture */

/* The shared plate under every floating control. Opaque, not translucent: a glassy panel over
   animated edges makes both unreadable. */
.tm-dock,
.tm-minimap,
.tm-legend-toggle,
.tm-legend-body,
.tm-cmd-pill,
.tm-cmd-feedback {
  background: var(--tm-float-bg);
  border: 1px solid var(--tm-float-border);
  box-shadow: var(--tm-float-shadow);
}

/* ---------------------------------------------------------------------------------- the dock */

.tm-dock {
  position: absolute;
  inset-block-end: var(--space-3);
  inset-inline-start: var(--space-3);
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  border-radius: var(--radius-sm);
}

.tm-dock-btn {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  border: 0;
  background: transparent;
  color: var(--color-text-muted);
  inline-size: var(--size-control-sm);
  block-size: var(--size-control-sm);
  border-radius: 7px;
  display: grid;
  place-items: center;
  cursor: pointer;
}

.tm-dock-btn svg { inline-size: 1rem; block-size: 1rem; }
.tm-dock-btn:hover:not(:disabled) { background: var(--color-hover); color: var(--color-text); }
.tm-dock-btn:disabled { opacity: 0.35; cursor: not-allowed; }

.tm-dock-zoom {
  min-inline-size: 3.25rem;
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}

.tm-dock-sep {
  inline-size: 1px;
  block-size: 1rem;
  margin-inline: 2px;
  background: var(--color-border);
}

/* ------------------------------------------------------------------------------- the minimap */

.tm-minimap {
  position: absolute;
  inset-block-end: var(--space-3);
  inset-inline-end: var(--space-3);
  inline-size: 11rem;
  border-radius: var(--radius-sm);
  padding: 3px;
  overflow: hidden;
}

.tm-minimap svg { display: block; inline-size: 100%; block-size: auto; cursor: crosshair; border-radius: 7px; }
.tm-mini-ground { fill: var(--tm-ground); }
.tm-mini-dot { fill: var(--tm-unknown); }
.tm-mini-dot[data-band="healthy"] { fill: var(--tm-ok); }
.tm-mini-dot[data-band="degraded"] { fill: var(--tm-warn); }
.tm-mini-dot[data-band="critical"] { fill: var(--tm-critical); }
.tm-mini-dot[data-selected="yes"] { stroke: var(--color-text); stroke-width: 8; }
.tm-mini-dot[data-root="yes"] { stroke: var(--tm-critical); stroke-width: 8; }

.tm-mini-view {
  fill: color-mix(in srgb, var(--color-text) 6%, transparent);
  stroke: var(--color-text);
  stroke-width: 6;
  opacity: 0.7;
  pointer-events: none;
}

/* -------------------------------------------------------------------------------- the legend */

/*
 * Top-LEFT, and the corner matters.
 *
 * It was top-right, which is where a legend normally goes and is wrong here: `layered` puts the
 * deepest column at the right edge, and the deepest column is where the root cause lands — so an
 * opened legend covered the one node the reader had come to look at. The first render of this surface
 * had `payment-api`, the blamed service, half-hidden behind it.
 *
 * Top-left covers the shallowest column instead: gateways and entry points, which are the hops
 * `pickBreakPoint` deliberately refuses to blame because they always look broken when something
 * downstream is. If the legend has to hide something, that is the something to hide.
 */
.tm-legend {
  position: absolute;
  inset-block-start: var(--space-3);
  inset-inline-start: var(--space-3);
  max-inline-size: 19rem;
}

.tm-legend-toggle {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--color-text-muted);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  block-size: 2rem;
  padding: 0 var(--space-3);
  border-radius: var(--radius-sm);
  cursor: pointer;
  float: inline-start;
}

.tm-legend-toggle:hover,
.tm-legend[data-open="yes"] .tm-legend-toggle { color: var(--color-text); }

.tm-legend-icon {
  inline-size: 0.875rem;
  block-size: 0.875rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
}

.tm-legend-body {
  clear: both;
  margin-block-start: var(--space-2);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  max-block-size: 24rem;
  overflow-y: auto;
}

.tm-legend-mode { margin: 0 0 var(--space-2); color: var(--color-text); line-height: var(--leading-body); }
.tm-legend-h { margin: var(--space-3) 0 var(--space-1); font-size: var(--text-xs); font-weight: 500; text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-subtle); }
.tm-legend-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }

.tm-legend-row { display: flex; align-items: center; gap: var(--space-2); }
.tm-legend-swatch { inline-size: 0.625rem; block-size: 0.625rem; border-radius: 50%; border: 2px solid var(--tm-unknown); flex: none; }
.tm-legend-swatch[data-band="healthy"] { border-color: var(--tm-ok); box-shadow: none; }
.tm-legend-swatch[data-band="degraded"] { border-color: var(--tm-warn); box-shadow: 0 0 6px 1px color-mix(in srgb, var(--tm-warn) 55%, transparent); }
.tm-legend-swatch[data-band="critical"] { border-color: var(--tm-critical); box-shadow: 0 0 6px 1px color-mix(in srgb, var(--tm-critical) 55%, transparent); }
.tm-legend-shape { inline-size: 1rem; block-size: 1rem; flex: none; fill: none; stroke: var(--color-text-muted); stroke-width: 2; }
.tm-legend-label { flex: 1 1 auto; min-width: 0; color: var(--color-text); }
.tm-legend-note { color: var(--color-text-subtle); }
.tm-legend-count { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-text-subtle); font-variant-numeric: tabular-nums; }
.tm-legend-foot { margin: var(--space-3) 0 0; padding-block-start: var(--space-2); border-block-start: 1px solid var(--color-border); line-height: var(--leading-body); }

/* -------------------------------------------------------------------------- the command bar */

/*
 * Top-right, not top-centre.
 *
 * Centred, it sat exactly where a layered graph puts its busiest column and covered two nodes outright
 * — a floating control obscuring the drawing it exists to interrogate. The three other corners are
 * already spoken for (legend top-left, zoom controls bottom-left, minimap bottom-right), so this is
 * the one that was free, and it is furthest from the leftmost column where `layered` starts drawing.
 */
.tm-cmd-pill-wrap {
  position: absolute;
  inset-block-start: var(--space-3);
  inset-inline-end: var(--space-3);
  display: grid;
  justify-items: end;
  gap: var(--space-2);
  inline-size: min(20rem, calc(100% - 2 * var(--space-3)));
}

.tm-cmd-pill {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  inline-size: 100%;
  color: var(--color-text-subtle);
  font: inherit;
  font-size: var(--text-sm);
  block-size: 2rem;
  padding: 0 var(--space-2) 0 var(--space-3);
  border-radius: var(--radius-sm);
  cursor: text;
  text-align: start;
}

.tm-cmd-pill:hover { color: var(--color-text-muted); border-color: var(--color-border-strong); }
.tm-cmd-pill-text { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.tm-cmd-icon {
  inline-size: 0.875rem;
  block-size: 0.875rem;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
}

.tm-kbd {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 1;
  background: var(--color-sunken);
  border: 1px solid var(--color-border);
  border-radius: 5px;
  padding: 3px 5px;
  color: var(--color-text-subtle);
  flex: none;
}

.tm-cmd-feedback {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  border-radius: 8px;
  padding: var(--space-1) var(--space-3);
}

.tm-cmd-scrim {
  position: fixed;
  inset: 0;
  background: var(--color-scrim);
  display: grid;
  justify-items: center;
  align-content: start;
  padding-block-start: 12vh;
  z-index: 60;
}

.tm-cmd-dialog {
  inline-size: min(36rem, calc(100vw - 2rem));
  background: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-lg);
  /* A literal, not `var(--shadow-lg, ...)`. There is no `--shadow-lg` in this stylesheet, so the
     fallback was always what rendered and the token was a fiction a reader would go looking for.
     `clock.test.ts`' sibling check catches exactly that. */
  box-shadow: 0 24px 64px rgb(0 0 0 / 0.5);
  overflow: hidden;
}

.tm-cmd-input {
  inline-size: 100%;
  border: 0;
  border-block-end: 1px solid var(--color-border);
  background: transparent;
  color: var(--color-text);
  font: inherit;
  font-size: var(--text-lg);
  padding: var(--space-4) var(--space-5);
}

.tm-cmd-input:focus { outline: none; }
.tm-cmd-input::-moz-placeholder { color: var(--color-text-subtle); }
.tm-cmd-input::placeholder { color: var(--color-text-subtle); }

.tm-cmd-list { list-style: none; margin: 0; padding: var(--space-2); max-block-size: 20rem; overflow-y: auto; }

.tm-cmd-item {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  inline-size: 100%;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  border: 0;
  background: transparent;
  color: var(--color-text);
  font: inherit;
  font-size: var(--text-base);
  padding: var(--space-2) var(--space-3);
  border-radius: 8px;
  cursor: pointer;
  text-align: start;
}

.tm-cmd-item[data-active="yes"] { background: var(--color-hover); }
.tm-cmd-item-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tm-cmd-item-hint { flex: none; font-size: var(--text-xs); color: var(--color-text-subtle); font-family: var(--font-mono); }
.tm-cmd-none { padding: var(--space-3); font-size: var(--text-base); color: var(--color-text-muted); }

.tm-cmd-foot {
  border-block-start: 1px solid var(--color-border);
  background: var(--color-sunken);
  padding: var(--space-2) var(--space-5);
  font-size: var(--text-xs);
  color: var(--color-text-subtle);
}

/* ----------------------------------------------------------------------------- the inspector */

/*
 * Docked into the stage, not floating beside it as a second card: it shares the canvas's height and
 * its left edge is the canvas's right edge, so it reads as a side of the same workbench. It scrolls
 * inside itself rather than growing the page — a panel that pushes the timeline down every time a
 * node is clicked moves the thing the reader is about to scrub.
 */
.tm-inspector,
.tm-investigation {
  min-width: 0;
  border-inline-start: 1px solid var(--color-border);
  background: var(--color-surface);
  padding: var(--space-4) var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  max-block-size: var(--tm-canvas-h);
  overflow-y: auto;
  overscroll-behavior: contain;
  animation: tm-panel-in var(--motion-slow) var(--ease-out);
}

@keyframes tm-panel-in {
  from { opacity: 0; transform: translateX(8px); }
  to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .tm-inspector, .tm-investigation { animation: none; }
}

@media (max-width: 1100px) {
  .tm-inspector,
  .tm-investigation {
    border-inline-start: 0;
    border-block-start: 1px solid var(--color-border);
    max-block-size: none;
  }
}

.tm-insp-head { display: grid; gap: var(--space-2); }
.tm-insp-title { display: flex; align-items: center; gap: var(--space-3); }

/* The node's own mark, on a plate in its band — the panel's anchor to what was clicked. */
.tm-insp-glyph {
  --band: var(--tm-unknown);
  flex: none;
  inline-size: 2.25rem;
  block-size: 2.25rem;
  display: grid;
  place-items: center;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--band) 12%, var(--color-sunken));
  border: 1px solid color-mix(in srgb, var(--band) 35%, transparent);
}

.tm-insp-glyph[data-band="healthy"] { --band: var(--tm-ok); }
.tm-insp-glyph[data-band="degraded"] { --band: var(--tm-warn); }
.tm-insp-glyph[data-band="critical"] { --band: var(--tm-critical); }
.tm-insp-glyph svg { inline-size: 1.5rem; block-size: 1.5rem; fill: color-mix(in srgb, var(--band) 20%, transparent); stroke: var(--band); stroke-width: 2; }

.tm-insp-name {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  font-size: var(--text-xl);
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: var(--leading-heading);
  /* The one place a long name must not be truncated into ambiguity: wrap instead. A service called
     `payment-service-canary-eu-west` is not usefully shown as `payment-serv…`. */
  overflow-wrap: anywhere;
}

.tm-insp-close {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  border: 0;
  background: transparent;
  color: var(--color-text-subtle);
  inline-size: var(--size-control-sm);
  block-size: var(--size-control-sm);
  border-radius: 8px;
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
  flex: none;
  align-self: start;
}

.tm-insp-close:hover { color: var(--color-text); background: var(--color-hover); }
.tm-insp-close:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 1px; }

.tm-insp-sub { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); font-size: var(--text-sm); color: var(--color-text-subtle); }
.tm-insp-kind { color: var(--color-text-muted); }
.tm-insp-ns { font-family: var(--font-mono); font-size: var(--text-xs); padding: 2px 6px; border-radius: 6px; background: var(--color-sunken); border: 1px solid var(--color-border); }

.tm-insp-blame {
  margin: 0;
  font-size: var(--text-sm);
  line-height: var(--leading-body);
  color: var(--critical-text);
  background: color-mix(in srgb, var(--tm-critical) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--tm-critical) 30%, transparent);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
}

.tm-insp-body { display: grid; gap: var(--space-4); }
.tm-insp-note { margin: 0; font-size: var(--text-sm); line-height: var(--leading-body); color: var(--color-text-muted); }
/* A pod's newest lines in the App graph inspector (tracemesh/inspector.tsx). Wrapped, never parsed. */
.tm-logs { display: grid; gap: var(--space-2); min-width: 0; }
.tm-logs-pre {
  margin: 0; max-height: 22rem; overflow-x: hidden; overflow-y: auto; padding: var(--space-2);
  border: 1px solid var(--color-border); border-radius: var(--radius-md);
  font-family: var(--font-mono); font-size: var(--text-xs); line-height: 1.5;
  color: var(--color-text-muted); white-space: pre-wrap; overflow-wrap: anywhere;
}
.tm-logs-line[data-level="error"] { color: var(--critical-text, var(--critical)); }
.tm-logs-line[data-level="warn"] { color: var(--warn-text); }

.tm-insp-gap {
  margin: 0;
  font-size: var(--text-sm);
  line-height: var(--leading-body);
  color: var(--warn-text);
  background: color-mix(in srgb, var(--tm-warn) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--tm-warn) 28%, transparent);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
}

.tm-insp-actions { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-2); }

/*
 * The figures as a tiled sheet: hairlines between tiles drawn by a 1px gap over a border-coloured
 * plate, so every tile is the same size and the lines never double up.
 */
.tm-metrics {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1px;
  margin: 0;
  background: var(--color-border);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.tm-figure { min-width: 0; background: var(--color-surface); padding: var(--space-2) var(--space-3); }
.tm-figure dt { font-size: var(--text-xs); color: var(--color-text-subtle); text-transform: uppercase; letter-spacing: 0.05em; }
.tm-figure dd { margin: 2px 0 0; font-family: var(--font-mono); font-size: var(--text-lg); color: var(--color-text); font-variant-numeric: tabular-nums; }
/* An absent figure is dimmed as well as dashed, so it does not read as a measured zero at a glance. */
.tm-figure[data-absent="yes"] dd { color: var(--color-text-subtle); }

.tm-score { display: flex; align-items: baseline; gap: var(--space-2); }
.tm-score-value { font-family: var(--font-mono); font-size: var(--text-3xl); font-weight: 500; line-height: 1; color: var(--tm-unknown); font-variant-numeric: tabular-nums; }
.tm-score-value[data-band="healthy"] { color: var(--ok-text); }
.tm-score-value[data-band="degraded"] { color: var(--warn-text); }
.tm-score-value[data-band="critical"] { color: var(--critical-text); }
.tm-score-band { font-size: var(--text-base); color: var(--color-text-muted); }

.tm-factors { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-3); }
.tm-factor { display: grid; grid-template-columns: 5.5rem minmax(0, 1fr); gap: var(--space-1) var(--space-3); align-items: center; font-size: var(--text-sm); }
.tm-factor-label { color: var(--color-text); }
.tm-factor-bar { block-size: var(--size-bar); background: var(--color-sunken); border-radius: var(--radius-full); overflow: hidden; }
.tm-factor-fill { display: block; block-size: 100%; border-radius: inherit; background: var(--color-text-muted); }
.tm-factor-note { grid-column: 2; color: var(--color-text-subtle); font-size: var(--text-xs); }
.tm-factor[data-absent="yes"] .tm-factor-absent { color: var(--color-text-subtle); font-style: italic; }

.tm-side { display: grid; gap: var(--space-2); }
.tm-side-title { margin: 0; font-size: var(--text-xs); font-weight: 500; text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-subtle); }
.tm-side-sub { text-transform: none; letter-spacing: 0; font-weight: 400; }
.tm-side-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }

.tm-dep {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  inline-size: 100%;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-2);
  border: 1px solid transparent;
  background: transparent;
  color: var(--color-text);
  font: inherit;
  font-size: var(--text-base);
  padding: var(--space-2);
  border-radius: 8px;
  cursor: pointer;
  text-align: start;
}

.tm-dep:hover { background: var(--color-hover); border-color: var(--color-border); }
.tm-dep:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 1px; }
.tm-dep-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tm-dep-figures { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-text-subtle); font-variant-numeric: tabular-nums; }

.tm-dep-dot { inline-size: 0.5rem; block-size: 0.5rem; border-radius: 50%; background: var(--tm-unknown); flex: none; }
.tm-dep-dot[data-band="healthy"] { background: var(--tm-ok); }
.tm-dep-dot[data-band="degraded"] { background: var(--tm-warn); box-shadow: 0 0 6px var(--tm-warn); }
.tm-dep-dot[data-band="critical"] { background: var(--tm-critical); box-shadow: 0 0 6px var(--tm-critical); }

/* ------------------------------------------------------------------------- the investigation */

.tm-inv-head { display: flex; align-items: start; justify-content: space-between; gap: var(--space-2); }
.tm-inv-title { margin: 0; font-size: var(--text-xl); font-weight: 600; letter-spacing: -0.01em; }
.tm-inv-sentence { margin: var(--space-1) 0 0; font-size: var(--text-base); line-height: var(--leading-body); color: var(--color-text-muted); }
.tm-inv-sub { margin: var(--space-2) 0 0; font-size: var(--text-xs); font-weight: 500; text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-subtle); }
.tm-inv-note { margin: 0; font-size: var(--text-sm); line-height: var(--leading-body); color: var(--color-text-muted); }

/* The ring and its plain-language reading, side by side and vertically centred on each other. The
   disclaimer is no longer in this grid — it is `.tm-inv-note` at full width below, where a long
   sentence does not force the arc into a narrow column. */
.tm-inv-confidence { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--space-3); align-items: center; }

/* "5 of 6 checks held" — the claim, in words. The number alone invited being read as a probability
   that the named service is at fault, which is not what it says.
   Not a grid. Grid makes every inline child its own row, so `<strong>5</strong>` broke onto a line
   of its own and the sentence read "5" / "of 6 checks held" — two fragments where there is one
   claim. A block with the sub-line promoted to `display: block` keeps the sentence intact. */
.tm-inv-held {
  margin: 0;
  font-size: var(--text-md);
  line-height: 1.4;
  color: var(--color-text);
}
.tm-inv-held strong { font-family: var(--font-mono); font-size: 1.125rem; font-weight: 600; }
.tm-inv-heldsub { display: block; font-size: var(--text-xs); color: var(--color-text-subtle); }

/* The unit, stated once in the section heading. Lower-case and unspaced so it reads as an aside to
   the heading rather than as a second heading. */
.tm-inv-subnote {
  margin-inline-start: var(--space-2);
  text-transform: none;
  letter-spacing: 0;
  color: var(--color-text-subtle);
  font-weight: 400;
}

.tm-conf { position: relative; inline-size: 4rem; block-size: 4rem; display: grid; place-items: center; }
.tm-conf svg { position: absolute; inset: 0; inline-size: 100%; block-size: 100%; }
.tm-conf-track { stroke: var(--color-sunken); stroke-width: 5; }
.tm-conf-arc { stroke: var(--tm-unknown); stroke-width: 5; stroke-linecap: round; }
.tm-conf-arc[data-band="healthy"] { stroke: var(--ok); }
.tm-conf-arc[data-band="degraded"] { stroke: var(--warn); }
.tm-conf-arc[data-band="critical"] { stroke: var(--critical); }
.tm-conf-value { font-family: var(--font-mono); font-size: var(--text-md); color: var(--color-text); }

.tm-inv-root, .tm-chain-node {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  border: 1px solid var(--color-border);
  background: var(--color-sunken);
  color: var(--color-text);
  font: inherit;
  font-size: var(--text-base);
  padding: var(--space-1) var(--space-3);
  border-radius: 8px;
  cursor: pointer;
}

.tm-inv-root:hover, .tm-chain-node:hover { background: var(--color-hover); border-color: var(--color-border-strong); }
.tm-inv-root:focus-visible, .tm-chain-node:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 1px; }
.tm-inv-rootname { font-family: var(--font-mono); }

.tm-checks { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-1); }

/*
 * A check that could not be run has to read differently at a glance, not just carry a different
 * glyph. Five greens and one grey tick in an otherwise identical row is a difference nobody scanning
 * the list sees, and "could not be checked" is exactly the row a sceptical reader is looking for.
 *
 * A tinted plate and an inset rule rather than red type: it is not a failure, it is an absence of
 * evidence, and colouring it like a fault would overstate it.
 *
 * Every row carries the padding and a transparent left rule, so only the colour changes on the one
 * that could not be checked. Padding the failed row alone — which is what this did first — shifted
 * it out of line with the five above and below it, and a row that is a different shape reads as a
 * different kind of thing rather than as the same check with a different outcome.
 */
.tm-check {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--space-2);
  align-items: start;
  font-size: var(--text-sm);
  padding: var(--space-1) var(--space-2);
  border-inline-start: 2px solid transparent;
  border-radius: 6px;
}

.tm-check[data-passed="no"] {
  background: var(--color-sunken);
  border-inline-start-color: var(--color-border-strong);
}

.tm-check-mark { font-size: var(--text-md); line-height: 1.3; color: var(--ok-text); }
.tm-check[data-passed="no"] .tm-check-mark { color: var(--color-text-subtle); }
.tm-check-body { display: grid; gap: 1px; min-width: 0; }
.tm-check-label { color: var(--color-text); }
.tm-check[data-passed="no"] .tm-check-label { color: var(--color-text-muted); }
.tm-check-detail { color: var(--color-text-subtle); }
.tm-check-weight { font-family: var(--font-mono); color: var(--color-text-subtle); }

.tm-chain { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-1); }
.tm-chain-step { display: grid; gap: 2px; justify-items: start; }
.tm-chain-arrow {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--critical-text);
  /* A rule before the badge, so the chain reads as a sequence without an icon per step. */
  padding-inline-start: var(--space-3);
  border-inline-start: 2px solid color-mix(in srgb, var(--tm-critical) 40%, transparent);
  margin-inline-start: var(--space-3);
}

.tm-inv-unexplained, .tm-inv-degraded, .tm-inv-dropped {
  margin: 0;
  font-size: var(--text-sm);
  line-height: var(--leading-body);
  color: var(--warn-text);
  background: color-mix(in srgb, var(--tm-warn) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--tm-warn) 28%, transparent);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
}

.tm-inv-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.tm-inv-model { border-block-start: 1px solid var(--color-border); padding-block-start: var(--space-3); display: grid; gap: var(--space-2); }
.tm-inv-modelhead { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.tm-inv-modelnote { font-size: var(--text-xs); color: var(--color-text-subtle); }
.tm-inv-answer { margin: 0; font-size: var(--text-base); color: var(--color-text); font-family: var(--font-ai, inherit); line-height: 1.6; }

/*
 * A citation, as a chip carrying the measurement's name instead of its id.
 *
 * Inline and baseline-aligned, because it sits mid-sentence and must not break the line rhythm — the
 * raw `【chain.suspects】` it replaces did not, and a chip with vertical padding would. It is an
 * anchor rather than a button so it works with the panel closed and with JavaScript still loading.
 */
.tm-cite {
  display: inline;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-primary-text);
  text-decoration: none;
  border-block-end: 1px dotted currentColor;
  padding-inline: 2px;
  white-space: nowrap;
}
.tm-cite::before { content: "\2197\00a0"; opacity: 0.7; }
.tm-cite:hover, .tm-cite:focus-visible { background: var(--color-hover); }

.tm-ev { font-size: var(--text-sm); }
.tm-ev-summary { cursor: pointer; color: var(--color-text-muted); }
.tm-ev-summary:hover { color: var(--color-text); }
.tm-ev-body { display: grid; gap: var(--space-2); padding-block-start: var(--space-2); }

.tm-ev-block {
  border: 1px solid var(--color-border);
  border-radius: 8px;
  padding: var(--space-2) var(--space-3);
  min-width: 0;
}
/* The ones the answer actually leant on, so following a chip lands somewhere visibly marked. */
.tm-ev-block.is-cited { border-color: var(--color-primary); }
.tm-ev-block:target { background: var(--color-hover); }

.tm-ev-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2); }
.tm-ev-title { color: var(--color-text); }
.tm-ev-id { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-text-subtle); }

/*
 * Wrapped, never parsed — the rule `sre/chat.tsx` sets out at length. These bodies are free text
 * from a cluster and contain the separators a parser would split on; wrapping changes no characters.
 */
.tm-ev-pre {
  margin: var(--space-1) 0 0;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* Ask Kairos, the App graph's answer popup (tracemesh/ask-kairos.tsx). The question sent is folded
   above the answer: the investigation's is a paragraph of context nobody needs to read first. */
.kairos-ask { display: grid; gap: var(--space-3); min-width: 0; }
.kairos-ask-question { font-size: var(--text-sm); color: var(--color-text-muted); }
.kairos-ask-question > summary { cursor: pointer; }
.kairos-ask-question > summary:hover { color: var(--color-text); }
.kairos-ask-question > p { margin: var(--space-2) 0 0; line-height: 1.55; overflow-wrap: anywhere; }

/* ------------------------------------------------------------------------------ the timeline */

/* Docked along the bottom of the workbench, so scrubbing reads as moving the picture above it. */
.tm-timeline {
  border-block-start: 1px solid var(--color-border);
  background: var(--color-surface);
  padding: var(--space-3) var(--space-5) var(--space-3);
  display: grid;
  gap: var(--space-2);
}

.tm-tl-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-2); }

.tm-tl-when {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  font-weight: 500;
  font-family: var(--font-mono);
  color: var(--color-text);
  padding: 2px var(--space-2);
  border-radius: var(--radius-full);
  border: 1px solid var(--color-border);
  background: var(--color-sunken);
}

.tm-tl-when[data-live="yes"] {
  color: var(--ok-text);
  border-color: color-mix(in srgb, var(--tm-ok) 35%, transparent);
  background: color-mix(in srgb, var(--tm-ok) 10%, transparent);
}

.tm-tl-when[data-live="no"] {
  color: var(--warn-text);
  border-color: color-mix(in srgb, var(--tm-warn) 35%, transparent);
  background: color-mix(in srgb, var(--tm-warn) 10%, transparent);
}

.tm-tl-dot {
  inline-size: 0.4375rem;
  block-size: 0.4375rem;
  border-radius: 50%;
  background: var(--ok);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--tm-ok) 25%, transparent);
  animation: tm-pulse 2s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) { .tm-tl-dot { animation: none; } }

.tm-tl-controls { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.tm-tl-hint, .tm-tl-loading { font-size: var(--text-xs); color: var(--color-text-subtle); }
/* Reserved width and a fade, so "reading…" coming and going never moves the buttons beside it. */
.tm-tl-loading { min-inline-size: 7ch; opacity: 0; transition: opacity var(--motion-base) ease; }
.tm-tl-loading[data-on="yes"] { opacity: 1; }

.tm-tl-track { position: relative; padding-block-start: 1.25rem; }

.tm-tl-ticks { position: absolute; inset-block-start: 0; inset-inline: 0; block-size: 1rem; }
.tm-tl-tick {
  position: absolute;
  transform: translateX(-50%);
  font-family: var(--font-mono);
  font-size: 0.625rem;
  color: var(--color-text-subtle);
  white-space: nowrap;
}
/* The first and last labels hang inward, so neither is cut by the frame's edge. */
.tm-tl-tick:first-child { transform: none; }
.tm-tl-tick:last-child { transform: translateX(-100%); }

.tm-tl-markers { position: absolute; inset-inline: 0; inset-block-start: calc(1.25rem - 2px); block-size: 1rem; pointer-events: none; z-index: 1; }

.tm-tl-marker {
  position: absolute;
  transform: translateX(-50%);
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  border: 0;
  background: transparent;
  color: var(--color-text-muted);
  font-size: 0.625rem;
  line-height: 1;
  padding: 2px;
  cursor: pointer;
  pointer-events: auto;
}

.tm-tl-marker[data-tone="warn"] { color: var(--warn); }
.tm-tl-marker[data-tone="critical"] { color: var(--critical); }
.tm-tl-marker[data-selected="yes"] { color: var(--color-text); transform: translateX(-50%) scale(1.5); }
.tm-tl-marker:hover { color: var(--color-text); }
.tm-tl-marker:focus-visible { outline: 2px solid var(--color-focus-ring); border-radius: 3px; }

/*
 * Left as a native range on purpose: arrow keys, Home and End, and an announced value, all free.
 * Only its paint is replaced — a thin rail and a round handle — so it matches the frame it sits in.
 */
.tm-tl-range {
  -webkit-appearance: none;
  -moz-appearance: none;
       appearance: none;
  inline-size: 100%;
  block-size: 1.25rem;
  margin: 0;
  background: transparent;
  cursor: pointer;
}

.tm-tl-range::-webkit-slider-runnable-track {
  block-size: 4px;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--color-text) 16%, transparent);
}

.tm-tl-range::-moz-range-track {
  block-size: 4px;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--color-text) 16%, transparent);
}

.tm-tl-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  inline-size: 14px;
  block-size: 14px;
  margin-block-start: -5px;
  border-radius: 50%;
  background: var(--color-text);
  border: 3px solid var(--color-surface);
  box-shadow: 0 0 0 1px var(--color-border-strong);
}

.tm-tl-range::-moz-range-thumb {
  inline-size: 14px;
  block-size: 14px;
  border-radius: 50%;
  background: var(--color-text);
  border: 3px solid var(--color-surface);
  box-shadow: 0 0 0 1px var(--color-border-strong);
}

.tm-tl-range:focus-visible { outline: none; }
.tm-tl-range:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 3px var(--color-focus-ring); }
.tm-tl-range:focus-visible::-moz-range-thumb { box-shadow: 0 0 0 3px var(--color-focus-ring); }

.tm-tl-changes { border-block-start: 1px solid var(--color-border); padding-block-start: var(--space-2); display: grid; gap: var(--space-1); }
.tm-tl-changes-title { margin: 0; font-size: var(--text-base); font-weight: 600; }
.tm-tl-delta-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.tm-tl-delta { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--space-2); font-size: var(--text-sm); color: var(--color-text-muted); }
.tm-tl-delta-dir { font-family: var(--font-mono); color: var(--color-text-subtle); }
.tm-tl-delta-dir[data-up="yes"] { color: var(--warn-text); }


/* The glyph inside a timeline marker. Sized here rather than inline so the triangle and the diamond
   share one optical weight — at the same font-size the diamond reads noticeably heavier. */
.tm-tl-marker-glyph { display: block; font-size: 0.5rem; line-height: 1; }

/* ---------------------------------------------------------------------------- blind spots */

/* A footnote strip between the drawing and the timeline: what this picture could not show. */
.tm-blindspots {
  border-block-start: 1px solid var(--color-border);
  background: var(--color-sunken);
  padding: var(--space-2) var(--space-5);
  display: grid;
  gap: 2px;
}

.tm-blindspots p { margin: 0; font-size: var(--text-sm); color: var(--color-text-muted); }
.tm-blindspots p::before { content: "\25CB\00a0\00a0"; color: var(--color-text-subtle); }

.tm-empty-links { font-size: var(--text-base); }

@media (max-width: 720px) {
  /* The furniture stacks off the canvas on a phone: four floating panels over a 26rem drawing leaves
     no drawing. The dock stays, because pinch-zoom is not reliable inside an SVG with touch-action. */
  .tm-minimap, .tm-legend { display: none; }
  /* In flow and *above* the drawing: placed after it, the zoom dock floats over it. */
  .tm-canvas-wrap { display: flex; flex-direction: column; }
  .tm-cmd-pill-wrap { position: static; order: -1; transform: none; inline-size: auto; max-inline-size: none; margin: var(--space-2) var(--space-2) 0; }
  /* Seven clock labels do not fit in 22rem; the two ends say the span, and the handle says the rest. */
  .tm-tl-tick:not(:first-child):not(:last-child) { display: none; }
  .tm-workbench { --tm-canvas-h: clamp(20rem, 52vh, 30rem); }
  .tm-bar { padding: var(--space-2); }
  .tm-levels, .tm-modes { max-inline-size: 100%; overflow-x: auto; overscroll-behavior-x: contain; }
  .tm-bar-label { display: none; }
  .tm-tool-right { margin-inline-start: 0; }
  .tm-timeline { padding-inline: var(--space-3); }
}

/* ------------------------------------------------- TraceMesh: the layer groups and two panels

   These selectors exist because `tracemesh.test.ts` caught them being drawn with no rule
   behind them. Each got a rule that does something rather than an empty block: a class with a
   placeholder rule satisfies a scan while teaching the next reader that the scan is theatre. */

/*
 * Edges take pointer events on their stroke only.
 *
 * A curved edge is a `<path>` whose bounding box can cover a quarter of the canvas, and the default
 * `visiblePainted` already restricts hits to painted pixels — but stating it is the difference
 * between relying on a default and depending on one. A node sitting inside an edge's box must stay
 * clickable, and `<title>` hover on the edge must still work, which rules out `none`.
 */
.tm-edges { pointer-events: visibleStroke; }

/* Nodes sit above the edges and take events normally; the group is here so the canvas has one
   handle for the layer, and so a future hit-test change has a single place to go. */
.tm-nodes { pointer-events: auto; }

/*
 * The health bars are decoration for a figure already written above them, so they recede.
 *
 * They are `aria-hidden` in the markup for the same reason: every value they encode is in the node's
 * accessible name and its `<title>`, and four unlabelled bars announced one after another is noise
 * that buries the name.
 */
.tm-node-bars { opacity: 0.85; pointer-events: none; }

/* The saturation note is deliberately quieter than the scored factors above it, because it is the one
   figure on this panel that is reported and *not* part of the score. */
.tm-sat {
  border-block-start: 1px solid var(--color-border);
  padding-block-start: var(--space-3);
}

/* ---------------------------------------------------------------- showing the working

 * One drawing for "here is what I measured", used by every surface that proposes a change.
 *
 * Deliberately not the Changes page's or the Diagnose tab's own classes. Those carry per-page
 * states (waiting, not reached, unknown) this does not have, and borrowing them would mean a
 * restyle of one page silently restyling three others.
 */
.rsn-trail { list-style: none; margin: var(--space-2) 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.rsn-step {
  padding: var(--space-3);
  border: 1px solid var(--border-hairline);
  border-radius: 4px;
  background: var(--bg-surface);
  min-width: 0;
}
.rsn-step-head { display: flex; align-items: baseline; gap: var(--space-2); }
.rsn-step-mark { font-size: 13px; line-height: 1; flex: none; width: 14px; text-align: center; color: var(--ok-text); }
.rsn-step-key { font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }
.rsn-step-flag { font-size: 11px; margin-left: auto; color: var(--critical-text); font-weight: 600; }
.rsn-step-q { margin: 2px 0 0 22px; font-size: 11px; color: var(--text-muted); }
.rsn-step-a { margin: var(--space-2) 0 0 22px; font-size: 12px; line-height: 1.55; overflow-wrap: anywhere; }
/*
 * Quoted lines from a cluster, a chart or a database row. Never truncated and never wrapped mid
 * token: the one thing a reader may want to do with these is compare them against the real thing,
 * so they scroll instead — contained on x, or reaching the end of one would slide the page.
 */
.rsn-step-evidence {
  margin: var(--space-2) 0 0 22px;
  padding: var(--space-2) var(--space-3);
  list-style: none;
  display: grid;
  gap: 2px;
  font-size: 11px;
  line-height: 1.6;
  color: var(--text-secondary);
  background: var(--bg-sunken);
  border-radius: 3px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  /*
   * The track has to be allowed to shrink, or `overflow-x: auto` above never engages.
   *
   * Auto-placement gives this grid a single implicit track sized `auto`, whose minimum is the
   * min-content of a `white-space: pre` line — so the box grew to the width of its longest evidence
   * line and pushed the dialog out instead of scrolling inside it. Measured at 294px, which was the
   * last thing holding the resource panel above a phone's width.
   */
  grid-template-columns: minmax(0, 1fr);
  min-width: 0;
}
.rsn-step-evidence li { white-space: pre; }
.rsn-step-source { margin: var(--space-2) 0 0 22px; font-size: 11px; line-height: 1.5; color: var(--text-muted); }
.rsn-step.is-cause {
  border-color: color-mix(in srgb, var(--critical) 45%, transparent);
  background: color-mix(in srgb, var(--critical) 6%, var(--bg-surface));
}
.rsn-step.is-cause .rsn-step-mark { color: var(--critical-text); }

/*
 * The chain, with the actor in a column of its own.
 *
 * A column rather than a clause, because the point being made is that most of these links are not
 * this product — and that is only visible if scanning the column is enough to see it.
 */
.rsn-chain { list-style: none; margin: var(--space-2) 0 0; padding: 0; display: grid; gap: 1px; counter-reset: rsn-link; }
.rsn-link {
  display: grid;
  grid-template-columns: 7.5rem 1fr;
  gap: var(--space-3);
  align-items: baseline;
  padding: var(--space-2) var(--space-3);
  border-left: 2px solid var(--border-hairline);
  background: var(--bg-sunken);
}
.rsn-actor { font-size: 10px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-muted); }
.rsn-link-body { display: grid; gap: 2px; min-width: 0; }
.rsn-link-what { font-size: 12px; font-weight: 600; line-height: 1.5; counter-increment: rsn-link; }
.rsn-link-what::before { content: counter(rsn-link) ". "; color: var(--text-muted); font-weight: 400; }
.rsn-link-detail { font-size: 11px; line-height: 1.6; color: var(--text-muted); overflow-wrap: anywhere; }
/* This product acts without asking; the other two do not. The left edge says which before the words do. */
.rsn-link.is-you { border-left-color: var(--warn); }
.rsn-link.is-you .rsn-actor { color: var(--warn-text); }
.rsn-link.is-your-cluster { border-left-color: var(--color-primary-text); }

@media (max-width: 560px) {
  /* The actor column cannot hold a word and a sentence side by side on a phone. */
  .rsn-link { grid-template-columns: 1fr; gap: 2px; }
}

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

 * Direction, not state — grouped by the question each measurement answers.
 *
 * Two decisions worth writing down. The sparkline **draws the comparison**: the recent window is
 * shaded and each half's median is a rule, so the step between the rules is the percentage claimed
 * beside it. Every other trend dashboard draws a line and asserts a direction underneath, and this
 * product's whole position is that a conclusion has to be checkable.
 *
 * And the grid is deliberately asymmetric. The worst mover in each group spans two columns, because
 * a wall of identical cards makes a reader scan all ten before learning anything.
 */
.tr-note {
  margin: 0 0 var(--space-5);
  font-size: 12px;
  line-height: 1.65;
  color: var(--text-muted);
  max-width: 76ch;
  text-wrap: pretty;
}
.tr-group { margin-bottom: var(--space-6); }
.tr-group-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-bottom: var(--space-3);
  padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--border-hairline);
}
.tr-group-head h2 { margin: 0; font-size: 14px; font-weight: 600; letter-spacing: -0.01em; }
.tr-group-head p { margin: 0; font-size: 12px; color: var(--text-muted); }
/*
 * `auto-fit`, not `auto-fill`: a group of two — one wide card and one narrow — left an empty track
 * beside them on a four-column row, which read as a card that failed to load. Fit collapses the
 * unused tracks and lets the cards share the row.
 */
/* `min(…, 100%)`: on a phone the content column is narrower than 19rem, and a bare minimum made
   every card wider than the screen, clipped at its right edge. */
.tr-grid { display: grid; gap: var(--space-4); grid-template-columns: repeat(auto-fit, minmax(min(19rem, 100%), 1fr)); }

.tr-card {
  /*
   * Flex rather than grid, for one reason: `.tr-why` takes `margin-top: auto` and sits on the
   * bottom edge. A grid row is as tall as its tallest card, so the short card beside a wide one
   * with a suggestion paragraph was leaving its link floating in the middle with 80px of dead
   * space under it, and the links across a row landed at four different heights.
   */
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
  min-width: 0;
  /* Softer on the container than on anything inside it, so the card reads as the outer surface. */
  border-radius: 8px;
  border: 1px solid var(--border-hairline);
  border-left-width: 3px;
  border-left-color: var(--border-hairline);
  background: var(--bg-surface);
  /* Tinted rather than black at low opacity — the shadow carries the surface's own hue. */
  box-shadow: 0 1px 2px var(--argo-shadow);
  transition: transform 200ms ease, box-shadow 200ms ease, border-color 200ms ease;
  animation: tr-enter 320ms ease both;
}
.tr-card:hover { transform: translateY(-2px); box-shadow: 0 6px 18px var(--argo-shadow); }
.tr-card.is-wide { grid-column: span 2; }
.tr-card.is-critical { border-left-color: var(--critical); }
.tr-card.is-warning { border-left-color: var(--warn); }
.tr-card.is-ready { border-left-color: var(--ok-text); }
.tr-card.is-info { border-left-color: var(--info-text); }

@keyframes tr-enter {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

.tr-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); }
.tr-label { margin: 0; font-size: 13px; font-weight: 600; letter-spacing: -0.01em; }
.tr-figures { margin: 0; display: flex; align-items: baseline; gap: var(--space-3); flex-wrap: wrap; }
/* Tabular figures: these numbers sit in a grid and must not shuffle sideways between reads. */
.tr-now { font-size: 26px; font-weight: 600; line-height: 1; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.tr-card.is-wide .tr-now { font-size: 32px; }
/* The trend card's former `.tr-was` / `.tr-window` labels lived here. The card stopped using both, and
   `.tr-window` is the trace chips' class: this later rule set every chip on Traces, Performance and
   the API console to 11px muted text with `margin-left: auto`, which spread a row of chips evenly
   across the whole page width. `trends/views.test.ts` forbids the card the class; `traces/page.test.ts`
   forbids any second rule for it. */

.tr-spark { width: 100%; height: 56px; display: block; overflow: visible; }
.tr-card.is-wide .tr-spark { height: 84px; }
/* The window the verdict is about. Faint: it is context for the line, not a second series. */
.tr-win { fill: var(--text-muted); opacity: 0.07; }
/*
 * The step. The two medians and the riser between them are the verdict, so they are the bold mark;
 * the daily readings are the evidence, drawn thin and quiet underneath. A series too short to have a
 * step keeps its readings at full strength, because then they are all there is.
 *
 * Colour is one custom property per tone, so the line, the step and the riser cannot disagree.
 * A flat series keeps the info hue rather than the grey an `unknown` row uses: it is a reading, and
 * the two states must not look identical at a glance.
 */
.tr-spark { --tr-c: var(--text-secondary); }
.tr-spark.is-critical { --tr-c: var(--critical); }
.tr-spark.is-warning { --tr-c: var(--warn); }
.tr-spark.is-ready { --tr-c: var(--ok-text); }
.tr-spark.is-info { --tr-c: var(--info-text); }
.tr-line { stroke: var(--tr-c); stroke-width: 1.75px; stroke-linejoin: round; stroke-linecap: round; }
.tr-spark.has-step .tr-line { stroke-width: 1.25px; opacity: 0.4; }
/* The earlier level is lighter than the recent one: the eye should land on where it is now. */
.tr-med { stroke: var(--tr-c); stroke-width: 2.5px; stroke-linecap: round; opacity: 0.55; }
.tr-med.is-now { opacity: 1; }
.tr-riser { stroke: var(--tr-c); stroke-width: 2.5px; stroke-linecap: round; opacity: 0.8; }
/*
 * No series. Said in the space the chart would have taken rather than by collapsing the card — a
 * grid whose cards change height depending on whether anything was collected is a grid that
 * reflows every time collection stops.
 */
.tr-spark-empty {
  height: 56px;
  display: grid;
  place-items: center;
  border-radius: 4px;
  border: 1px dashed var(--border-hairline);
  font-size: 11px;
  color: var(--text-muted);
}
.tr-suggestion { margin: 0; font-size: 12px; line-height: 1.6; color: var(--text-secondary); overflow-wrap: anywhere; text-wrap: pretty; }
.tr-why {
  background: none;
  border: 0;
  padding: 2px 0;
  font-size: 11px;
  font-weight: 600;
  color: var(--color-primary-text);
  -webkit-text-decoration: underline dotted;
          text-decoration: underline dotted;
  text-underline-offset: 3px;
  cursor: pointer;
  transition: opacity 150ms ease;
}
.tr-why:hover { opacity: 0.75; }
.tr-why:focus-visible { outline: 2px solid var(--color-primary-text); outline-offset: 3px; border-radius: 2px; }

/* Loading, shaped like what is coming. A spinner over nothing tells a reader nothing. */
.tr-skeleton { display: grid; gap: var(--space-4); grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr)); }
.tr-skel-card {
  height: 11rem;
  border-radius: 8px;
  border: 1px solid var(--border-hairline);
  background: var(--bg-surface);
  animation: tr-pulse 1.4s ease-in-out infinite;
}
@keyframes tr-pulse { 0%, 100% { opacity: 0.45; } 50% { opacity: 0.9; } }

@media (max-width: 720px) {
  /* A two-column span on a one-column grid leaves a card half off the screen. */
  .tr-card.is-wide { grid-column: span 1; }
  .tr-card.is-wide .tr-now { font-size: 26px; }
}
@media (prefers-reduced-motion: reduce) {
  /* Entry, pulse and lift are all decoration; none of them carries information. */
  .tr-card, .tr-skel-card { animation: none; }
  .tr-card { transition: none; }
  .tr-card:hover { transform: none; }
}

/* ---- a card's chart frame: the hover readout and the dates under the chart */
.tr-plot { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.tr-plot-area { position: relative; border-radius: 4px; touch-action: pan-y; }
.tr-plot-area[tabindex] { cursor: crosshair; }
.tr-plot-area[tabindex]:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
.tr-cross {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--text-muted);
  opacity: 0.55;
  transform: translateX(-0.5px);
  pointer-events: none;
}
/* An 8px dot with a 2px ring in the surface colour, so it stays legible where it crosses the line. */
.tr-cross-dot {
  position: absolute;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--text-secondary);
  box-shadow: 0 0 0 2px var(--bg-surface);
  transform: translate(-50%, -50%);
  pointer-events: none;
}
.tr-cross-dot.is-critical { background: var(--critical); }
.tr-cross-dot.is-warning { background: var(--warn); }
.tr-cross-dot.is-ready { background: var(--ok-text); }
.tr-cross-dot.is-info { background: var(--info-text); }
/* Inverted, so it reads as a transient label over the chart rather than as another card. */
.tr-readout {
  position: absolute;
  bottom: calc(100% + 6px);
  z-index: 3;
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  padding: 3px 8px;
  border-radius: 6px;
  background: var(--text-primary);
  color: var(--bg-surface);
  font-size: 11px;
  white-space: nowrap;
  transform: translateX(-50%);
  pointer-events: none;
  box-shadow: 0 4px 12px var(--argo-shadow);
}
.tr-readout.is-start { transform: none; }
.tr-readout.is-end { transform: translateX(-100%); }
.tr-readout strong { font-size: 12px; font-weight: 600; }
.tr-readout em { font-style: normal; opacity: 0.7; }
.tr-axis {
  position: relative;
  display: flex;
  justify-content: space-between;
  height: 14px;
  font-size: 10.5px;
  line-height: 14px;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
/* Where "recent" begins: the date the verdict's two windows meet, ticked up into the chart. */
.tr-axis-seam { position: absolute; top: 0; transform: translateX(-50%); color: var(--text-secondary); }
.tr-axis-seam::before {
  content: "";
  position: absolute;
  left: 50%;
  top: -5px;
  width: 1px;
  height: 4px;
  background: var(--border-strong);
}
/* Pinned to the bottom so the links across a row line up however tall each card's text is. */
.tr-foot { margin-top: auto; display: flex; align-items: baseline; justify-content: space-between; flex-wrap: wrap; gap: 4px var(--space-3); }
/* Each footer item keeps its words together; on a narrow card the row wraps between items, not inside them. */
.tr-foot > * { white-space: nowrap; }
.tr-count { font-size: 11px; color: var(--text-muted); font-variant-numeric: tabular-nums; }

/* =================================================================== trends > choosing a view */

/*
 * The gallery of ways to draw the trends, and every view it offers.
 *
 * Prefixed `tv-` rather than `tr-`: `tr-` is already shared with the trace waterfall (`.tr-window`
 * is defined by both), and a third owner of that prefix would make every rule here one rename away
 * from styling somebody else's page.
 *
 * Colour is one custom property, `--tv-c`, set from the move's tone. Every mark reads it, so a view
 * cannot colour a series differently from the card beside it.
 */
.tv-mark, .tv-key { --tv-c: var(--text-secondary); }
.tv-mark.is-critical, .tv-key.is-critical { --tv-c: var(--critical); }
.tv-mark.is-warning, .tv-key.is-warning { --tv-c: var(--warn); }
.tv-mark.is-ready, .tv-key.is-ready { --tv-c: var(--ok-text); }
.tv-mark.is-info, .tv-key.is-info { --tv-c: var(--info-text); }
.tv-mark.is-neutral, .tv-key.is-neutral { --tv-c: var(--text-muted); }
/* Context: a series that did not move. Present, so movers are read against it, and recessive. */
.tv-mark.is-context, .tv-key.is-context { --tv-c: var(--border-strong); }

/*
 * The view chooser. It was a strip of thirteen live thumbnails standing above the data on every
 * visit; it is now one button beside the view's name, and the thumbnails draw only while its menu is
 * open. The menu is anchored to the heading row rather than to the button, so on a phone it starts at
 * the content's edge instead of hanging off the right of the screen.
 */
.tv-lede-head {
  position: relative;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
  min-width: 0;
}
.tv-picker { min-width: 0; }
/*
 * The switcher: the view's name, its live thumbnail and a chevron, as one control. It replaced a
 * heading with a grey "Change view" button beside it, which read as two unrelated things.
 */
.tv-switch-heading { margin: 0; font: inherit; min-width: 0; max-width: 100%; }
.tv-switch {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  min-width: min(320px, 100%);
  max-width: 100%;
  padding: 6px 8px 6px 6px;
  border-radius: 10px;
  border: 1px solid var(--border-hairline);
  background: var(--bg-surface);
  color: var(--text-primary);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color 120ms ease, background 120ms ease;
}
.tv-switch:hover, .tv-switch[aria-expanded="true"] { border-color: var(--border-strong); background: var(--bg-sunken); }
.tv-switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The same canvas the menu's tiles use, so the switcher shows exactly the picture it will select. */
.tv-switch-thumb {
  position: relative;
  flex: none;
  width: 72px;
  height: 35px;
  overflow: hidden;
  border-radius: 6px;
  border: 1px solid var(--border-hairline);
  background: var(--bg-page);
}
.tv-switch-text { display: grid; gap: 2px; flex: 1 1 auto; min-width: 0; }
.tv-switch-eyebrow { font-size: 10.5px; font-weight: 600; line-height: 1.2; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-muted); }
.tv-switch-name {
  font-size: 15px;
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.01em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tv-switch-chevron {
  display: grid;
  place-items: center;
  flex: none;
  width: 28px;
  height: 28px;
  border-radius: 7px;
  background: var(--bg-sunken);
  color: var(--text-secondary);
  transition: transform 150ms ease, color 120ms ease;
}
.tv-switch:hover .tv-switch-chevron { color: var(--text-primary); }
.tv-switch[aria-expanded="true"] .tv-switch-chevron { transform: rotate(180deg); color: var(--text-primary); }
.tv-picker-menu {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: var(--z-raised);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  /* Seven 82px tiles and their gaps, plus the padding: the larger family fits on one row. */
  width: min(640px, 100%);
  max-height: min(70vh, 520px);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--space-3);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  box-shadow: var(--elev-dropdown);
}
/* The two families are a real split — one chart per series against one chart for all — so it is drawn. */
.tv-picker-group + .tv-picker-group { padding-top: var(--space-3); border-top: 1px solid var(--border-hairline); }
.tv-picker-group-head { display: flex; align-items: baseline; flex-wrap: wrap; gap: 2px var(--space-2); padding: 0 4px 6px; }
.tv-picker-group-title { font-size: 10.5px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-muted); }
.tv-picker-group-hint { font-size: 11px; color: var(--text-muted); }
.tv-picker-items { display: flex; flex-wrap: wrap; gap: 4px; }
.tv-picker-browse {
  align-self: flex-start;
  padding: 4px 6px;
  border: 0;
  border-radius: 4px;
  background: none;
  color: var(--color-primary-text);
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}
.tv-picker-browse:hover { background: var(--bg-sunken); }
.tv-picker-browse:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.tv-pick {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  width: 82px;
  padding: 5px 4px 6px;
  border-radius: 8px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-secondary);
  font: inherit;
  cursor: pointer;
  transition: background 120ms ease, border-color 120ms ease;
}
.tv-pick:hover { background: var(--bg-sunken); color: var(--text-primary); }
.tv-pick.is-on { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); color: var(--text-primary); }
.tv-pick:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.tv-pick-thumb {
  position: relative;
  display: block;
  width: 72px;
  height: 35px;
  overflow: hidden;
  border-radius: 4px;
  border: 1px solid var(--border-hairline);
  background: var(--bg-surface);
}
/* Drawn at the gallery tile's size and halved, so every thumbnail shrinks by the same factor. */
.tv-pick-canvas {
  position: absolute;
  left: 0;
  top: 0;
  width: 144px;
  height: 70px;
  padding: 7px;
  box-sizing: border-box;
  transform: scale(0.5);
  transform-origin: 0 0;
  pointer-events: none;
}
.tv-pick-name { font-size: 11px; line-height: 1.2; white-space: nowrap; }
.tv-pick.is-on .tv-pick-name { font-weight: 600; }
.tv-pick-check {
  position: absolute;
  top: 1px;
  right: 1px;
  display: grid;
  place-items: center;
  width: 16px;
  height: 16px;
  border-radius: 999px;
  border: 2px solid var(--bg-surface);
  background: var(--accent);
  color: var(--on-accent);
}
.tv-browse {
  flex: none;
  align-self: flex-start;
  padding: 6px 12px;
  border-radius: 6px;
  border: 1px solid var(--border-strong);
  background: var(--bg-surface);
  color: var(--text-primary);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}
.tv-browse:hover { background: var(--bg-sunken); }
.tv-browse:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* What the chosen view is, how to read it, and — folded — the rule behind every direction. */
.tv-lede { display: grid; gap: 4px; min-width: 0; }
.tv-method { font-size: 12px; }
.tv-method summary {
  width: -moz-max-content;
  width: max-content;
  max-width: 100%;
  font-weight: 600;
  color: var(--color-primary-text);
  cursor: pointer;
}
.tv-method summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
.tv-method .tr-note { margin: var(--space-2) 0 0; }

.tv-gallery {
  display: grid;
  grid-template-columns: 212px minmax(0, 1fr);
  gap: var(--space-4);
  margin-top: var(--space-3);
  padding: var(--space-4);
  border-radius: 8px;
  border: 1px solid var(--border-hairline);
  background: var(--bg-sunken);
}
.tv-filters { display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; }
.tv-chips { display: flex; flex-direction: column; gap: 6px; }
.tv-chips-label { font-size: 12px; font-weight: 600; color: var(--text-primary); }
.tv-chips-row { display: flex; flex-wrap: wrap; gap: 4px; }
.tv-chip {
  padding: 3px 9px;
  border-radius: 999px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-secondary);
  font-size: 11.5px;
  line-height: 1.4;
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease;
}
.tv-chip:hover { background: var(--bg-surface); color: var(--text-primary); }
.tv-chip.is-on { background: var(--accent); border-color: var(--accent); color: var(--on-accent); font-weight: 600; }
.tv-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.tv-sections {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  min-width: 0;
  padding: var(--space-4);
  border-radius: 8px;
  border: 1px solid var(--border-hairline);
  background: var(--bg-surface);
}
.tv-section { min-width: 0; }
.tv-section-head { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--space-2) var(--space-3); margin-bottom: var(--space-3); }
.tv-section-head h3 { margin: 0; font-size: 13px; font-weight: 600; }
.tv-section-head span { font-size: 11.5px; color: var(--text-muted); }
.tv-tiles { display: grid; gap: var(--space-3); grid-template-columns: repeat(auto-fill, minmax(136px, 1fr)); }
.tv-tile {
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding: 0;
  overflow: hidden;
  border-radius: 6px;
  border: 1px solid var(--border-hairline);
  background: var(--bg-surface);
  color: inherit;
  font: inherit;
  text-align: center;
  cursor: pointer;
  transition: border-color 150ms ease, box-shadow 150ms ease, transform 150ms ease;
}
.tv-tile:hover { border-color: var(--border-strong); transform: translateY(-1px); box-shadow: 0 4px 12px var(--argo-shadow); }
.tv-tile.is-on { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.tv-tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The picture. Fixed height, so a tile whose data is empty is the same size as one whose is not. */
.tv-thumb {
  display: block;
  position: relative;
  height: 84px;
  padding: 8px;
  overflow: hidden;
  border-bottom: 1px solid var(--border-hairline);
  pointer-events: none;
}
.tv-thumb .tv-chart, .tv-thumb .tr-spark, .tv-thumb .tr-spark-empty, .tv-fit, .tv-fit .tr-spark,
.tv-pick-canvas .tv-chart, .tv-pick-canvas .tr-spark, .tv-pick-canvas .tr-spark-empty { height: 100%; }
.tv-thumb .tv-ba, .tv-pick-canvas .tv-ba { height: 100%; }
.tv-thumb .tv-ba-row, .tv-pick-canvas .tv-ba-row { grid-template-columns: minmax(0, 1fr); }
.tv-tile-name {
  display: block;
  padding: 6px 6px 7px;
  font-size: 11.5px;
  color: var(--text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.tv-tile.is-on .tv-tile-name { color: var(--text-primary); font-weight: 600; }
.tv-count, .tv-nomatch { margin: 0; font-size: 11.5px; color: var(--text-muted); }
.tv-link {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-weight: 600;
  color: var(--color-primary-text);
  -webkit-text-decoration: underline dotted;
          text-decoration: underline dotted;
  text-underline-offset: 3px;
  cursor: pointer;
}

.tv-caption { margin: var(--space-3) 0 var(--space-4); font-size: 12px; line-height: 1.6; color: var(--text-muted); max-width: 84ch; text-wrap: pretty; }
.tv-stage {
  position: relative;
  margin-bottom: var(--space-6);
  padding: var(--space-4);
  border-radius: 8px;
  border: 1px solid var(--border-hairline);
  background: var(--bg-surface);
}

/* -- per-series marks: thin lines, a wash for area, the move's colour on what the verdict is about */
.tv-chart { width: 100%; height: 56px; display: block; overflow: visible; }
.tr-card.is-wide .tv-chart { height: 84px; }
.tv-line { stroke: var(--tv-c); stroke-width: 2px; stroke-linejoin: round; stroke-linecap: round; }
.tv-line.is-faint { stroke-width: 1.25px; opacity: 0.45; }
.tv-fill { fill: var(--tv-c); opacity: 0.12; stroke: none; }
/* Zero-length round-capped lines: the stroke width is the dot's diameter. */
.tv-dot { stroke: var(--tv-c); stroke-width: 8px; stroke-linecap: round; }
.tv-dot.is-small { stroke-width: 4px; opacity: 0.7; }
.tv-col { fill: var(--text-muted); opacity: 0.35; }
.tv-col.is-now { fill: var(--tv-c); opacity: 1; }
.tv-hl { position: relative; }
.tv-hl-label {
  position: absolute;
  transform: translate(-50%, -135%);
  padding: 0 3px;
  border-radius: 3px;
  background: var(--bg-surface);
  font-size: 10.5px;
  color: var(--text-secondary);
  white-space: nowrap;
  pointer-events: none;
  font-variant-numeric: tabular-nums;
}
.tv-hl-label.is-bottom { transform: translate(-50%, 40%); }
.tv-ba { display: grid; gap: 8px; align-content: center; height: 56px; }
.tr-card.is-wide .tv-ba { height: 84px; }
.tv-ba-row { display: grid; grid-template-columns: 6.5rem minmax(0, 1fr) auto; align-items: center; gap: 8px; }
.tv-ba-label { font-size: 11px; color: var(--text-muted); }
.tv-ba-track { display: block; height: 12px; }
.tv-ba-bar { display: block; height: 100%; border-radius: 0 4px 4px 0; background: var(--text-muted); opacity: 0.45; }
.tv-ba-bar.is-now { background: var(--tv-c); opacity: 1; }
.tv-ba-value { font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums; }

/* -- estate charts */
.tv-estate { position: relative; width: 100%; min-width: 0; }
.tv-svg { display: block; overflow: visible; }
.tv-grid { stroke: var(--border-hairline); stroke-width: 1px; }
.tv-grid.is-base { stroke: var(--border-strong); }
.tv-axis { fill: var(--text-muted); font-size: 10.5px; font-variant-numeric: tabular-nums; }
.tv-axis-html { font-size: 10.5px; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.tv-iline { stroke: var(--tv-c); stroke-width: 2px; stroke-linejoin: round; stroke-linecap: round; }
.tv-iline.is-context { stroke-width: 1.25px; opacity: 0.7; }
.tv-svg.is-thumb { display: block; }
.tv-thumb .tv-iline { stroke-width: 1.5px; }
.tv-pick-canvas .tv-iline, .tv-pick-canvas .tv-slope { stroke-width: 2.5px; }
.tv-cross { stroke: var(--text-muted); stroke-width: 1px; }
.tv-hit { fill: transparent; cursor: crosshair; }
.tv-slope { stroke: var(--tv-c); stroke-width: 2px; stroke-linecap: round; }
.tv-mark.is-context .tv-slope { stroke-width: 1.25px; opacity: 0.7; }
/* The hit area is wider than the 2px line: nobody can land a pointer on a hairline. */
.tv-slope-hit { stroke: transparent; stroke-width: 14px; cursor: pointer; }
.tv-mark:hover > .tv-slope { stroke-width: 3px; }
.tv-end { fill: var(--tv-c); stroke: var(--bg-surface); stroke-width: 2px; }
.tv-end.is-start { fill: var(--text-muted); }
.tv-leader { stroke: var(--border-strong); stroke-width: 1px; }
.tv-slope-label { fill: var(--text-secondary); font-size: 11.5px; }

.tv-legend { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-top: var(--space-3); font-size: 12px; color: var(--text-secondary); }
.tv-legend-item { display: inline-flex; align-items: center; gap: 6px; }
.tv-legend-item strong { color: var(--text-primary); font-variant-numeric: tabular-nums; }
.tv-legend-note { flex-basis: 100%; font-size: 11.5px; color: var(--text-muted); }
/* Line keys, not boxes: at legend and tooltip density a filled box is ink doing a label's job. */
.tv-key { display: inline-block; flex: none; width: 14px; height: 3px; border-radius: 2px; background: var(--tv-c); vertical-align: middle; }

.tv-tip {
  position: absolute;
  z-index: 5;
  min-width: 150px;
  max-width: 300px;
  padding: 8px 10px;
  border-radius: 6px;
  border: 1px solid var(--border-strong);
  background: var(--bg-surface);
  box-shadow: 0 6px 18px var(--argo-shadow);
  font-size: 12px;
  pointer-events: none;
}
.tv-tip-title { margin-bottom: 4px; font-size: 11px; color: var(--text-muted); }
.tv-tip-row { display: flex; align-items: center; gap: 6px; min-width: 0; }
.tv-tip-row strong { color: var(--text-primary); font-variant-numeric: tabular-nums; }
.tv-tip-row span:last-child { color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.tv-sm { display: grid; gap: var(--space-3); grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr)); }
.tv-sm-tile {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
  padding: 10px 12px;
  border-radius: 6px;
  border: 1px solid var(--border-hairline);
  border-top: 2px solid var(--tv-c);
}
.tv-sm .tv-chart, .tv-sm .tr-spark-empty { height: 44px; }
.tv-sm-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; min-width: 0; }
.tv-sm-label { font-size: 12px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tv-sm-change { flex: none; font-size: 11px; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.tv-sm-now { font-size: 18px; font-weight: 600; letter-spacing: -0.01em; }
.tv-sm.is-thumb { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 4px; height: 100%; align-content: center; }
.tv-sm.is-thumb .tv-sm-tile { padding: 0; border: 0; border-top: 1.5px solid var(--tv-c); }
.tv-sm.is-thumb .tv-chart, .tv-sm.is-thumb .tr-spark-empty { height: 17px; }

.tv-cb { display: grid; gap: 6px; }
.tv-cb-row {
  display: grid;
  grid-template-columns: minmax(8rem, 15rem) minmax(0, 1fr) 7.5rem;
  align-items: center;
  gap: 12px;
  padding: 3px 4px;
  border-radius: 4px;
}
.tv-cb-row:hover { background: var(--bg-sunken); }
.tv-cb-label { font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tv-cb-track { position: relative; height: 14px; }
.tv-cb-zero { position: absolute; left: 50%; top: -3px; bottom: -3px; width: 1px; background: var(--border-strong); }
.tv-cb-bar { position: absolute; top: 0; bottom: 0; background: var(--tv-c); }
.tv-cb-bar.is-pos { border-radius: 0 4px 4px 0; }
.tv-cb-bar.is-neg { border-radius: 4px 0 0 4px; }
.tv-cb-value { font-size: 12px; font-weight: 600; text-align: right; font-variant-numeric: tabular-nums; }
.tv-cb-dir { font-size: 11px; font-weight: 400; color: var(--text-muted); }
.tv-cb.is-thumb { gap: 3px; height: 100%; align-content: center; }
.tv-cb.is-thumb .tv-cb-row { grid-template-columns: minmax(0, 1fr); padding: 0; }
.tv-cb.is-thumb .tv-cb-track { height: 5px; }
.tv-foot { margin: var(--space-3) 0 0; font-size: 11.5px; color: var(--text-muted); }

.tv-donut { position: relative; display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-6); }
.tv-donut.is-thumb { justify-content: center; height: 100%; }
.tv-seg { stroke: var(--tv-c); cursor: pointer; transition: opacity 120ms ease; }
.tv-seg:hover { opacity: 0.8; }
.tv-donut-num { fill: var(--text-primary); font-size: 30px; font-weight: 600; }
.tv-donut-legend { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; font-size: 12.5px; min-width: 0; flex: 1 1 18rem; }
.tv-donut-legend li { display: grid; grid-template-columns: 10px 2rem minmax(0, 1fr); align-items: baseline; -moz-column-gap: 8px; column-gap: 8px; }
.tv-donut-legend strong { font-variant-numeric: tabular-nums; }
.tv-donut-who { grid-column: 3; font-size: 11.5px; color: var(--text-muted); overflow-wrap: anywhere; }
.tv-swatch { display: inline-block; width: 10px; height: 10px; border-radius: 2px; background: var(--tv-c); }

.tv-heat { position: relative; }
.tv-heat-grid { display: grid; gap: 2px; align-items: center; }
.tv-heat-label { padding-right: 8px; font-size: 12px; color: var(--text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* One hue, light to dark: magnitude is sequential, and a second hue would read as a category. */
.tv-cell { display: block; height: 18px; border-radius: 2px; background: var(--chart-1); }
.tv-cell.is-s0 { opacity: 0.14; }
.tv-cell.is-s1 { opacity: 0.32; }
.tv-cell.is-s2 { opacity: 0.52; }
.tv-cell.is-s3 { opacity: 0.74; }
.tv-cell.is-s4 { opacity: 1; }
.tv-cell.is-none { background: var(--bg-sunken); opacity: 1; }
.tv-heat-grid .tv-cell:hover { outline: 1px solid var(--text-primary); outline-offset: 0; }
.tv-heat-foot { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-3); }
.tv-heat-scale { display: inline-flex; align-items: center; gap: 3px; font-size: 11px; color: var(--text-muted); }
.tv-heat-scale .tv-cell { width: 14px; height: 10px; }
.tv-heat.is-thumb { display: grid; height: 100%; align-content: center; }
.tv-heat.is-thumb .tv-heat-grid { gap: 1px; }
.tv-heat.is-thumb .tv-cell { height: 7px; border-radius: 1px; }

.tv-table-wrap { overflow-x: auto; overscroll-behavior-x: contain; }
.tv-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.tv-table th, .tv-table td { padding: 8px 10px; text-align: left; border-bottom: 1px solid var(--border-hairline); white-space: nowrap; }
.tv-table thead th { font-size: 11px; font-weight: 600; color: var(--text-muted); }
.tv-table tbody th { font-weight: 600; }
.tv-table .is-num { text-align: right; }
.tv-table-thumb { display: grid; gap: 7px; height: 100%; align-content: center; }
.tv-table-thumb-row { display: grid; grid-template-columns: 2fr 1fr 1fr; gap: 6px; }
.tv-table-thumb-row span { height: 5px; border-radius: 2px; background: var(--border-strong); }
.tv-table-thumb-row span:last-child { background: var(--tv-c); }

.tv-estate-empty { padding: var(--space-6); text-align: center; font-size: 12px; color: var(--text-muted); border-radius: 6px; border: 1px dashed var(--border-hairline); }
.tv-estate-empty.is-thumb { height: 100%; padding: 0; }

@media (max-width: 720px) {
  /* The filters go above the tiles; a 212px rail beside them leaves no room for a tile. */
  .tv-gallery { grid-template-columns: minmax(0, 1fr); padding: var(--space-3); }
  .tv-sections { padding: var(--space-3); }
  /* Two to a row: one per row made a tile taller than the phone is wide, and thirteen of them a scroll. */
  .tv-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-2); }
  .tv-thumb { height: 68px; }
  .tv-cb-row { grid-template-columns: minmax(0, 1fr) 6rem; }
  .tv-cb-label { grid-column: 1 / -1; }
}
@media (prefers-reduced-motion: reduce) {
  .tv-tile, .tv-chip, .tv-seg, .tv-pick, .tv-switch, .tv-switch-chevron { transition: none; }
  .tv-tile:hover { transform: none; }
}

/* ---- a card's past → now → next, and the forecast on its chart */
/*
 * Read left to right in time. Now is the large figure; past and next are the same size as each
 * other, because one is a median the verdict compared against and the other is a forecast — both
 * are context for the reading taken today.
 */
.tr-pnn { display: flex; align-items: flex-end; gap: var(--space-2) 10px; flex-wrap: wrap; min-width: 0; }
.tr-pnn-step { display: inline-flex; align-items: flex-end; gap: 10px; min-width: 0; }
/* A narrow card's Now is a size smaller, so past → now → next fits one line at a third of the grid. */
.tr-card:not(.is-wide) .tr-pnn .tr-now { font-size: 23px; }
.tr-pnn-item { display: grid; gap: 2px; min-width: 0; }
.tr-pnn-k {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.tr-pnn-v { font-size: 15px; font-weight: 600; line-height: 1.2; color: var(--text-secondary); }
.tr-pnn-v.is-quiet { font-size: 12px; font-weight: 400; color: var(--text-muted); }
.tr-pnn-range { font-size: 10.5px; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.tr-pnn-arrow { padding-bottom: 3px; font-size: 13px; color: var(--text-muted); }
/* Status rides a dot beside the label, never the figure's own colour — text wears text tokens. */
.tr-pnn-item.is-worse .tr-pnn-k::before,
.tr-pnn-item.is-better .tr-pnn-k::before { content: ""; width: 7px; height: 7px; border-radius: 50%; }
.tr-pnn-item.is-worse .tr-pnn-k::before { background: var(--warn); }
.tr-pnn-item.is-better .tr-pnn-k::before { background: var(--ok-text); }
.tr-label-link { color: inherit; text-decoration: none; }
.tr-label-link:hover { text-decoration: none; text-underline-offset: 3px; }
.tr-label-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
.tr-open { font-size: 11px; font-weight: 600; color: var(--color-primary-text); text-decoration: none; white-space: nowrap; }
.tr-open:hover { text-decoration: none; text-underline-offset: 3px; }
.tr-open:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
.tr-ahead { stroke: var(--tr-c); stroke-width: 1.75px; stroke-dasharray: 4 3; stroke-linecap: round; opacity: 0.9; }
.tr-ahead-band { fill: var(--tr-c); opacity: 0.12; stroke: none; }
.tr-today { stroke: var(--text-primary); stroke-width: 1px; opacity: 0.35; }
.tr-cross-dot.is-ahead { opacity: 0.6; }
.tr-axis-today { position: absolute; top: 0; transform: translateX(-50%); font-weight: 600; color: var(--text-primary); }

/* ---- payment flows as rows on Trends */
.fc-sums { display: grid; gap: var(--space-3); }
.fc-sum {
  display: grid;
  gap: var(--space-3);
  min-width: 0;
  padding: var(--space-4);
  border-radius: 10px;
  border: 1px solid var(--border-hairline);
  background: var(--bg-surface);
  transition: border-color 150ms ease, box-shadow 150ms ease;
}
.fc-sum:hover { border-color: var(--border-strong); box-shadow: 0 4px 14px var(--argo-shadow); }
.fc-sum-head { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--space-2) var(--space-3); min-width: 0; }
.fc-sum-name { margin: 0; font-size: 15px; font-weight: 600; }
.fc-sum-name a { color: inherit; text-decoration: none; }
.fc-sum-name a:hover { text-decoration: none; text-underline-offset: 3px; }
.fc-sum-name a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
.fc-sum-body { display: grid; grid-template-columns: minmax(0, 1fr) minmax(16rem, 24rem); gap: var(--space-4); align-items: center; }
.fc-sum-chart { min-width: 0; }
.fc-sum-figs { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-3); margin: 0; }
.fc-sum-figs div { display: grid; gap: 2px; min-width: 0; }
.fc-sum-figs dt { font-size: 10.5px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-muted); }
.fc-sum-figs dd { margin: 0; font-size: 16px; font-weight: 600; }
.fc-sum-figs dd.is-over::after { content: " ▲"; font-size: 10px; color: var(--warn); }
.fc-sum-sub { font-size: 11px; color: var(--text-secondary); overflow-wrap: anywhere; }
.fc-sum-msg, .fc-sum-guide { margin: 0; font-size: 12.5px; line-height: 1.55; color: var(--text-secondary); }
.fc-sum-open { justify-self: start; font-size: 12px; font-weight: 600; color: var(--color-primary-text); text-decoration: none; }
.fc-sum-open:hover { text-decoration: none; text-underline-offset: 3px; }
.fc-sum-open:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
.fc-seg line { stroke: var(--chart-1); stroke-width: 2.5px; stroke-linecap: round; opacity: 0.5; }
.fc-seg.is-now line { opacity: 1; }
.fc-seg-label { fill: var(--text-secondary); font-size: 10.5px; paint-order: stroke; stroke: var(--bg-surface); stroke-width: 3px; }

/* ---- one trend's own page */
.td-back {
  justify-self: start;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  width: -moz-max-content;
  width: max-content;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-secondary);
  text-decoration: none;
}
.td-back:hover { color: var(--text-primary); }
.td-back:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
.td-pnn { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr)); gap: var(--space-3); }
.td-tile {
  display: grid;
  align-content: start;
  justify-items: start;
  gap: var(--space-2);
  min-width: 0;
  padding: var(--space-4);
  border-radius: 10px;
  border: 1px solid var(--border-hairline);
  background: var(--bg-surface);
}
.td-tile.is-now { border-color: var(--border-strong); box-shadow: 0 1px 2px var(--argo-shadow); }
.td-tile.is-next { border-style: dashed; }
.td-tile-k { margin: 0; font-size: 11px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-muted); }
.td-tile-v { font-size: 28px; font-weight: 600; letter-spacing: -0.02em; line-height: 1.1; }
.td-tile-note { margin: 0; font-size: 12.5px; line-height: 1.55; color: var(--text-secondary); text-wrap: pretty; }
.td-chart { display: grid; gap: var(--space-2); min-width: 0; padding: var(--space-4); border-radius: 10px; border: 1px solid var(--border-hairline); background: var(--bg-surface); }
.td-chart-note { margin: 0; font-size: 11.5px; line-height: 1.5; color: var(--text-muted); }
.td-panels { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr)); gap: var(--space-4); }
.td-deeper { display: grid; gap: var(--space-3); }
.td-h { margin: 0; font-size: 15px; font-weight: 600; }
.td-links { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(16rem, 100%), 1fr)); gap: var(--space-3); }
.td-link {
  display: grid;
  gap: 4px;
  min-width: 0;
  padding: var(--space-3) var(--space-4);
  border-radius: 8px;
  border: 1px solid var(--border-hairline);
  background: var(--bg-surface);
  color: inherit;
  text-decoration: none;
  transition: border-color 150ms ease, transform 150ms ease;
}
.td-link:hover { border-color: var(--border-strong); transform: translateY(-1px); }
.td-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.td-link.is-sibling { background: var(--bg-sunken); }
.td-link-page { font-size: 13px; font-weight: 600; color: var(--text-primary); }
.td-link-finds { font-size: 12px; line-height: 1.5; color: var(--text-secondary); }
.td-link-inline { font-weight: 600; color: var(--color-primary-text); }

@media (max-width: 720px) {
  .fc-sum-body { grid-template-columns: minmax(0, 1fr); }
}
@media (prefers-reduced-motion: reduce) {
  .td-link, .fc-sum { transition: none; }
  .td-link:hover { transform: none; }
}

/* ===================================================================== trends > what's coming */

/*
 * The payment-flow forecast. One identity hue (`--chart-1`) for the series in both its states —
 * measured solid, forecast dashed — because it is one series, not two; status colours are kept for
 * the things that are status (a technical-failure share, a calendar effect that was measured).
 */
.fc-section { display: grid; gap: var(--space-3); min-width: 0; }
.fc-section-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--border-hairline);
}
.fc-section-head h2 { margin: 0; font-size: 16px; font-weight: 600; letter-spacing: -0.01em; }
.fc-section-head p { margin: 2px 0 0; font-size: 12px; color: var(--text-muted); }
.fc-cards { display: grid; gap: var(--space-4); }
.fc-card {
  display: grid;
  gap: var(--space-4);
  min-width: 0;
  padding: var(--space-5);
  border-radius: 10px;
  border: 1px solid var(--border-hairline);
  background: var(--bg-surface);
  box-shadow: 0 1px 2px var(--argo-shadow);
}
.fc-head { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--space-2) var(--space-3); min-width: 0; }
.fc-kind {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 9px;
  border-radius: 999px;
  border: 1px solid var(--border-hairline);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--text-primary);
}
.fc-kind::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--chart-1); }
.fc-kind.is-corporate_banking::before { background: var(--chart-3); }
.fc-kind.is-cards::before { background: var(--chart-4); }
.fc-kind.is-other_payments::before { background: var(--chart-2); }
.fc-kind.is-application::before { background: var(--text-muted); }
.fc-name { margin: 0; font-size: 18px; font-weight: 600; letter-spacing: -0.015em; }
.fc-svc { min-width: 0; font-size: 11.5px; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.fc-figs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(10.5rem, 100%), 1fr));
  gap: var(--space-4);
  padding: var(--space-3) 0;
  border-top: 1px solid var(--border-hairline);
  border-bottom: 1px solid var(--border-hairline);
}
.fc-fig { display: grid; gap: 3px; min-width: 0; }
.fc-fig-label { font-size: 11px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-muted); }
/* Proportional figures: these stand alone, they do not line up in a column. */
.fc-fig-value { font-size: 22px; font-weight: 600; letter-spacing: -0.02em; line-height: 1.1; }
.fc-fig-sub { font-size: 11.5px; color: var(--text-secondary); overflow-wrap: anywhere; }

.fc-legend { display: flex; flex-wrap: wrap; gap: 6px 16px; font-size: 11.5px; color: var(--text-secondary); }
.fc-legend span { display: inline-flex; align-items: center; gap: 6px; }
.fc-key { display: inline-block; width: 16px; height: 0; border-top: 2px solid var(--chart-1); }
.fc-key.is-forecast { border-top-style: dashed; }
.fc-key.is-band { height: 10px; border: 0; border-radius: 2px; background: var(--chart-1); opacity: 0.18; }
.fc-key.is-ref { border-top: 1.5px dashed var(--text-muted); }
.fc-key.is-shade { height: 10px; border: 0; border-radius: 2px; background: var(--chart-5); opacity: 0.3; }

.fc-chart { position: relative; width: 100%; min-width: 0; }
.fc-chart svg { display: block; overflow: visible; }
.fc-chart-empty { display: grid; place-items: center; font-size: 12px; color: var(--text-muted); border: 1px dashed var(--border-hairline); border-radius: 6px; }
.fc-grid { stroke: var(--border-hairline); stroke-width: 1px; }
.fc-tick { stroke: var(--border-strong); stroke-width: 1px; }
.fc-axis { fill: var(--text-muted); font-size: 10.5px; font-variant-numeric: tabular-nums; }
.fc-line { stroke: var(--chart-1); stroke-width: 2px; stroke-linejoin: round; stroke-linecap: round; }
/* Dashed: it is a projection, and a dash is how a reader already knows one. */
.fc-line.is-forecast { stroke-dasharray: 5 4; }
.fc-band { fill: var(--chart-1); opacity: 0.14; stroke: none; }
.fc-ref { stroke: var(--text-muted); stroke-width: 1.5px; stroke-dasharray: 2 4; }
.fc-ref-label { fill: var(--text-secondary); font-size: 10.5px; paint-order: stroke; stroke: var(--bg-surface); stroke-width: 3px; }
.fc-now { stroke: var(--text-primary); stroke-width: 1px; opacity: 0.5; }
.fc-now-label { fill: var(--text-primary); font-size: 10.5px; font-weight: 600; }
.fc-now-label.is-ahead { fill: var(--text-muted); font-weight: 400; }
.fc-peak { fill: var(--chart-1); stroke: var(--bg-surface); stroke-width: 2px; }
/* A calendar day the flow's history has measured is warm; one it has not is a grey hypothesis. */
.fc-shade rect { fill: var(--text-muted); opacity: 0.07; }
.fc-shade.is-measured rect { fill: var(--chart-5); opacity: 0.16; }
.fc-shade-label { fill: var(--text-secondary); font-size: 10px; font-weight: 600; }
.fc-cross { stroke: var(--text-muted); stroke-width: 1px; }
.fc-dot { fill: var(--chart-1); stroke: var(--bg-surface); stroke-width: 2px; }
.fc-dot.is-forecast { fill: var(--bg-surface); stroke: var(--chart-1); }
.fc-hit { fill: transparent; cursor: crosshair; }
.fc-tip {
  position: absolute;
  top: 0;
  z-index: 4;
  display: grid;
  gap: 1px;
  min-width: 140px;
  padding: 7px 10px;
  border-radius: 6px;
  background: var(--text-primary);
  color: var(--bg-surface);
  font-size: 11px;
  white-space: nowrap;
  transform: translateX(12px);
  pointer-events: none;
  box-shadow: 0 6px 16px var(--argo-shadow);
}
.fc-tip.is-left { transform: translateX(calc(-100% - 12px)); }
.fc-tip strong { font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums; }
.fc-tip-when, .fc-tip-kind { opacity: 0.72; }

.fc-guidance { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.fc-guidance li {
  position: relative;
  padding: 10px 12px 10px 34px;
  border-radius: 8px;
  background: var(--bg-sunken);
  font-size: 13px;
  line-height: 1.55;
  color: var(--text-primary);
  text-wrap: pretty;
}
.fc-guidance li::before {
  content: "→";
  position: absolute;
  left: 13px;
  top: 10px;
  font-weight: 700;
  color: var(--text-secondary);
}

.fc-minis { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr)); gap: var(--space-4); }
.fc-mini { display: grid; gap: 4px; min-width: 0; padding: var(--space-3); border-radius: 8px; border: 1px solid var(--border-hairline); }
.fc-mini-title { font-size: 12px; font-weight: 600; }
.fc-mini-note { font-size: 11px; color: var(--text-muted); }

.fc-panels { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr)); gap: var(--space-4); }
.fc-panel { display: grid; align-content: start; gap: var(--space-2); min-width: 0; padding: var(--space-4); border-radius: 8px; background: var(--bg-sunken); }
.fc-panel-title { margin: 0; font-size: 13px; font-weight: 600; }
.fc-panel-lede { margin: 0; font-size: 12px; line-height: 1.55; color: var(--text-secondary); text-wrap: pretty; }
.fc-panel-foot { margin: var(--space-1) 0 0; font-size: 11px; line-height: 1.5; color: var(--text-muted); }

.fc-cands, .fc-cal { list-style: none; margin: var(--space-1) 0 0; padding: 0; display: grid; gap: 10px; }
.fc-cand {
  display: grid;
  grid-template-columns: minmax(9rem, 13rem) minmax(3rem, 1fr) 4.5rem auto;
  align-items: center;
  -moz-column-gap: 10px;
       column-gap: 10px;
  row-gap: 2px;
}
.fc-cand-name { font-size: 12.5px; font-weight: 600; overflow-wrap: anywhere; }
.fc-cand-bar { height: 8px; border-radius: 4px; background: var(--bg-surface); overflow: hidden; }
.fc-cand-bar span { display: block; height: 100%; border-radius: 0 4px 4px 0; background: var(--text-muted); opacity: 0.5; }
.fc-cand.is-won .fc-cand-bar span { background: var(--chart-1); opacity: 1; }
.fc-cand-err { font-size: 11.5px; text-align: right; color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.fc-cand-status { justify-self: start; padding: 1px 7px; border-radius: 999px; font-size: 10.5px; font-weight: 600; color: var(--text-secondary); border: 1px solid var(--border-hairline); white-space: nowrap; }
.fc-cand-status.is-won { color: var(--text-primary); border-color: var(--chart-1); }
.fc-cand-status.is-untestable { border-style: dashed; color: var(--text-muted); }
.fc-cand-why { grid-column: 1 / -1; font-size: 11.5px; color: var(--text-muted); }
.fc-cal-row {
  display: grid;
  grid-template-columns: minmax(9rem, 1fr) auto auto;
  align-items: baseline;
  -moz-column-gap: 10px;
       column-gap: 10px;
  row-gap: 2px;
}
.fc-cal-name { font-size: 12.5px; font-weight: 600; }
.fc-cal-when { font-size: 11.5px; color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.fc-cal-status { padding: 1px 7px; border-radius: 999px; font-size: 10.5px; font-weight: 600; border: 1px dashed var(--border-strong); color: var(--text-muted); white-space: nowrap; }
.fc-cal-status.is-measured { border-style: solid; border-color: var(--chart-5); color: var(--text-primary); }
.fc-cal-why { grid-column: 1 / -1; font-size: 11.5px; line-height: 1.5; color: var(--text-muted); }
.fc-foot { margin: 0; font-size: 11px; line-height: 1.55; color: var(--text-muted); max-width: 96ch; }

.fc-empty {
  display: grid;
  justify-items: start;
  gap: var(--space-3);
  padding: var(--space-6);
  border-radius: 10px;
  border: 1px dashed var(--border-strong);
  background: var(--bg-surface);
}
.fc-empty h3 { margin: 0; font-size: 15px; font-weight: 600; }
.fc-empty p { margin: 0; max-width: 72ch; font-size: 13px; line-height: 1.6; color: var(--text-secondary); }

.fc-picks { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.fc-pick {
  display: grid;
  /* A fixed button column: `auto` sized each row to its own button and the selects stopped lining up. */
  grid-template-columns: minmax(0, 1.4fr) minmax(9rem, 12rem) minmax(7rem, 11rem) 7.5rem;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border-hairline);
}
.fc-pick > button { justify-self: stretch; }
.fc-pick-svc { display: grid; gap: 1px; min-width: 0; }
.fc-pick-svc strong { font-size: 13px; }
.fc-pick-svc span { font-size: 11px; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

@media (max-width: 720px) {
  .fc-card { padding: var(--space-4); }
  /* Names take their own line: beside two figures they wrapped one word per line. */
  .fc-cand { grid-template-columns: minmax(0, 1fr) auto; }
  .fc-cand-name { grid-column: 1 / -1; }
  .fc-cand-bar { display: none; }
  .fc-cand-err { text-align: left; }
  /* A 9rem minimum on the name column was wider than a phone could give it, and pushed the row out. */
  .fc-cal-row { grid-template-columns: minmax(0, 1fr) auto; }
  .fc-cal-name { grid-column: 1 / -1; }
  .fc-panel { padding: var(--space-3); }
  .fc-pick { grid-template-columns: minmax(0, 1fr); }
}

/* ======================================================== stopping a sync (deploy > deployments) */

/*
 * The dialog that ends a sync's unstarted steps.
 *
 * Laid out as a reckoning rather than as a warning: a count, what it means, and one line saying
 * what follows from it. Three of the four rows are about work this control will *not* touch, so
 * they have to read as information the reader has to act on rather than as small print under a
 * button.
 */
.cdw-stop { display: grid; gap: var(--space-4); min-width: 0; }
.cdw-stop-lede { margin: 0; font-size: var(--text-sm); line-height: 1.6; color: var(--text-secondary); }
.cdw-stop-lines { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-3); min-width: 0; }
.cdw-stop-line {
  display: grid;
  grid-template-columns: 2.25rem minmax(0, 1fr);
  gap: var(--space-3);
  align-items: start;
  padding-inline-start: var(--space-3);
  /*
   * The tone is on the edge, not on the background. A dialog of four tinted blocks is four things
   * shouting; a rule down the side separates them and still says which one is the action.
   */
  border-inline-start: 3px solid var(--border-hairline);
}
.cdw-stop-line.is-act { border-inline-start-color: var(--critical-text); }
.cdw-stop-line.is-warn { border-inline-start-color: var(--warn-text); }
.cdw-stop-line.is-note { border-inline-start-color: var(--border-strong); }
.cdw-stop-count {
  font-size: var(--text-lg);
  font-weight: 650;
  line-height: 1.15;
  text-align: end;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}
.cdw-stop-body { display: grid; gap: 2px; min-width: 0; }
.cdw-stop-label { font-size: var(--text-sm); font-weight: 600; color: var(--text-primary); overflow-wrap: anywhere; }
/*
 * The consequence, and the reason this dialog is worth its space. Full measure and ordinary weight:
 * it is a sentence to be read, not a caption to be skimmed past.
 */
.cdw-stop-detail { font-size: 12px; line-height: 1.6; color: var(--text-secondary); overflow-wrap: anywhere; }
.cdw-stop-after {
  margin: 0;
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-hairline);
  font-size: 12px;
  line-height: 1.6;
  color: var(--text-secondary);
}

/* ======================================================================================
   Applications — a namespace across every cluster, from DNS to its dependencies.
   ======================================================================================

   Appended, like every surface added while other sessions edit this file (see the TraceMesh header).
   Prefix `apx-`, unused anywhere else.

   The visual rule is the App graph's: health owns colour, and nothing else takes a hue — with one
   deliberate exception. A hop that crosses from one cluster into another is drawn in `--info`, the
   only non-health colour on the page, because the spec's one hard UI requirement is that crossing a
   cluster boundary is impossible to miss, and a colour nothing else uses is how that is guaranteed.

   Borders are `1px solid var(--color-border)`: `--border-hairline` is a bare colour, and using it
   as a border shorthand draws nothing (`border-shorthand.test.ts`).
   ====================================================================================== */

/* -------------------------------------------------------------------------------- list */

.apx-list-tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-block-end: var(--space-3);
}

.apx-search {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  block-size: var(--size-control-sm);
  padding: 0 var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-sunken);
  color: var(--color-text-subtle);
  min-inline-size: 14rem;
}

.apx-search-wide { flex: 1 1 18rem; max-inline-size: 26rem; }
.apx-search:focus-within { border-color: var(--color-border-strong); outline: 2px solid var(--color-focus-ring); outline-offset: 1px; }
.apx-search input { flex: 1 1 auto; min-width: 0; border: 0; background: transparent; color: var(--color-text); font: inherit; font-size: var(--text-sm); }
.apx-search input:focus { outline: none; }
.apx-search-icon { inline-size: 0.875rem; block-size: 0.875rem; flex: none; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; }

.apx-toggle { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-sm); color: var(--color-text-muted); cursor: pointer; }
.apx-toggle input { accent-color: var(--color-text); }

.apx-app-link { color: var(--color-text); font-weight: 500; text-decoration: none; }
.apx-app-link:hover { text-decoration: none; text-underline-offset: 3px; }
.apx-app-link:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; border-radius: 4px; }

.apx-cluster-chips { display: inline-flex; flex-wrap: wrap; gap: 4px; }
.apx-chip {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  padding: 1px 6px;
  border-radius: 6px;
  border: 1px solid var(--color-border);
  background: var(--color-sunken);
  color: var(--color-text-muted);
}
.apx-chip[data-collecting="no"] { color: var(--color-text-subtle); border-style: dashed; }

.apx-ratio { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.apx-ratio[data-short="yes"] { color: var(--warn-text); }
.apx-muted { color: var(--color-text-subtle); }
.apx-count-bad { color: var(--critical-text); font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.apx-empty-links { font-size: var(--text-base); }

/* ---------------------------------------------------------------------------- overview */

.apx-head-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }

.apx-btn {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  block-size: var(--size-control-sm);
  padding: 0 var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-surface);
  color: var(--color-text);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}
.apx-btn:hover { background: var(--color-hover); border-color: var(--color-border-strong); }
.apx-btn:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 1px; }
.apx-btn-primary { background: var(--color-primary-solid); border-color: var(--color-primary-solid); color: var(--color-on-primary); }
.apx-btn-primary:hover { background: var(--color-primary-hover); border-color: var(--color-primary-hover); }

/* ==================================================================
 * The latency plate: the Applications page's one instrument.
 *
 * A recessed, cool surface with a real graticule, read like the face of a meter. The palette is
 * scoped to this block and sits on top of the product's tokens rather than replacing them:
 *
 *   plate      a cooler, deeper ground than the page's surface, so the bar reads as set into glass
 *   graticule  the millisecond marks, at the threshold of visible — information, not decoration
 *   within     time inside the budget. TEAL, not green: green is every dashboard's "healthy" and
 *              says nothing; teal is the colour of an instrument trace and is not --ok, which this
 *              deliberately does not borrow, because this is a measurement and not a verdict
 *   over       time past the budget, amber running to red at the hop that spends the most — the
 *              language of a needle entering the red, which is this subject's own vernacular
 *   budget     the ceiling rule, in bone: a hard neutral mark, the way a limit is printed on a dial
 * ================================================================== */
.apx-plate {
  --plate: #0e1116;
  --plate-edge: #1c2027;
  --graticule: rgba(226, 232, 240, 0.13);
  --within: #2e8c84;
  --over: #d98324;
  --budget: #e8e4da;
  --plate-ink: #e7eaef;
  --plate-ink-dim: #8c94a1;
  /*
   * The empty bar track, and the ink for `--over` where it is used as *text* rather than as fill.
   * Both are separate variables only because they are the two values that cannot survive the ground
   * being inverted: a track is a tint of the surface it sits on, and #d98324 that reads comfortably
   * on #0e1116 measures about 2.6:1 on white, which is below the floor for body text.
   */
  --track: rgba(255, 255, 255, 0.035);
  --over-ink: var(--over);

  position: relative;
  margin-block-end: var(--space-4);
  padding: var(--space-5) var(--space-5) var(--space-4);
  border: 1px solid var(--plate-edge);
  border-radius: var(--radius-card);
  background: var(--plate);
  color: var(--plate-ink);
  /*
   * One shadow, tinted to the plate's own hue rather than black. The inner top highlight that was
   * here is a bevel imitating a material this plate is not made of; the drop alone separates it.
   */
  box-shadow: 0 1px 2px rgba(6, 9, 14, 0.5);
  overflow: hidden;
}
/*
 * In light mode the plate is light, and this comment used to argue the opposite.
 *
 * It said an instrument is read off a dark ground and that the plate should keep its dark face in
 * both themes — "a device set into the page rather than a theme failure". That was wrong in the only
 * way this kind of claim can be tested: it was read as a failure. On a white page the plate was the
 * single dark element, and a lone inverted section does not read as deliberate, it reads as
 * something that did not get themed. The project's own design audit names this exact pattern.
 *
 * The argument for the dark ground was really an argument about the *bar*, not about the panel —
 * and the bar keeps everything it needed. Its segments are the same saturated teal and amber with
 * near-black labels, which is a combination that works on either ground, so the picture is
 * unchanged. Only the panel behind it follows the page now.
 *
 * Three values cannot simply be reused, because each was chosen against #0e1116:
 *   `--graticule` was a light tint and has to become a dark one, or the marks vanish;
 *   `--budget`, a near-white, becomes a neutral dark enough to read as a drawn rule;
 *   `--over-ink` darkens, because the amber that carries the fill is too light to be text on white.
 * `--over` itself is untouched, so the hot segment keeps its colour.
 */
:root[data-theme="light"] .apx-plate {
  --plate: var(--bg-surface);
  --plate-edge: var(--border-hairline);
  --plate-ink: var(--text-primary);
  --plate-ink-dim: var(--text-muted);
  --graticule: rgba(9, 9, 11, 0.13);
  --budget: #52525b;
  --track: rgba(9, 9, 11, 0.05);
  --over-ink: #a85a10;
  box-shadow: var(--elev-rest);
}

.apx-plate-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2) var(--space-3); }
.apx-plate-title {
  margin: 0;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--plate-ink-dim);
}
.apx-plate-sub { margin: 0; font-size: var(--text-sm); color: var(--plate-ink-dim); }
.apx-plate-missing { margin: var(--space-3) 0 0; font-size: var(--text-sm); line-height: var(--leading-body); color: var(--plate-ink-dim); }

/* The reading. The figure is this page's display type — there is no second display face. */
.apx-plate-read { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-3); margin-block: var(--space-3) var(--space-5); }
.apx-plate-figure { display: flex; align-items: baseline; gap: 0.12em; margin: 0; }
.apx-plate-num {
  font-family: var(--font-mono);
  font-size: clamp(34px, 4.4vw, 46px);
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.035em;
  color: var(--plate-ink);
}
.apx-plate-unit { font-family: var(--font-mono); font-size: 15px; font-weight: 500; color: var(--plate-ink-dim); }
.apx-plate-verdict { margin: 0; font-size: var(--text-sm); color: var(--plate-ink-dim); }
.apx-plate[data-over="yes"] .apx-plate-verdict strong { color: var(--over-ink); font-variant-numeric: tabular-nums; }

/* ------------------------------------------------ the bar and the marks behind it */
.apx-bar-wrap { position: relative; padding-block-start: 16px; }
/* Behind the bar: the measurement marks, at the threshold of visible. */
.apx-graticule { position: absolute; inset-block: 0 auto; inset-inline: 0; block-size: 74px; pointer-events: none; }
.apx-tick { position: absolute; inset-block: 0; inline-size: 0; border-inline-start: 1px solid var(--graticule); }
.apx-tick-label {
  position: absolute;
  inset-block-start: 1px;
  inset-inline-start: 5px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.02em;
  color: var(--plate-ink-dim);
  opacity: 0.75;
}
/*
 * The ceiling, over the bar rather than above it.
 *
 * The finding is that the request does not approach its budget, it goes through it, so the rule has
 * to cross the fill. Dashed across the bar and solid in the margin above: a dashed line stays
 * legible over both the teal and the amber without needing a halo, and the solid head is what the
 * label hangs from. `z-index` over the bar; the bar is opaque and would otherwise bury it.
 */
.apx-budget-rule {
  position: absolute;
  inset-block-start: 0;
  block-size: 76px;
  inline-size: 0;
  border-inline-start: 1px dashed var(--budget);
  z-index: 1;
  pointer-events: none;
}
.apx-budget-rule::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: -1px;
  block-size: 16px;
  border-inline-start: 1px solid var(--budget);
}
.apx-budget-label {
  position: absolute;
  inset-block-start: 1px;
  inset-inline-start: 6px;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--budget);
}

.apx-bar {
  position: relative;
  display: flex;
  margin: 0;
  padding: 0;
  list-style: none;
  block-size: 56px;
  border-radius: 7px;
  overflow: hidden;
  background: var(--track);
}
.apx-seg { position: relative; flex: 0 0 var(--seg); min-inline-size: 2px; }
/*
 * The fill is a child so the growth animates `transform` alone. Animating the item's own width
 * would lay the flex row out again on every frame — and on this bar that is four reflows a frame.
 */
.apx-seg-fill {
  position: absolute;
  inset: 0;
  background: var(--within);
  transform-origin: left center;
  animation: apx-seg-grow 540ms cubic-bezier(0.22, 0.75, 0.3, 1) backwards;
  animation-delay: var(--seg-delay, 0ms);
}
.apx-seg + .apx-seg .apx-seg-fill { box-shadow: inset 1px 0 0 rgba(14, 17, 22, 0.55); }
@keyframes apx-seg-grow {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}
/*
 * The hop that spends the most, once the request is past its ceiling.
 *
 * Solid, and it was a gradient — amber at the left edge running to red at the right. That failed
 * blacklist 6 in `tokens.test.ts`, and the test was right in a way worth keeping written down: the
 * segment is **one hop with one number**, so a colour that changes across it reads as a quantity
 * changing across it and there is no such quantity. It was decoration on a surface carrying a
 * measurement, which is exactly what that rule exists to stop. Amber rather than the old red end,
 * because this segment is 96% of the bar on the live estate and a wall of #c0392b shouts louder
 * than the finding underneath it does.
 */
.apx-plate[data-over="yes"] .apx-seg[data-hot="yes"] .apx-seg-fill {
  background: var(--over);
}
.apx-seg-label {
  position: absolute;
  inset-block: 0;
  inset-inline: 12px auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  max-inline-size: calc(100% - 20px);
}
.apx-seg-label .apx-mono { font-size: 12px; font-weight: 600; color: #0b0e13; letter-spacing: 0.01em; }
.apx-seg-ms { font-family: var(--font-mono); font-size: 11px; color: rgba(11, 14, 19, 0.72); }
.apx-seg[data-hot="yes"] .apx-seg-label .apx-mono { color: #190a06; }
.apx-seg[data-hot="yes"] .apx-seg-ms { color: rgba(25, 10, 6, 0.74); }

/* Hops too narrow to carry a label inside the bar. Named under it, in the order they are passed. */
.apx-seg-rest {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  margin: var(--space-3) 0 0;
  padding: 0;
  list-style: none;
  font-size: 11px;
  color: var(--plate-ink-dim);
}
.apx-seg-rest .apx-mono { color: var(--plate-ink); }

.apx-plate-finding {
  margin: var(--space-4) 0 0;
  padding-block-start: var(--space-3);
  border-block-start: 1px solid var(--plate-edge);
  font-size: var(--text-base);
  line-height: var(--leading-body);
  color: var(--plate-ink);
}
.apx-plate-finding strong { color: var(--over-ink); font-variant-numeric: tabular-nums; }
.apx-plate .apx-mono { font-family: var(--font-mono); }
.apx-sr { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

@media (prefers-reduced-motion: reduce) {
  /* The finished bar, not a frozen frame: `animation: none` alone leaves it at scaleX(0). */
  .apx-seg-fill { animation: none; transform: none; }
}
@media (max-width: 40rem) {
  .apx-plate { padding: var(--space-4); }
  .apx-bar { block-size: 48px; }
  .apx-seg-label { inset-inline-start: 8px; }
  /*
   * The ruled marks stay, their numbers go.
   *
   * At 390px the plate is 292px wide and `200ms`, `budget 500ms` and `1.00s` land on top of each
   * other — three labels in the space of one, which is worse than none. The lines still divide the
   * bar, and the budget keeps its label because it is the only mark the finding is read against.
   */
  .apx-tick-label { display: none; }
  .apx-budget-label { letter-spacing: 0.04em; }
}

/*
 * The legend under the plate.
 *
 * Hairline-divided cells, not cards. The plate above is the one object with chrome on this page;
 * five bordered boxes beneath it would read as a second instrument competing with the first. What
 * carries the hierarchy here is type size and a single rule, which is all a legend needs.
 */
.apx-legend {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(10rem, 100%), 1fr));
  gap: 0;
  margin: 0 0 var(--space-4);
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  background: var(--color-surface);
  overflow: hidden;
}
.apx-legend-item {
  --legend-ink: var(--color-text);
  padding: var(--space-3) var(--space-4);
  border-inline-start: 1px solid var(--color-border);
}
/* `:first-child` leaves a seam on every wrapped row; a negative offset on the track does not. The
   container clips, so the first cell's rule falls outside it at every column count. */
.apx-legend { margin-inline-start: 0; }
.apx-legend-item:first-child { border-inline-start-color: transparent; }
.apx-legend-item[data-tone="warning"] { --legend-ink: var(--warn-text); }
.apx-legend-item[data-tone="critical"] { --legend-ink: var(--critical-text); }
.apx-legend-item[data-tone="none"] { --legend-ink: var(--color-text-muted); }
.apx-legend-label {
  margin: 0;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-text-subtle);
}
.apx-legend-value {
  margin: 4px 0 0;
  font-family: var(--font-mono);
  font-size: 19px;
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.1;
  color: var(--legend-ink);
}
.apx-legend-hint { margin: 2px 0 0; font-size: 11px; line-height: 1.35; color: var(--color-text-subtle); }

.apx-signals {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr));
  gap: var(--space-3);
  margin-block-end: var(--space-4);
}

.apx-split {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(26rem, 100%), 1fr));
  gap: var(--space-4);
  margin-block-end: var(--space-4);
  align-items: start;
}
/*
 * The lead row: the evidence for the verdict, beside a rail of the short panels.
 *
 * Asymmetric on purpose. Two equal columns made the page's height the *taller* panel's height in
 * both rows, and the other column stood empty for the difference — 443px beside the checklist and
 * 1,235px beside the incident feed on the live `storefront`, which is more than a screen and a half
 * of nothing on one page. A void beside a panel does not read as whitespace; it reads as something
 * that failed to load.
 *
 * 1.35fr to the checklist because it is nine rows of sentence and the rail is three short panels of
 * labels — equal columns would wrap the sentences while the rail kept space it had no use for.
 */
.apx-split-lead { grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); }
.apx-rail { display: grid; align-content: start; gap: var(--space-4); min-width: 0; }
/* The rail's own panels carry the gap, so their bottom margin would double it. */
.apx-rail > .apx-panel { margin-block-end: 0; }
@media (max-width: 68rem) {
  .apx-split-lead { grid-template-columns: minmax(0, 1fr); }
}

.apx-panel {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  background: var(--color-surface);
  padding: var(--space-4) var(--space-5);
  min-width: 0;
  margin-block-end: var(--space-4);
}
.apx-split > .apx-panel { margin-block-end: 0; }
.apx-panel-flush { padding: 0; overflow: clip; }
.apx-panel-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--space-2); margin-block-end: var(--space-3); }
.apx-panel-head-pad { padding: var(--space-4) var(--space-5) 0; }
.apx-panel-head h2 { margin: 0; font-size: var(--text-lg); font-weight: 600; letter-spacing: -0.01em; }
.apx-panel-note { font-size: var(--text-sm); color: var(--color-text-subtle); }
.apx-panel-link { font-size: var(--text-sm); color: var(--color-text-muted); }
.apx-sub-h { margin: var(--space-4) 0 var(--space-2); font-size: var(--text-xs); font-weight: 500; letter-spacing: 0.06em; text-transform: uppercase; color: var(--color-text-subtle); }
.apx-empty { margin: 0; font-size: var(--text-sm); line-height: var(--leading-body); color: var(--color-text-muted); }
.apx-panel-flush > .apx-empty { padding: 0 var(--space-5) var(--space-5); }
.apx-empty-sub { margin-block-start: var(--space-3); color: var(--color-text-subtle); }
/* The DNS list sits under the entry points, separated so the two read as two questions. */
.apx-dns { margin-block-start: var(--space-2); border-block-start: 1px solid var(--color-border); }

/* The checklist: one row per check, the state carried by a coloured mark and a word. */
.apx-checks { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.apx-check {
  --state: var(--color-text-subtle);
  display: grid;
  grid-template-columns: 1.5rem minmax(0, 1fr) auto;
  gap: var(--space-3);
  align-items: start;
  padding: var(--space-2);
  border-radius: 8px;
}
.apx-check[data-state="pass"] { --state: var(--ok); }
.apx-check[data-state="warn"] { --state: var(--warn); }
.apx-check[data-state="fail"] { --state: var(--critical); background: color-mix(in srgb, var(--critical) 7%, transparent); }
.apx-check-mark {
  inline-size: 1.5rem;
  block-size: 1.5rem;
  display: grid;
  place-items: center;
  border-radius: 50%;
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--state);
  background: color-mix(in srgb, var(--state) 14%, transparent);
}
.apx-check-body { display: grid; gap: 2px; min-width: 0; }
.apx-check-label { font-size: var(--text-base); font-weight: 500; color: var(--color-text); }
.apx-check-detail { font-size: var(--text-sm); line-height: var(--leading-body); color: var(--color-text-muted); overflow-wrap: anywhere; }
.apx-check[data-state="unmeasured"] .apx-check-label { color: var(--color-text-muted); }
.apx-check-state { font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.05em; color: var(--state); padding-block-start: 3px; }

/* Rows: entry points, DNS records, dependencies. */
.apx-rows { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.apx-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--space-3);
  align-items: center;
  padding: var(--space-2);
  border-radius: 8px;
}
.apx-row:hover { background: var(--color-hover); }
.apx-row[data-unknown="yes"] { background: color-mix(in srgb, var(--warn) 6%, transparent); }
.apx-row-main { display: grid; gap: 2px; min-width: 0; }
.apx-row-title { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); font-size: var(--text-base); color: var(--color-text); font-weight: 500; overflow-wrap: anywhere; }
.apx-row-sub { font-size: var(--text-sm); color: var(--color-text-subtle); overflow-wrap: anywhere; line-height: var(--leading-body); }
.apx-row-figs { display: flex; flex-wrap: wrap; justify-content: end; gap: var(--space-3); font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-text-muted); font-variant-numeric: tabular-nums; }

.apx-dot, .apx-hop-dot { --band: var(--color-text-subtle); inline-size: 0.5rem; block-size: 0.5rem; border-radius: 50%; background: var(--band); flex: none; }
.apx-dot[data-band="healthy"], .apx-hop-dot[data-band="healthy"] { --band: var(--ok); }
.apx-dot[data-band="degraded"], .apx-hop-dot[data-band="degraded"] { --band: var(--warn); box-shadow: 0 0 0 3px color-mix(in srgb, var(--warn) 22%, transparent); }
.apx-dot[data-band="critical"], .apx-hop-dot[data-band="critical"] { --band: var(--critical); box-shadow: 0 0 0 3px color-mix(in srgb, var(--critical) 22%, transparent); }

.apx-tag { font-size: var(--text-xs); font-weight: 400; padding: 0 6px; border-radius: 6px; border: 1px solid var(--color-border); color: var(--color-text-subtle); }
.apx-tag-warn { color: var(--warn-text); border-color: color-mix(in srgb, var(--warn) 40%, transparent); }
.apx-tag-cross { color: var(--info-text); border-color: color-mix(in srgb, var(--info) 45%, transparent); }

/* Incidents and alerts feed, and the change timeline. */
.apx-feed { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
/*
 * A fixed badge column, so every sentence in the feed starts at the same x.
 *
 * `auto` sized it to each row's own badge — "alert" is narrower than "critical" is narrower than
 * "resolved" — so the text ran at three different left edges down one list and the column read as
 * ragged. Measured at 138, 150 and 157px on the live `storefront`.
 */
.apx-feed-row { display: grid; grid-template-columns: minmax(5.5rem, auto) minmax(0, 1fr) auto; gap: var(--space-3); align-items: center; padding: var(--space-2); border-radius: 8px; }
/* A long alert wraps to three lines; its badge and time belong at the top of that block, not adrift
   in the middle of it. Rows whose text is one line are unaffected — there is nothing to align to. */
.apx-feed-row:has(.apx-feed-text) { align-items: start; }
.apx-feed-row > :first-child, .apx-feed-row > .apx-feed-time { margin-block-start: 1px; }
.apx-feed-row:hover { background: var(--color-hover); }
.apx-feed-text { font-size: var(--text-base); color: var(--color-text); text-decoration: none; overflow-wrap: anywhere; }
/*
 * The second group: what was wrong recently, one step quieter than what is wrong now.
 *
 * Same row, less contrast — not hidden and not a different shape, because a reader comparing an
 * open incident with the one that looks like it last week has to be able to read both the same way.
 */
.apx-feed-group {
  margin: var(--space-5) 0 var(--space-2);
  padding-block-start: var(--space-3);
  border-block-start: 1px solid var(--color-border);
  font-size: var(--text-sm);
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--color-text-subtle);
}
.apx-feed-row[data-resolved="yes"] .apx-feed-text { color: var(--color-text-muted); }
.apx-feed-row[data-resolved="yes"] { opacity: 0.85; }
a.apx-feed-text:hover { text-decoration: none; text-underline-offset: 3px; }
.apx-feed-time { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-text-subtle); }

.apx-timeline { list-style: none; margin: 0; padding: 0; display: grid; }
.apx-tl-row {
  --sev: var(--color-text-subtle);
  display: grid;
  grid-template-columns: 4.5rem 1rem minmax(0, 1fr);
  gap: var(--space-2);
  align-items: baseline;
  padding-block: var(--space-1);
  position: relative;
}
.apx-tl-row[data-severity="warning"] { --sev: var(--warn); }
.apx-tl-row[data-severity="critical"] { --sev: var(--critical); }
.apx-tl-time { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-text-subtle); text-align: end; }
.apx-tl-dot { inline-size: 0.5rem; block-size: 0.5rem; border-radius: 50%; background: var(--sev); justify-self: center; align-self: center; }
.apx-tl-text { font-size: var(--text-sm); color: var(--color-text-muted); line-height: var(--leading-body); overflow-wrap: anywhere; }
.apx-tl-text strong { color: var(--color-text); font-weight: 500; }

/* ------------------------------------------------------------------------- path graph */

.apx-graph { --apx-graph-h: clamp(26rem, calc(100vh - 22rem), 46rem); border-block-start: 1px solid var(--color-border); margin-block-start: var(--space-3); }

.apx-graph-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
  padding: var(--space-2) var(--space-4);
  border-block-end: 1px solid var(--color-border);
}

.apx-tiers {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 2px;
  padding: 2px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border);
  background: var(--color-sunken);
}

.apx-tier-btn {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  border: 1px solid transparent;
  background: transparent;
  color: var(--color-text-subtle);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  line-height: 1;
  block-size: 26px;
  padding: 0 10px;
  border-radius: 7px;
  cursor: pointer;
  white-space: nowrap;
}
.apx-tier-btn:hover { color: var(--color-text); }
.apx-tier-btn[data-on="yes"] { background: var(--color-surface); border-color: var(--color-border-strong); color: var(--color-text); }
.apx-tier-btn:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 1px; }

.apx-cross-note { display: inline-flex; align-items: center; gap: var(--space-2); margin-inline-start: auto; font-size: var(--text-sm); color: var(--info-text); }
.apx-cross-swatch { inline-size: 1.25rem; block-size: 0; border-block-start: 2px dashed var(--info); }
/*
 * The flow caption. `margin-inline-start: auto` only when it is the first thing pushed right, so a
 * graph with both notes keeps them together at the end of the bar rather than at opposite ends.
 */
.apx-flow-note { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-sm); color: var(--color-text-muted); }
.apx-flow-note:first-of-type, .apx-cross-note + .apx-flow-note { margin-inline-start: 0; }
.apx-graph-bar > .apx-flow-note:not(.apx-cross-note + .apx-flow-note) { margin-inline-start: auto; }
.apx-flow-swatch {
  inline-size: 1.25rem;
  block-size: 0;
  border-block-start: 3px dotted var(--ok);
  animation: apx-flow-swatch 1.6s linear infinite;
}
@keyframes apx-flow-swatch { from { opacity: 0.35; } 50% { opacity: 1; } to { opacity: 0.35; } }

.apx-graph-stage { display: grid; grid-template-columns: minmax(0, 1fr); }
.apx-graph[data-inspector="yes"] .apx-graph-stage { grid-template-columns: minmax(0, 1fr) minmax(0, 22rem); }
@media (max-width: 1100px) {
  .apx-graph[data-inspector="yes"] .apx-graph-stage { grid-template-columns: minmax(0, 1fr); }
}

.apx-canvas-wrap { position: relative; min-width: 0; background: var(--color-background); overflow: clip; }
/* `user-select: none`: a drag pans the drawing, and without it every drag also selected the figures it crossed. */
.apx-canvas { display: block; inline-size: 100%; block-size: var(--apx-graph-h); cursor: grab; touch-action: none; -webkit-user-select: none; -moz-user-select: none; user-select: none; }
.apx-canvas[data-panning="yes"] { cursor: grabbing; }

.apx-lane-band { fill: color-mix(in srgb, var(--color-text) 2.5%, transparent); stroke: var(--color-border); stroke-width: 1; }
.apx-lane[data-collecting="no"] .apx-lane-band { stroke-dasharray: 6 6; }
.apx-lane-name { fill: var(--color-text-muted); font-size: 13px; font-weight: 500; letter-spacing: 0.02em; }
.apx-lane[data-collecting="no"] .apx-lane-name { fill: var(--warn-text); }
.apx-col-label { fill: var(--color-text-subtle); font-size: 11px; font-weight: 500; letter-spacing: 0.08em; text-transform: uppercase; text-anchor: middle; }
/*
 * Column titles pinned to the top of the camera's view (see `path-graph.tsx`). The band keeps the
 * cards and lines scrolling beneath them from printing through the titles; it takes no pointer
 * events, so a card half under it can still be clicked.
 */
.apx-col-heads { pointer-events: none; }
.apx-col-band { fill: var(--color-background); stroke: var(--color-border); stroke-width: 1; }
.apx-col-heads[data-pinned="yes"] .apx-col-label { fill: var(--color-text-muted); }

.apx-edges { pointer-events: visibleStroke; }
/* Nodes above the edges, taking events normally; one handle for the layer. */
.apx-nodes { pointer-events: auto; }
.apx-edge { transition: opacity var(--motion-base) var(--ease-out); }
.apx-edge[data-dim="yes"] { opacity: 0.14; }
.apx-edge-line { stroke: color-mix(in srgb, var(--color-text) 28%, transparent); stroke-width: 1.5; }
.apx-edge[data-relation="calls"] .apx-edge-line { stroke-width: 2; }
.apx-edge[data-cross="yes"] .apx-edge-line { stroke: var(--info); stroke-width: 2.25; stroke-dasharray: 7 5; }
.apx-arrowhead { fill: color-mix(in srgb, var(--color-text) 35%, transparent); }
.apx-arrowhead-cross { fill: var(--info); }
.apx-edge-label { fill: var(--color-text-subtle); font-family: var(--font-mono); font-size: 11px; text-anchor: middle; paint-order: stroke; stroke: var(--color-background); stroke-width: 4px; stroke-linejoin: round; }
/*
 * The figures' own layer, over the lines and the moving dots (see `path-graph.tsx`). It takes no
 * pointer events, so hovering a figure still reaches the line underneath and its full title. It
 * dims with its hop, exactly as the hop's group does.
 */
.apx-edge-tags { pointer-events: none; }
.apx-edge-tag { transition: opacity var(--motion-base) var(--ease-out); }
.apx-edge-tag[data-dim="yes"] { opacity: 0.14; }

/* ------------------------------------------------------------------ traffic, moving
 *
 * Two layers over a hop that carries traffic.
 *
 * **The pipe** — `.apx-flow` — is a solid band whose width is the hop's request rate on `flow.ts`'s
 * log scale and whose colour is the server's verdict on it. It used to be the motion too, as dashes
 * marching along it; that read as a progress bar, every hop moved in the same rhythm, and a stream
 * of evenly spaced dashes is the one thing real traffic never looks like. It is still now, and the
 * dots ride it.
 *
 * **The dots** — `.apx-particle` — are `traffic-layer.tsx`: drawn as a Poisson process at the measured
 * rate, red at the measured failure share, crossing at a speed set by the measured p95. All of the
 * randomness is in their timing and none of it is in a number. See `particles.ts`.
 */
.apx-flow {
  stroke: var(--flow-colour, var(--ok));
  stroke-width: var(--flow-width, 2);
  stroke-linecap: round;
  stroke-opacity: 0.32;
}
/*
 * A hop whose traffic was carried onto it from a measurement elsewhere — a Service's endpoints, a
 * pod's share. Quieter than a hop the mesh measured directly, so the two kinds of figure stay
 * distinguishable at a glance; the tooltip says which measurement it was.
 */
.apx-flow[data-carried="yes"] { stroke-opacity: 0.2; }
/* The hop's own verdict, classified on the server so this cannot disagree with the mesh map. */
.apx-flow[data-band="healthy"] { --flow-colour: var(--ok); }
.apx-flow[data-band="degraded"] { --flow-colour: var(--warn); }
.apx-flow[data-band="critical"] { --flow-colour: var(--critical); }
.apx-flow[data-band="unknown"] { --flow-colour: var(--color-text-subtle); }

/*
 * A particle. Out of the pool until `data-on`, so the 320 circles that exist at all times cost
 * nothing to paint while idle. A failure is drawn larger as well as red, so the share survives for a
 * reader who cannot tell this red from this green.
 *
 * It was called `.apx-dot`, which is also the row verdict dot defined ~190 lines above. Same
 * specificity, later in the file, so this block won — and `visibility: hidden` applied to **every**
 * verdict dot in Ways in, DNS, Dependencies, Services and the five row lists on the incident page.
 * Nine call sites across two pages lost their colour to one SVG circle, silently: the dots still
 * occupied their grid column, so the rows stayed aligned and nothing looked broken. Renamed rather
 * than reordered, because the collision would come back the next time somebody moved a block.
 */
.apx-particle { fill: var(--ok); stroke: var(--color-surface); stroke-width: 1; visibility: hidden; }
.apx-particle[data-on="yes"] { visibility: visible; }
.apx-particle[data-failed="yes"] { fill: var(--critical); r: 4.6px; }

/* ------------------------------------------------------------------ the live bar */

/*
 * Inset like the bar above it. It sat flush against the panel's border while the search and tier
 * bar above it is padded by `--space-4`, so the LIVE pill, its sentence and the caption under it
 * started 16px to the left of everything else in the panel, and Pause/Latency/Failures touched the
 * right edge.
 */
.apx-live-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
  padding: var(--space-2) var(--space-4) 0;
  margin-block-end: var(--space-2);
  min-width: 0;
}
/*
 * The verdict. Green and pulsing only when the measurements are within the server's stale threshold;
 * amber and still once they are not. A "LIVE" mark that is simply always on is the easiest lie a
 * monitoring page can tell, so this one has to be earned on every read.
 */
.apx-live {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 9px 2px 7px;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  font-size: 11px;
  font-weight: 650;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-subtle);
}
.apx-live-dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.apx-live[data-state="live"] { color: var(--ok-text, var(--ok)); border-color: color-mix(in srgb, var(--ok) 45%, var(--color-border)); }
.apx-live[data-state="live"] .apx-live-dot { animation: apx-live-pulse 1.8s ease-out infinite; }
.apx-live[data-state="paused"] { color: var(--color-text); }
.apx-live[data-state="stale"] { color: var(--warn-text); border-color: color-mix(in srgb, var(--warn) 45%, var(--color-border)); }
@keyframes apx-live-pulse {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, currentColor 55%, transparent); }
  70% { box-shadow: 0 0 0 6px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}
.apx-live-detail { font-size: var(--text-sm); color: var(--color-text-muted); min-width: 0; overflow-wrap: anywhere; }
.apx-live-controls { display: inline-flex; gap: 4px; margin-inline-start: auto; }
.apx-live-btn {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  padding: 3px 10px;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: var(--color-surface);
  color: var(--color-text-muted);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.apx-live-btn:hover:not(:disabled) { color: var(--color-text); border-color: var(--color-border-strong); }
.apx-live-btn:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.apx-live-btn[aria-pressed="true"] { color: var(--color-text); background: var(--bg-sunken); border-color: var(--color-border-strong); }
.apx-live-btn:disabled { opacity: 0.45; cursor: not-allowed; }
/* The caption the motion owes the reader: a dot is a rate drawn, never a request observed. */
.apx-live-caption { flex-basis: 100%; font-size: 11px; color: var(--color-text-subtle); }

/*
 * What a Service or Pod received, hung off the card's top-right corner. Its own pill rather than a
 * third line in a 50-unit card that already carries the name and the readiness.
 */
.apx-node-load rect { fill: var(--color-surface); stroke: var(--color-border-strong); stroke-width: 1; }
.apx-node-load text { fill: var(--color-text-muted); font-family: var(--font-mono); font-size: 10px; text-anchor: middle; }
.apx-node-load[data-failing="yes"] rect { stroke: var(--critical); }
.apx-node-load[data-failing="yes"] text { fill: var(--critical-text); }
/* A failing hop's figures are read, not glanced at, so the label takes the verdict's colour too. */
.apx-edge-label[data-band="degraded"] { fill: var(--warn-text); }
.apx-edge-label[data-band="critical"] { fill: var(--critical-text); }
/* Right-aligned onto the bow's apex, so a crescent's figures sit in the gutter and never under the
   card beside them. Here and not as a `text-anchor` attribute: CSS wins over the attribute in SVG. */
.apx-edge-label[data-anchor="end"] { text-anchor: end; }

/*
 * Volume, matching `LabelTier` in `path-layout.ts`.
 *
 * A quiet hop is carrying traffic and is fine, and the only reason its rate is on the canvas at all
 * is to answer "is anything using this". It is set a step down and a step back so it reads as
 * texture rather than as a finding — the eye should cross twenty of these without stopping.
 *
 * A loud hop is the finding. It keeps the full size, takes the weight, and keeps the band colour it
 * already had. Nothing is added to shout with: no chip, no rule, no glow. On a canvas where every
 * other figure has just stepped back, weight alone is enough — and a badge behind each of the four
 * would be four more objects on a drawing that has thirty-five.
 */
/*
 * Stepped back with `fill-opacity`, not `opacity`. `opacity` fades the halo with the glyphs, so the
 * lines a quiet figure sits over showed through its own halo at 38% and "0.4/s" read as part of the
 * arc bundle. Fading only the fill keeps the halo whole and the figure still reads as texture.
 */
.apx-edge-label[data-tier="quiet"] { font-size: 10px; fill-opacity: 0.72; }
.apx-edge-label[data-tier="loud"] { font-weight: 600; }
/*
 * A thinner halo on the quiet ones. At 4px behind every label it was a thick rim that ate the lines
 * passing under it; 3px is enough to lift a short rate off the arcs it sits beside.
 */
.apx-edge-label[data-tier="quiet"] { stroke-width: 3px; }

.apx-cross-badge rect { fill: var(--color-surface); stroke: var(--info); stroke-width: 1; }
.apx-cross-badge text { fill: var(--info-text); font-size: 11px; font-weight: 500; text-anchor: middle; dominant-baseline: central; }

.apx-node { --band: var(--color-text-subtle); cursor: pointer; transition: opacity var(--motion-base) var(--ease-out); }
.apx-node[data-band="healthy"] { --band: var(--ok); }
.apx-node[data-band="degraded"] { --band: var(--warn); }
.apx-node[data-band="critical"] { --band: var(--critical); }
.apx-node[data-dim="yes"] { opacity: 0.3; }
.apx-node-card { fill: var(--color-surface); stroke: var(--color-border-strong); stroke-width: 1; }
.apx-node[data-band="critical"] .apx-node-card { fill: color-mix(in srgb, var(--critical) 9%, var(--color-surface)); stroke: color-mix(in srgb, var(--critical) 60%, transparent); }
.apx-node[data-band="degraded"] .apx-node-card { fill: color-mix(in srgb, var(--warn) 7%, var(--color-surface)); stroke: color-mix(in srgb, var(--warn) 50%, transparent); }
.apx-node[data-foreign="yes"] .apx-node-card { stroke-dasharray: 5 4; }
.apx-node:hover .apx-node-card { stroke: var(--color-text-muted); }
.apx-node[data-selected="yes"] .apx-node-card { stroke: var(--color-text); stroke-width: 2; }
.apx-node:focus { outline: none; }
.apx-node:focus-visible .apx-node-card { stroke: var(--color-focus-ring); stroke-width: 3; }
.apx-node-edge { fill: var(--band); }
.apx-node-mark { fill: color-mix(in srgb, var(--band) 16%, var(--color-sunken)); }
.apx-node-mark-text { fill: var(--band); font-family: var(--font-mono); font-size: 10px; font-weight: 600; text-anchor: middle; }
.apx-node[data-band="unknown"] .apx-node-mark-text { fill: var(--color-text-muted); }
.apx-node-name { fill: var(--color-text); font-size: 13px; font-weight: 500; }
.apx-node-sub { fill: var(--color-text-subtle); font-size: 11px; }

@media (prefers-reduced-motion: reduce) {
  .apx-node, .apx-edge, .apx-edge-tag { transition: none; }
  /*
   * The stream stops and becomes a solid band.
   *
   * Not hidden: the width is the hop's rate and the colour is its verdict, and both are information
   * this reader would otherwise lose. What goes is the movement, which is the part that is a
   * preference — and `animation: none` alone would leave the dashes frozen mid-gap, which reads as
   * a rendering fault rather than as a still picture.
   */
  .apx-flow { animation: none; stroke-dasharray: none; }
  .apx-flow-swatch { animation: none; opacity: 1; }
  /* Still green when live — the verdict is information; only the pulse is a preference. */
  .apx-live[data-state="live"] .apx-live-dot { animation: none; }
}

.apx-dock {
  position: absolute;
  inset-block-end: var(--space-3);
  inset-inline-start: var(--space-3);
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  box-shadow: 0 10px 30px -8px rgb(0 0 0 / 0.45);
}
.apx-dock-btn {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  border: 0;
  background: transparent;
  color: var(--color-text-muted);
  inline-size: 28px;
  block-size: 28px;
  border-radius: 7px;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}
.apx-dock-btn:hover:not(:disabled) { background: var(--color-hover); color: var(--color-text); }
.apx-dock-btn:disabled { opacity: 0.35; cursor: not-allowed; }
.apx-dock-btn:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 1px; }
.apx-dock-zoom { min-inline-size: 3.25rem; text-align: center; font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-text-muted); font-variant-numeric: tabular-nums; }

.apx-notes { margin: 0; padding: var(--space-2) var(--space-5); list-style: none; border-block-start: 1px solid var(--color-border); background: var(--color-sunken); display: grid; gap: 2px; }
.apx-notes li { font-size: var(--text-sm); color: var(--color-text-muted); }

/* The hop inspector, docked beside the drawing at its height. */
.apx-inspector {
  border-inline-start: 1px solid var(--color-border);
  background: var(--color-surface);
  padding: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  max-block-size: var(--apx-graph-h);
  overflow-y: auto;
  overscroll-behavior: contain;
  min-width: 0;
}
@media (max-width: 1100px) {
  .apx-inspector { border-inline-start: 0; border-block-start: 1px solid var(--color-border); max-block-size: none; }
}
.apx-insp-head { display: flex; align-items: center; gap: var(--space-3); }
.apx-insp-mark {
  --band: var(--color-text-subtle);
  flex: none;
  inline-size: 2.5rem;
  block-size: 2rem;
  display: grid;
  place-items: center;
  border-radius: 8px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  color: var(--band);
  background: color-mix(in srgb, var(--band) 14%, var(--color-sunken));
}
.apx-insp-mark[data-band="healthy"] { --band: var(--ok); }
.apx-insp-mark[data-band="degraded"] { --band: var(--warn); }
.apx-insp-mark[data-band="critical"] { --band: var(--critical); }
.apx-insp-title { flex: 1 1 auto; min-width: 0; }
.apx-insp-title h3 { margin: 0; font-size: var(--text-lg); font-weight: 600; overflow-wrap: anywhere; }
.apx-insp-title p { margin: 2px 0 0; font-size: var(--text-sm); color: var(--color-text-subtle); overflow-wrap: anywhere; }
.apx-insp-close { -webkit-appearance: none; -moz-appearance: none; appearance: none; border: 0; background: transparent; color: var(--color-text-subtle); inline-size: 28px; block-size: 28px; border-radius: 7px; font-size: 1.25rem; line-height: 1; cursor: pointer; align-self: start; }
.apx-insp-close:hover { background: var(--color-hover); color: var(--color-text); }
.apx-insp-close:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 1px; }
.apx-insp-state { margin: 0; font-size: var(--text-sm); color: var(--color-text-muted); padding: var(--space-2) var(--space-3); border-radius: 8px; background: var(--color-sunken); line-height: var(--leading-body); }
.apx-insp-state[data-band="critical"] { color: var(--critical-text); background: color-mix(in srgb, var(--critical) 10%, transparent); }
.apx-insp-state[data-band="degraded"] { color: var(--warn-text); background: color-mix(in srgb, var(--warn) 10%, transparent); }
.apx-insp-state[data-band="healthy"] { color: var(--ok-text); background: color-mix(in srgb, var(--ok) 9%, transparent); }
.apx-insp-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }

.apx-facts { margin: 0; display: grid; gap: 1px; background: var(--color-border); border: 1px solid var(--color-border); border-radius: 8px; overflow: clip; }
.apx-facts > div { display: grid; grid-template-columns: 6rem minmax(0, 1fr); gap: var(--space-2); background: var(--color-surface); padding: var(--space-1) var(--space-3); }
.apx-facts dt { font-size: var(--text-xs); color: var(--color-text-subtle); text-transform: uppercase; letter-spacing: 0.05em; padding-block-start: 2px; }
.apx-facts dd { margin: 0; font-family: var(--font-mono); font-size: var(--text-sm); color: var(--color-text); overflow-wrap: anywhere; }

.apx-hops h4 { margin: 0 0 var(--space-1); font-size: var(--text-xs); font-weight: 500; text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-subtle); }
.apx-hops ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.apx-hop {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  inline-size: 100%;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--space-2);
  align-items: center;
  border: 1px solid transparent;
  background: transparent;
  color: var(--color-text);
  font: inherit;
  text-align: start;
  padding: var(--space-2);
  border-radius: 8px;
  cursor: pointer;
}
.apx-hop:hover { background: var(--color-hover); border-color: var(--color-border); }
.apx-hop:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 1px; }
.apx-hop[data-cross="yes"] { border-color: color-mix(in srgb, var(--info) 35%, transparent); }
.apx-hop-name { display: grid; gap: 1px; min-width: 0; font-size: var(--text-sm); overflow-wrap: anywhere; }
.apx-hop-rel { font-size: var(--text-xs); color: var(--color-text-subtle); }
.apx-hop-conf { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-text-subtle); }

@media (max-width: 720px) {
  .apx-graph { --apx-graph-h: clamp(20rem, 60vh, 30rem); }
  .apx-graph-bar { padding: var(--space-2); }
  .apx-tiers { max-inline-size: 100%; overflow-x: auto; overscroll-behavior-x: contain; flex-wrap: nowrap; }
  .apx-cross-note { margin-inline-start: 0; }
  .apx-panel { padding: var(--space-3); }
  .apx-row, .apx-feed-row { grid-template-columns: auto minmax(0, 1fr); }
  .apx-row > :last-child, .apx-feed-row > :last-child { grid-column: 2; justify-self: start; }
}

/* ======================================================================================
   Incident workspace (`/incidents/[id]`) — prefix `iws-`, plus the `apx-` panels and rows it shares
   with the application page. Same token rules as the block above.
   ====================================================================================== */

/* An incident's subject on the request path: a critical ring outside the card, never a fill change,
   so the hop's own health colour still reads underneath it. */
.apx-node-affected { fill: none; stroke: var(--critical); stroke-width: 2; stroke-dasharray: 4 3; }

.iws-body { display: grid; gap: var(--space-4); margin-block-start: var(--space-4); }
.iws-body > .apx-panel { margin-block-end: 0; }

.iws-impact { margin: 0 0 var(--space-3); font-size: var(--text-md); line-height: var(--leading-body); color: var(--color-text); }
.iws-root {
  margin: 0 0 var(--space-3);
  padding: var(--space-3);
  border-radius: 8px;
  border: 1px solid color-mix(in srgb, var(--critical) 30%, transparent);
  background: color-mix(in srgb, var(--critical) 8%, transparent);
  font-size: var(--text-base);
  line-height: var(--leading-body);
  color: var(--color-text);
}
.iws-conf { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-text-subtle); }

.iws-trigger {
  display: grid;
  gap: 4px;
  padding: var(--space-3);
  border-radius: 8px;
  border: 1px solid color-mix(in srgb, var(--warn) 35%, transparent);
  background: color-mix(in srgb, var(--warn) 8%, transparent);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.iws-trigger strong { color: var(--color-text); font-size: var(--text-base); }
.iws-trigger-when { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--warn-text); }

/* The timeline: a time gutter, a rail, and the entry. Entries after onset sit on a solid rail,
   before it on a dashed one, so "before" and "after" read without the offsets. */
.iws-timeline { list-style: none; margin: 0; padding: 0; display: grid; }
.iws-tl-row {
  --kind: var(--color-text-subtle);
  display: grid;
  grid-template-columns: 6.5rem 1.25rem minmax(0, 1fr);
  gap: var(--space-2);
  padding-block: var(--space-2);
  position: relative;
}
.iws-tl-row::before {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: calc(6.5rem + var(--space-2) + 0.625rem - 1px);
  border-inline-start: 2px dashed var(--color-border);
}
.iws-tl-row[data-after="yes"]::before { border-inline-start-style: solid; }
.iws-tl-row[data-kind="signal"] { --kind: var(--critical); }
.iws-tl-row[data-kind="change"] { --kind: var(--warn); }
.iws-tl-row[data-kind="action"], .iws-tl-row[data-kind="verification"] { --kind: var(--info); }
.iws-tl-row[data-kind="incident"] { --kind: var(--color-text); }
.iws-tl-row[data-trigger="yes"] { background: color-mix(in srgb, var(--warn) 8%, transparent); border-radius: 8px; }
.iws-tl-time { display: grid; justify-items: end; font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-text-muted); }
.iws-tl-offset { color: var(--color-text-subtle); }
.iws-tl-dot { position: relative; z-index: 1; inline-size: 0.75rem; block-size: 0.75rem; margin-block-start: 3px; justify-self: center; border-radius: 50%; background: var(--kind); box-shadow: 0 0 0 3px var(--color-surface); }
.iws-tl-body { display: grid; gap: 2px; min-width: 0; }
.iws-tl-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); font-size: var(--text-base); color: var(--color-text); }
.iws-tl-head strong { font-weight: 500; overflow-wrap: anywhere; }
.iws-tl-kind { font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.06em; color: var(--kind); }
.iws-tl-detail { font-size: var(--text-sm); line-height: var(--leading-body); color: var(--color-text-muted); overflow-wrap: anywhere; }

/* Logs: monospace, wrapped never truncated, errors tinted. */
.iws-logs { max-block-size: 36rem; overflow-y: auto; overscroll-behavior: contain; border: 1px solid var(--color-border); border-radius: 8px; background: var(--color-background); }
.iws-log {
  display: grid;
  grid-template-columns: 5.5rem 3.5rem minmax(6rem, 14rem) minmax(0, 1fr);
  gap: var(--space-2);
  padding: 3px var(--space-3);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 1.55;
  color: var(--color-text-muted);
  border-block-end: 1px solid color-mix(in srgb, var(--color-border) 60%, transparent);
}
.iws-log[data-level="error"], .iws-log[data-level="fatal"] { background: color-mix(in srgb, var(--critical) 7%, transparent); color: var(--color-text); }
.iws-log[data-level="warn"], .iws-log[data-level="warning"] { background: color-mix(in srgb, var(--warn) 6%, transparent); }
.iws-log time { color: var(--color-text-subtle); }
.iws-log-level { text-transform: uppercase; }
.iws-log[data-level="error"] .iws-log-level, .iws-log[data-level="fatal"] .iws-log-level { color: var(--critical-text); }
.iws-log-pod { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--color-text-subtle); }
.iws-log-msg { white-space: pre-wrap; overflow-wrap: anywhere; }

@media (max-width: 720px) {
  .iws-tl-row { grid-template-columns: 4.5rem 1.25rem minmax(0, 1fr); }
  .iws-tl-row::before { inset-inline-start: calc(4.5rem + var(--space-2) + 0.625rem - 1px); }
  .iws-log { grid-template-columns: 4.5rem minmax(0, 1fr); }
  .iws-log-level, .iws-log-pod { display: none; }
}

/* Evidence subjects in the SRE chat: links under a block to the objects it is about and their
   application. Small and quiet — the evidence text is the thing being read; these are the exits. */
.sre-block-links { display: flex; flex-wrap: wrap; gap: 6px; margin-block-start: var(--space-2); }
.sre-block-link {
  font-size: var(--text-xs);
  padding: 2px 8px;
  border-radius: var(--radius-full);
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text-muted);
  text-decoration: none;
}
.sre-block-link:hover { color: var(--color-text); border-color: var(--color-border-strong); }
.sre-block-link:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 1px; }

/* Risk and rollback on each remedy in the fix panel. A small key/value sheet under the caution. */
.fix-option-facts { margin: var(--space-2) 0; display: grid; gap: 4px; font-size: var(--text-sm); }
.fix-option-facts > div { display: grid; grid-template-columns: 5.5rem minmax(0, 1fr); gap: var(--space-2); }
.fix-option-facts dt { color: var(--color-text-subtle); font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.05em; padding-block-start: 2px; }
.fix-option-facts dd { margin: 0; color: var(--color-text-muted); line-height: var(--leading-body); }
.fix-option-facts dd[data-risk="low"] { color: var(--ok-text); }
.fix-option-facts dd[data-risk="medium"] { color: var(--warn-text); }
.fix-option-facts dd[data-risk="high"] { color: var(--critical-text); }
.fix-option-facts dd[data-history="good"] { color: var(--ok-text); }
.fix-option-facts dd[data-history="bad"] { color: var(--critical-text); }

/* ======================================================== applications: what is coming (Ahead) */

/*
 * One column: the forecast, then the predictions. Side by side they would be two panels whose heights
 * come from their content — a chart and a list that is often one sentence long — and this page's own
 * layout test measures and refuses exactly that: a panel standing beside a void reads as something that
 * failed to load.
 */
.apx-ahead { display: grid; gap: var(--space-5); min-width: 0; }
.apx-ahead-forecast, .apx-ahead-preds { display: grid; gap: var(--space-3); min-width: 0; }
.apx-ahead-title { margin: 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px var(--space-3); font-size: var(--text-sm); font-weight: 600; color: var(--color-text); }
.apx-ahead-why { font-weight: 400; color: var(--color-text-subtle); overflow-wrap: anywhere; }
.apx-ahead-state { margin: 0; font-size: var(--text-sm); line-height: 1.6; color: var(--color-text-muted); }
.apx-ahead-state.is-error { color: var(--critical-text); }

/*
 * Happened → now → next. One sentence, then three cards read left to right, each marked the way the
 * chart below draws the same span: the past is the solid line on a quiet card, now is the live dot,
 * the next days are dashed. The arrow between cards is the direction of reading, not decoration —
 * on a phone the cards stack and it turns to point down.
 */
.apx-ahead-headline { margin: 0; font-size: var(--text-xl); font-weight: 500; line-height: 1.5; color: var(--color-text); max-width: 80ch; text-wrap: pretty; }

.apx-ahead-story { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-4); }
.apx-ahead-card {
  position: relative;
  display: grid;
  align-content: start;
  gap: 6px;
  min-width: 0;
  padding: var(--space-3) var(--space-4) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: 12px;
  background: var(--color-surface);
}
.apx-ahead-card + .apx-ahead-card::before {
  content: "→";
  position: absolute;
  left: calc(var(--space-4) / -2);
  top: 50%;
  transform: translate(-50%, -50%);
  width: 22px;
  height: 22px;
  display: grid;
  place-items: center;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--color-surface);
  font-size: 12px;
  color: var(--color-text-subtle);
}
.apx-ahead-card[data-phase="past"] { background: var(--color-sunken); }
.apx-ahead-card[data-phase="now"] { border-color: var(--chart-1); box-shadow: 0 0 0 3px color-mix(in srgb, var(--chart-1) 14%, transparent); }
.apx-ahead-card[data-phase="next"] { border-style: dashed; border-color: color-mix(in srgb, var(--chart-1) 60%, var(--color-border)); }
.apx-ahead-card[data-phase="next"][data-tone="warn"] { border-color: var(--warn-text); background: var(--warn-soft); }

.apx-ahead-phase { display: flex; align-items: center; gap: 7px; font-size: 10.5px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-text-subtle); }
.apx-ahead-dot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--color-text-subtle); }
.apx-ahead-card[data-phase="now"] .apx-ahead-dot { background: var(--chart-1); animation: apx-ahead-pulse 2.4s ease-out infinite; }
.apx-ahead-card[data-phase="next"] .apx-ahead-dot { box-sizing: border-box; width: 10px; height: 10px; background: transparent; border: 2px dashed var(--chart-1); }
@keyframes apx-ahead-pulse {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--chart-1) 55%, transparent); }
  70%, 100% { box-shadow: 0 0 0 7px color-mix(in srgb, var(--chart-1) 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
  .apx-ahead-card[data-phase="now"] .apx-ahead-dot { animation: none; }
}

.apx-ahead-big { font-size: var(--text-2xl); font-weight: 650; line-height: 1.15; letter-spacing: -0.01em; color: var(--color-text); overflow-wrap: anywhere; }
.apx-ahead-big small { font-size: 12px; font-weight: 500; letter-spacing: 0; color: var(--color-text-subtle); }
.apx-ahead-line { font-size: var(--text-sm); font-weight: 500; color: var(--color-text); }
.apx-ahead-sub { font-size: 12px; line-height: 1.5; color: var(--color-text-subtle); }

.apx-ahead-chip { justify-self: start; display: inline-flex; align-items: center; gap: 5px; padding: 2px 9px; border-radius: 999px; font-size: 12px; font-weight: 600; }
.apx-ahead-chip[data-tone="up"] { background: var(--info-soft); color: var(--info-text); }
.apx-ahead-chip[data-tone="up"]::before { content: "▲"; font-size: 8px; }
.apx-ahead-chip[data-tone="down"] { background: var(--warn-soft); color: var(--warn-text); }
.apx-ahead-chip[data-tone="down"]::before { content: "▼"; font-size: 8px; }
.apx-ahead-chip[data-tone="normal"] { background: var(--color-sunken); color: var(--color-text-muted); }
.apx-ahead-chip[data-tone="ok"] { background: var(--ok-soft); color: var(--ok-text); }
.apx-ahead-chip[data-tone="ok"]::before { content: "✓"; }
.apx-ahead-chip[data-tone="warn"] { background: var(--warn-soft); color: var(--warn-text); }
.apx-ahead-chip[data-tone="warn"]::before { content: "!"; font-weight: 700; }

@media (max-width: 760px) {
  .apx-ahead-story { grid-template-columns: minmax(0, 1fr); }
  .apx-ahead-card + .apx-ahead-card::before { content: "↓"; left: 50%; top: calc(var(--space-4) / -2); }
  .apx-ahead-headline { font-size: var(--text-lg); }
}

/* The chart's key, drawn with the chart's own strokes, and where the traffic was measured. */
.apx-ahead-chart { display: grid; gap: var(--space-2); min-width: 0; }
.apx-ahead-legend { margin: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 4px var(--space-4); font-size: 12px; color: var(--color-text-subtle); }
.apx-ahead-key { display: inline-flex; align-items: center; gap: 6px; }
.apx-ahead-key::before { content: ""; width: 18px; border-top: 2px solid var(--chart-1); }
.apx-ahead-key[data-k="forecast"]::before { border-top-style: dashed; }
.apx-ahead-key[data-k="band"]::before { height: 10px; border: 0; border-radius: 2px; background: color-mix(in srgb, var(--chart-1) 20%, transparent); }
.apx-ahead-door { flex: 1 1 22rem; text-align: end; overflow-wrap: anywhere; }
@media (max-width: 760px) { .apx-ahead-door { text-align: start; } }

/* The working, one click down: the summary already says which model won and on how many hours. */
.apx-ahead-how { padding-top: var(--space-3); border-top: 1px solid var(--color-border); }
.apx-ahead-how > summary { cursor: pointer; list-style: none; display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px var(--space-3); font-size: var(--text-sm); font-weight: 600; color: var(--color-text); }
.apx-ahead-how > summary::-webkit-details-marker { display: none; }
.apx-ahead-how > summary::before { content: "▸"; width: 1em; color: var(--color-text-subtle); transition: transform 0.15s ease; }
.apx-ahead-how[open] > summary::before { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) { .apx-ahead-how > summary::before { transition: none; } }
.apx-ahead-how > summary:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; border-radius: 4px; }
.apx-ahead-how-sub { font-size: 12px; font-weight: 400; color: var(--color-text-subtle); }
.apx-ahead-how[open] > summary { margin-bottom: var(--space-3); }
.apx-ahead-how > .apx-ahead-sentence { margin-bottom: var(--space-3); }

.apx-ahead-sentence { margin: 0; font-size: var(--text-sm); line-height: 1.6; color: var(--color-text-muted); max-width: 90ch; }
.apx-ahead-sentence.is-warn { color: var(--warn-text); }

/*
 * The forecaster's working: every candidate, its verdict and its reason. The chosen one is the only row
 * drawn at full strength, so the eye lands on the answer and the losers still say why they lost.
 */
.apx-ahead-candidates { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; font-size: 12px; }
.apx-ahead-candidates li {
  display: grid;
  grid-template-columns: minmax(0, 14rem) minmax(0, 6rem) minmax(0, 1fr);
  gap: var(--space-3);
  align-items: baseline;
  color: var(--color-text-subtle);
}
.apx-ahead-candidates li[data-status="won"] { color: var(--color-text); }
.apx-ahead-cand { font-weight: 500; overflow-wrap: anywhere; }
.apx-ahead-verdict { font-family: var(--font-mono); font-size: 11px; }
.apx-ahead-candidates li[data-status="won"] .apx-ahead-verdict { color: var(--ok-text); }
.apx-ahead-cand-why { overflow-wrap: anywhere; }
@media (max-width: 720px) {
  .apx-ahead-candidates li { grid-template-columns: minmax(0, 1fr) auto; }
  .apx-ahead-cand-why { grid-column: 1 / -1; }
}

.apx-ahead-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.apx-ahead-pred {
  display: grid;
  grid-template-columns: minmax(0, max-content) minmax(0, max-content) minmax(0, 1fr);
  gap: 2px var(--space-3);
  align-items: baseline;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: 8px;
}
.apx-ahead-kind { font-size: var(--text-sm); font-weight: 600; color: var(--color-text); }
.apx-ahead-subject { font-size: 12px; color: var(--color-text-muted); overflow-wrap: anywhere; }
.apx-ahead-when { font-size: 11px; color: var(--color-text-subtle); text-align: end; }
.apx-ahead-msg { grid-column: 1 / -1; font-size: 12px; line-height: 1.55; color: var(--color-text-muted); overflow-wrap: anywhere; }
/* The empty case says which empty it is, so it is read rather than skimmed. */
.apx-ahead-note { margin: 0; font-size: var(--text-sm); line-height: 1.6; color: var(--color-text-muted); max-width: 90ch; }
.apx-ahead-preds { padding-top: var(--space-4); border-top: 1px solid var(--color-border); }
.apx-ahead-notes { display: grid; gap: var(--space-2); }
/* Nothing predicted is a state with a way forward, so it is a card with the action in it — not a line. */
.apx-ahead-empty { display: grid; justify-items: start; gap: var(--space-3); padding: var(--space-4); border: 1px dashed var(--color-border-strong); border-radius: 12px; background: var(--color-sunken); }
.apx-ahead-pred[data-tone="critical"] { border-left: 3px solid var(--critical-text); }
.apx-ahead-pred[data-tone="warning"] { border-left: 3px solid var(--warn-text); }
.apx-ahead-pred[data-tone="info"] { border-left: 3px solid var(--info-text); }

/* ---------------------------------------------------------------- accuracy > forecast engine */

/* Three facts side by side: is the held-out forecaster answering, what share of the week's
   forecasts it chose, and how many fell back to a straight line. Wraps to a column on a phone. */
.acc-engine {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-4);
  margin-bottom: var(--space-3);
}

.acc-engine-cell {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-1);
  min-width: 0;
}

.acc-engine-label {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

/* ---------------------------------------------------------------- capacity plan */

/* Grouped by what to do: changes first, then the workloads already sized right. */
.cp-section {
  margin-top: var(--space-6);
}

.cp-section-title {
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin: 0 0 var(--space-3);
}

.cp-plans {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 520px), 1fr));
  gap: var(--space-4);
}

.cp-plan {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  background: var(--color-surface);
  min-width: 0;
}

.cp-plan-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
}

.cp-plan-name {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.cp-plan-name h3 {
  margin: 0;
  font-size: var(--text-lg);
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* The plan's sentence is the product; it reads at body size, not as a caption. */
.cp-headline {
  margin: 0;
  font-size: var(--text-md);
  line-height: 1.5;
  color: var(--color-text);
}

.cp-figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(118px, 1fr));
  gap: var(--space-3);
  margin: 0;
}

.cp-figures > div {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.cp-figures dt {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

.cp-figures dd {
  margin: 0;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.cp-figures dd.panel-note {
  font-weight: 400;
}

.cp-chart {
  min-width: 0;
}

.cp-propose {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
}

.cp-propose > .panel-note {
  flex: 1 1 260px;
}

.cp-outcome {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.cp-link {
  font-weight: 600;
  color: var(--color-primary-text);
}

.cp-working > summary {
  cursor: pointer;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.cp-working[open] > summary {
  margin-bottom: var(--space-2);
}

.cp-unknown {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.cp-unknown > li {
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}

.cp-unknown-why {
  display: block;
  margin-top: 2px;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* Reserved share of the workers' CPU and memory, with the 85% rollout-room mark. */
.cp-reserved {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.cp-reserved-row {
  display: grid;
  grid-template-columns: 120px minmax(80px, 1fr) auto;
  align-items: center;
  gap: var(--space-3);
}

.cp-reserved-label {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

.cp-reserved-value {
  font-size: var(--text-sm);
  white-space: nowrap;
}

.cp-bar {
  position: relative;
  height: 8px;
  border-radius: 999px;
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  overflow: hidden;
}

.cp-bar-fill {
  position: absolute;
  inset: 0 auto 0 0;
  background: var(--color-primary-solid);
  border-radius: 999px;
}

.cp-bar-target {
  position: absolute;
  top: -2px;
  bottom: -2px;
  left: 85%;
  width: 2px;
  background: var(--color-text-muted);
}

.cp-provision {
  margin: 0;
}

@media (max-width: 560px) {
  .cp-reserved-row {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-1);
  }
  .cp-reserved-value {
    white-space: normal;
  }
}

/* Right-sizing as the alternative to new hardware — a quiet aside, emphasised when it would cover it. */
.cp-alt {
  margin: 0;
  padding: var(--space-2) var(--space-3);
  border-left: 3px solid var(--color-border);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.cp-alt-good {
  border-left-color: var(--severity-ok-fg);
  color: var(--color-text);
}

/* A workload that needs nothing: tighter, and its chart lives inside the working. */
.cp-plan-compact {
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
}

.cp-plan-compact .cp-headline {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* ---------------------------------------------------------------- virtual machines */

/*
 * The VM list: one card per machine, four meters, the worst finding, the databases as chips. A card's
 * left edge carries its health, so a fleet is read down one edge before anything else.
 */
.vm-grid { list-style: none; margin: var(--space-4) 0 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 22rem), 1fr)); gap: var(--space-4); }
.vm-card { min-width: 0; border: 1px solid var(--color-border); border-left: 3px solid var(--color-border-strong); border-radius: 12px; background: var(--color-surface); transition: border-color 0.15s ease, box-shadow 0.15s ease; }
.vm-card:hover { border-color: var(--color-border-strong); box-shadow: 0 2px 10px color-mix(in srgb, var(--color-text) 7%, transparent); }
.vm-card[data-health="healthy"] { border-left-color: var(--ok-text); }
.vm-card[data-health="warning"] { border-left-color: var(--warn-text); }
.vm-card[data-health="critical"] { border-left-color: var(--critical-text); }
.vm-card-link { display: grid; gap: var(--space-3); padding: var(--space-4); color: inherit; text-decoration: none; min-width: 0; }
.vm-card-link:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; border-radius: 12px; }
.vm-card-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-3); min-width: 0; }
.vm-card-title { display: grid; gap: 2px; min-width: 0; }
.vm-card-title h3 { margin: 0; font-size: var(--text-lg); font-weight: 600; color: var(--color-text); overflow-wrap: anywhere; }
.vm-card-addr { font-size: 12px; color: var(--color-text-subtle); overflow-wrap: break-word; word-break: normal; }
.vm-card-facts { margin: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 4px var(--space-2); font-size: var(--text-sm); color: var(--color-text-muted); }
.vm-chip { display: inline-flex; padding: 1px 8px; border-radius: 999px; background: var(--color-sunken); color: var(--color-text-muted); font-size: 11px; font-weight: 500; }
.vm-card-finding { margin: 0; font-size: var(--text-sm); line-height: 1.5; color: var(--color-text-muted); }
.vm-card-finding strong { font-weight: 600; color: var(--color-text); }
.vm-card-finding[data-severity="critical"] strong { color: var(--critical-text); }
.vm-card-finding[data-severity="warning"] strong { color: var(--warn-text); }
.vm-card-finding[data-severity="ok"] strong { color: var(--ok-text); font-weight: 500; }
.vm-card-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-2); padding-top: var(--space-3); border-top: 1px solid var(--color-border); font-size: 12px; color: var(--color-text-subtle); }
.vm-card-when { font-size: 12px; color: var(--color-text-subtle); white-space: nowrap; }
.vm-dbs { display: flex; flex-wrap: wrap; gap: 6px; min-width: 0; }
.vm-db-chip { display: inline-flex; align-items: center; gap: 5px; padding: 1px 8px; border: 1px solid var(--color-border); border-radius: 999px; font-size: 11px; color: var(--color-text); }
.vm-db-chip::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--color-text-subtle); }
.vm-db-chip[data-health="healthy"]::before { background: var(--ok-text); }
.vm-db-chip[data-health="warning"]::before { background: var(--warn-text); }
.vm-db-chip[data-health="critical"]::before { background: var(--critical-text); }

/* Four meters, label · bar · value, in two columns; faded when the figures are stale or unmeasured. */
.vm-meters { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-2) var(--space-4); }
.vm-meters[data-stale="true"] { opacity: 0.55; }
.vm-meter { display: grid; grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "label value" "bar bar"; gap: 3px var(--space-2); min-width: 0; }
.vm-meter-label { grid-area: label; font-size: 11px; color: var(--color-text-subtle); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vm-meter-value { grid-area: value; font-size: 12px; font-weight: 600; color: var(--color-text); }
.vm-meter-bar, .vm-bar { grid-area: bar; display: block; height: 6px; border-radius: 999px; background: var(--color-sunken); overflow: hidden; }
.vm-meter-bar > span, .vm-bar > span { display: block; height: 100%; border-radius: inherit; background: var(--ok-text); }
.vm-meter[data-tone="warning"] .vm-meter-bar > span, .vm-bar[data-tone="warning"] > span { background: var(--warn-text); }
.vm-meter[data-tone="critical"] .vm-meter-bar > span, .vm-bar[data-tone="critical"] > span { background: var(--critical-text); }
.vm-meter[data-tone="none"] .vm-meter-bar > span { background: transparent; }
.vm-bar-inline { display: inline-block; width: 80px; vertical-align: middle; }

/* The detail page: sections in reading order. */
.vm-head-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.vm-collect { display: inline-flex; align-items: center; gap: var(--space-2); }
.vm-section { display: grid; gap: var(--space-3); margin-top: var(--space-6); min-width: 0; }
.vm-section-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-2) var(--space-3); }
.vm-section-head h2, .vm-section-title { margin: 0; font-size: var(--text-lg); font-weight: 600; color: var(--color-text); }
.vm-note { margin: 0; font-size: var(--text-sm); line-height: 1.6; color: var(--color-text-muted); max-width: 90ch; }
.vm-note.is-error { color: var(--critical-text); }
.vm-sub { color: var(--color-text-subtle); }

.vm-findings { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.vm-findings li { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 2px var(--space-3); align-items: start; padding: var(--space-3); border: 1px solid var(--color-border); border-left: 3px solid var(--info-text); border-radius: 8px; background: var(--color-surface); }
.vm-findings li[data-severity="critical"] { border-left-color: var(--critical-text); background: color-mix(in srgb, var(--critical-soft) 45%, var(--color-surface)); }
.vm-findings li[data-severity="warning"] { border-left-color: var(--warn-text); }
.vm-findings strong { font-size: var(--text-md); font-weight: 600; color: var(--color-text); }
.vm-findings span { grid-column: 1; font-size: var(--text-sm); line-height: 1.55; color: var(--color-text-muted); overflow-wrap: anywhere; }
.vm-findings .vm-ask { grid-column: 2; grid-row: 1 / span 2; align-self: center; }
/* AskButton renders a plain button; this matches <Button size="sm" variant="secondary">. */
.vm-ask { display: inline-flex; align-items: center; justify-content: center; height: var(--size-control-sm); padding: 0 var(--space-2); border: 1px solid var(--border-strong); border-radius: var(--radius-md); background: var(--bg-surface); color: var(--text-primary); font-size: var(--text-sm); font-weight: 500; white-space: nowrap; cursor: pointer; }
.vm-ask:hover { background: var(--color-hover); }
.vm-ask:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; }
@media (max-width: 640px) {
  .vm-findings li { grid-template-columns: minmax(0, 1fr); }
  .vm-findings .vm-ask { grid-column: 1; grid-row: auto; justify-self: start; }
}

.vm-facts { margin: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 12rem), 1fr)); gap: var(--space-3) var(--space-5); }
.vm-facts > div { display: grid; gap: 2px; min-width: 0; }
.vm-facts dt { font-size: 10.5px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-text-subtle); }
.vm-facts dd { margin: 0; font-size: var(--text-sm); color: var(--color-text); overflow-wrap: anywhere; }

.vm-charts { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 26rem), 1fr)); gap: var(--space-5); min-width: 0; }
.vm-chart { min-width: 0; padding: var(--space-3); border: 1px solid var(--color-border); border-radius: 10px; background: var(--color-surface); }
/*
 * On the VM page the legend always sits under its plot, full width. Side by side, each legend asks for
 * its own content width, so six stacked charts got six different plot widths and ragged right edges;
 * underneath, every plot and every legend in the grid line up at any window size.
 */
.vm-chart .panel-legend { flex-basis: 100%; }
.vm-chart-loading { height: 170px; border-radius: 6px; background: var(--color-sunken); }
.vm-chart-empty { margin: 0; display: grid; place-items: center; height: 170px; border-radius: 6px; background: var(--color-sunken); font-size: var(--text-sm); color: var(--color-text-subtle); text-align: center; }

.vm-table-wrap { overflow-x: auto; overscroll-behavior-x: contain; min-width: 0; border: 1px solid var(--color-border); border-radius: 10px; }
.vm-table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.vm-table caption { padding: var(--space-2) var(--space-3); text-align: start; font-weight: 600; color: var(--color-text); }
.vm-table th { padding: var(--space-2) var(--space-3); text-align: start; font-size: 11px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--color-text-subtle); border-bottom: 1px solid var(--color-border); white-space: nowrap; }
.vm-table td { padding: var(--space-2) var(--space-3); border-bottom: 1px solid var(--color-border); color: var(--color-text); vertical-align: top; }
.vm-table tr:last-child td { border-bottom: 0; }
.vm-query { max-width: 40rem; white-space: pre-wrap; overflow-wrap: anywhere; font-size: 12px; }
.vm-procs { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr)); gap: var(--space-4); }

/* Databases. */
.vm-dbs-list { display: grid; gap: var(--space-4); }
.vm-db { display: grid; gap: var(--space-3); padding: var(--space-4); border: 1px solid var(--color-border); border-left: 3px solid var(--color-border-strong); border-radius: 12px; background: var(--color-surface); min-width: 0; }
.vm-db[data-health="healthy"] { border-left-color: var(--ok-text); }
.vm-db[data-health="warning"] { border-left-color: var(--warn-text); }
.vm-db[data-health="critical"] { border-left-color: var(--critical-text); }
.vm-db-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-3); min-width: 0; }
.vm-db-head h3 { margin: 0; font-size: var(--text-lg); font-weight: 600; color: var(--color-text); }
.vm-db-figures { margin: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 9rem), 1fr)); gap: var(--space-3) var(--space-4); }
.vm-db-figures > div { display: grid; gap: 3px; align-content: start; min-width: 0; }
.vm-db-figures dt { font-size: 10.5px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-text-subtle); }
.vm-db-figures dd { margin: 0; font-size: var(--text-xl); font-weight: 600; color: var(--color-text); }
.vm-db-figures dd small { font-size: var(--text-sm); font-weight: 400; color: var(--color-text-subtle); }
.vm-db-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.vm-db-actions .vm-card-when { margin-left: auto; }
.vm-activity { display: grid; gap: var(--space-2); min-width: 0; }
.vm-empty { display: grid; justify-items: start; gap: var(--space-3); padding: var(--space-4); border: 1px dashed var(--color-border-strong); border-radius: 12px; background: var(--color-sunken); }
.vm-empty p { margin: 0; font-size: var(--text-sm); line-height: 1.6; color: var(--color-text-muted); max-width: 80ch; }

/* Forms and settings. */
.vm-form-row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 9rem); gap: var(--space-3); }
@media (max-width: 520px) { .vm-form-row { grid-template-columns: minmax(0, 1fr); } }
.vm-command { padding: var(--space-3); border: 1px solid var(--color-border); border-radius: 8px; background: var(--color-sunken); }
.vm-command > summary { cursor: pointer; font-size: var(--text-sm); font-weight: 600; color: var(--color-text); }
.vm-command[open] > summary { margin-bottom: var(--space-2); }
.vm-command > .vm-note { margin-bottom: var(--space-2); }
.vm-pre { margin: 0; padding: var(--space-3); max-height: 16rem; overflow: auto; overscroll-behavior-x: contain; border-radius: 6px; background: var(--color-surface); font-family: var(--font-mono); font-size: 11.5px; line-height: 1.55; color: var(--color-text); white-space: pre-wrap; overflow-wrap: anywhere; }
.vm-settings { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 22rem), 1fr)); gap: var(--space-4); }
.vm-setting { display: grid; gap: var(--space-2); align-content: start; padding: var(--space-4); border: 1px solid var(--color-border); border-radius: 12px; background: var(--color-surface); min-width: 0; }
.vm-setting h3 { margin: 0; font-size: var(--text-md); font-weight: 600; color: var(--color-text); }
.vm-window { display: inline-flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--space-2); }
/* The live line: a dot that pulses while readings arrive, and the words for each other state. */
.vm-livebar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-4); margin-bottom: var(--space-3); }
.vm-live { display: inline-flex; align-items: center; gap: 7px; font-size: var(--text-sm); font-weight: 500; color: var(--color-text); }
.vm-live-dot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--color-text-subtle); }
.vm-live[data-state="live"] .vm-live-dot { background: var(--ok-text); animation: vm-live-pulse 2s ease-out infinite; }
.vm-live[data-state="stale"] { color: var(--warn-text); }
.vm-live[data-state="stale"] .vm-live-dot { background: var(--warn-text); }
.vm-live[data-state="down"] { color: var(--critical-text); }
.vm-live[data-state="down"] .vm-live-dot { background: var(--critical-text); }
.vm-live[data-state="paused"], .vm-live[data-state="pending"] { color: var(--color-text-muted); }
@keyframes vm-live-pulse {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--ok-text) 55%, transparent); }
  70%, 100% { box-shadow: 0 0 0 6px color-mix(in srgb, var(--ok-text) 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) { .vm-live[data-state="live"] .vm-live-dot { animation: none; } }
.vm-card-foot .vm-live { font-size: 12px; font-weight: 400; }
.vm-db-actions .vm-live { margin-left: auto; font-size: 12px; font-weight: 400; }

/* The list's sparklines and each chart's current values. */
.vm-sparks { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-5); }
.vm-spark { display: grid; gap: 2px; }
.vm-spark-label { font-size: 11px; color: var(--color-text-subtle); }
.vm-chart-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 2px var(--space-3); margin-bottom: var(--space-2); }
.vm-chart-head .metric-panel-title { margin: 0; }
.vm-chart-now { display: inline-flex; flex-wrap: wrap; gap: 2px var(--space-3); font-size: 12px; color: var(--color-text-muted); }
.vm-chart-now > span:first-child { color: var(--color-text); font-weight: 600; }
.vm-inline { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }

/* The first section under a tab strip sits closer: the strip already separates it from the hero. */
.vm-section-first { margin-top: var(--space-2); }
.vm-form-row-wide { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
@media (max-width: 520px) { .vm-form-row-wide { grid-template-columns: minmax(0, 1fr); } }

/*
 * Node health: ten tiles, each saying its state in a word as well as a colour, so "not measured" can
 * never pass for "fine". A tile that is a button shows only its findings below it.
 */
.vm-conditions { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 13.5rem), 1fr)); gap: var(--space-2); }
.vm-condition { display: grid; gap: 4px; align-content: start; min-width: 0; padding: var(--space-3); border: 1px solid var(--color-border); border-top: 3px solid var(--color-border-strong); border-radius: 10px; background: var(--color-surface); color: inherit; font: inherit; text-align: start; }
button.vm-condition { cursor: pointer; transition: border-color 0.15s ease, background 0.15s ease; }
button.vm-condition:hover { background: var(--color-hover); }
button.vm-condition:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; }
.vm-condition[data-selected="true"] { border-color: var(--color-text-muted); box-shadow: inset 0 0 0 1px var(--color-text-muted); }
.vm-condition[data-status="ok"] { border-top-color: var(--ok-text); }
.vm-condition[data-status="warning"] { border-top-color: var(--warn-text); background: color-mix(in srgb, var(--warn-soft) 35%, var(--color-surface)); }
.vm-condition[data-status="critical"] { border-top-color: var(--critical-text); background: color-mix(in srgb, var(--critical-soft) 45%, var(--color-surface)); }
.vm-condition[data-status="unknown"] { border-top-style: dashed; }
.vm-condition-head { display: flex; align-items: center; gap: 6px; min-width: 0; }
.vm-condition-dot, .vm-dot { flex: none; display: inline-block; width: 9px; height: 9px; border-radius: 50%; background: var(--color-text-subtle); }
.vm-condition[data-status="ok"] .vm-condition-dot, .vm-dot[data-status="ok"] { background: var(--ok-text); }
.vm-condition[data-status="warning"] .vm-condition-dot, .vm-dot[data-status="warning"] { background: var(--warn-text); }
.vm-condition[data-status="critical"] .vm-condition-dot, .vm-dot[data-status="critical"] { background: var(--critical-text); }
.vm-dot[data-status="unknown"] { background: transparent; box-shadow: inset 0 0 0 1.5px var(--color-text-subtle); }
.vm-condition-label { font-size: var(--text-sm); font-weight: 600; color: var(--color-text); }
.vm-condition-state { margin-left: auto; font-size: 11px; font-weight: 600; color: var(--color-text-subtle); white-space: nowrap; }
.vm-condition[data-status="warning"] .vm-condition-state { color: var(--warn-text); }
.vm-condition[data-status="critical"] .vm-condition-state { color: var(--critical-text); }
.vm-condition-summary { font-size: 12px; line-height: 1.45; color: var(--color-text-muted); overflow-wrap: anywhere; }
.vm-condition-count { font-size: 11px; font-weight: 600; color: var(--color-text); }
.vm-dots { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 4px; }
.vm-card-health { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.vm-card-health-label { font-size: 11px; color: var(--color-text-subtle); }
.vm-chip-env { background: color-mix(in srgb, var(--info-soft) 70%, var(--color-sunken)); color: var(--color-text); }

/* Posture: failing checks open, unknown next, passing folded. */
.vm-posture { display: grid; gap: var(--space-3); }
.vm-posture-score { margin: 0; display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4); font-size: var(--text-sm); font-weight: 600; }
.vm-posture-score [data-status="pass"] { color: var(--ok-text); }
.vm-posture-score [data-status="fail"] { color: var(--critical-text); }
.vm-posture-score [data-status="unknown"] { color: var(--color-text-subtle); }
.vm-posture-score [data-status="advice"] { color: var(--info-text); }
.vm-posture-item[data-severity="info"] .vm-posture-icon { background: var(--info-soft); color: var(--info-text); }
.vm-posture-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.vm-posture-item { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 2px var(--space-3); align-items: start; padding: var(--space-3); border: 1px solid var(--color-border); border-radius: 8px; background: var(--color-surface); }
.vm-posture-item[data-status="fail"] { border-left: 3px solid var(--warn-text); }
.vm-posture-item[data-severity="critical"] { border-left-color: var(--critical-text); background: color-mix(in srgb, var(--critical-soft) 35%, var(--color-surface)); }
.vm-posture-item[data-severity="info"] { border-left-color: var(--info-text); }
.vm-posture-item[data-status="unknown"] { border-style: dashed; }
.vm-posture-icon { display: grid; place-items: center; width: 22px; height: 22px; border-radius: 50%; background: var(--color-sunken); font-size: 12px; font-weight: 700; color: var(--color-text-muted); }
.vm-posture-item[data-status="pass"] .vm-posture-icon { background: var(--ok-soft); color: var(--ok-text); }
.vm-posture-item[data-status="fail"] .vm-posture-icon { background: var(--warn-soft); color: var(--warn-text); }
.vm-posture-item[data-severity="critical"] .vm-posture-icon { background: var(--critical-soft); color: var(--critical-text); }
.vm-posture-body { display: grid; gap: 2px; min-width: 0; }
.vm-posture-body strong { font-size: var(--text-sm); font-weight: 600; color: var(--color-text); }
.vm-posture-body span { font-size: var(--text-sm); line-height: 1.5; color: var(--color-text-muted); overflow-wrap: anywhere; }
.vm-posture-state { font-size: 11px; font-weight: 600; color: var(--color-text-subtle); white-space: nowrap; }
.vm-posture-passed > summary { cursor: pointer; font-size: var(--text-sm); font-weight: 600; color: var(--ok-text); }
.vm-posture-passed[open] > summary { margin-bottom: var(--space-2); }
.vm-posture-bar { display: inline-flex; width: 120px; height: 8px; border-radius: 999px; overflow: hidden; background: var(--color-sunken); vertical-align: middle; }
.vm-posture-bar > span { display: block; height: 100%; }
.vm-posture-bar > [data-status="pass"] { background: var(--ok-text); }
.vm-posture-bar > [data-status="fail"] { background: var(--critical-text); }
.vm-posture-bar > [data-status="advice"] { background: var(--info-text); }
.vm-posture-bar > [data-status="unknown"] { background: var(--color-text-subtle); opacity: 0.45; }
@media (max-width: 640px) {
  .vm-posture-item { grid-template-columns: auto minmax(0, 1fr); }
  .vm-posture-state { grid-column: 2; }
}

/* An environment's machines by health: one bar, its counts beside it. */
.vm-healthbar { display: grid; gap: 4px; min-width: min(100%, 14rem); }
.vm-healthbar-track { display: flex; height: 8px; border-radius: 999px; overflow: hidden; background: var(--color-sunken); }
.vm-healthbar-track > span { display: block; height: 100%; }
.vm-healthbar-track > [data-health="healthy"] { background: var(--ok-text); }
.vm-healthbar-track > [data-health="warning"] { background: var(--warn-text); }
.vm-healthbar-track > [data-health="critical"] { background: var(--critical-text); }
.vm-healthbar-track > [data-health="unknown"] { background: var(--color-text-subtle); opacity: 0.45; }
.vm-healthbar-legend { display: flex; flex-wrap: wrap; gap: 2px var(--space-3); font-size: 12px; color: var(--color-text-muted); }
.vm-healthbar-legend [data-health="critical"] { color: var(--critical-text); font-weight: 600; }
.vm-healthbar-legend [data-health="warning"] { color: var(--warn-text); font-weight: 600; }

/* Environment cards: the cluster card's shape for a group of VMs, its worst state on the top edge. */
.vm-env-grid { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 24rem), 1fr)); gap: var(--space-4); }
.vm-env-card { min-width: 0; border: 1px solid var(--color-border); border-top: 3px solid var(--color-border-strong); border-radius: 12px; background: var(--color-surface); transition: border-color 0.15s ease, box-shadow 0.15s ease; }
.vm-env-card:hover { border-color: var(--color-border-strong); box-shadow: 0 2px 10px color-mix(in srgb, var(--color-text) 7%, transparent); }
.vm-env-card[data-health="healthy"] { border-top-color: var(--ok-text); }
.vm-env-card[data-health="warning"] { border-top-color: var(--warn-text); }
.vm-env-card[data-health="critical"] { border-top-color: var(--critical-text); }
.vm-env-card.is-unassigned { border-style: dashed; border-top-style: solid; }
.vm-env-link { display: grid; gap: var(--space-3); width: 100%; padding: var(--space-4); border: 0; background: none; color: inherit; font: inherit; text-align: start; text-decoration: none; cursor: pointer; min-width: 0; }
.vm-env-link:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; border-radius: 12px; }
.vm-env-figures { margin: 0; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--space-2); }
.vm-env-figures > div { display: grid; gap: 2px; min-width: 0; }
.vm-env-figures dt { font-size: 10.5px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--color-text-subtle); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.vm-env-figures dd { margin: 0; font-size: var(--text-lg); font-weight: 600; color: var(--color-text); }
@media (max-width: 420px) { .vm-env-figures { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.vm-env-trouble { margin: 0; display: flex; flex-wrap: wrap; gap: 6px; }
.vm-env-chip { display: inline-flex; align-items: center; gap: 5px; padding: 2px 9px; border-radius: 999px; font-size: 12px; font-weight: 600; background: var(--color-sunken); color: var(--color-text); }
.vm-env-chip[data-status="critical"] { background: var(--critical-soft); color: var(--critical-text); }
.vm-env-chip[data-status="warning"] { background: var(--warn-soft); color: var(--warn-text); }
.vm-env-chip[data-status="security"] { background: color-mix(in srgb, var(--info-soft) 80%, var(--color-sunken)); color: var(--info-text); }
.vm-tier { flex: none; display: inline-flex; padding: 2px 9px; border-radius: 999px; font-size: 11px; font-weight: 600; letter-spacing: 0.02em; background: var(--color-sunken); color: var(--color-text-muted); white-space: nowrap; }
.vm-tier[data-tier="production"] { background: color-mix(in srgb, var(--critical-soft) 70%, var(--color-sunken)); color: var(--critical-text); }
.vm-tier[data-tier="disaster_recovery"] { background: color-mix(in srgb, var(--warn-soft) 70%, var(--color-sunken)); color: var(--warn-text); }
.vm-tier[data-tier="staging"], .vm-tier[data-tier="testing"] { background: color-mix(in srgb, var(--info-soft) 70%, var(--color-sunken)); color: var(--info-text); }

/* The environment's node-health matrix: a row per machine, a dot per condition. */
.vm-matrix th.vm-matrix-col { writing-mode: vertical-rl; transform: rotate(180deg); padding: var(--space-2) 4px; text-align: start; letter-spacing: 0.02em; text-transform: none; font-size: 11px; }
.vm-matrix td.vm-matrix-cell { text-align: center; vertical-align: middle; padding: var(--space-2) 4px; }
.vm-matrix tr[data-health="critical"] td:first-child { box-shadow: inset 3px 0 0 var(--critical-text); }
.vm-matrix tr[data-health="warning"] td:first-child { box-shadow: inset 3px 0 0 var(--warn-text); }
.vm-matrix-name { display: block; font-weight: 600; white-space: nowrap; }
.vm-matrix-addr { display: block; font-size: 11px; }
.vm-matrix .vm-live { font-size: 12px; font-weight: 400; white-space: nowrap; }
.vm-matrix-key { margin: 0; display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); padding: var(--space-2) var(--space-3); border-top: 1px solid var(--color-border); font-size: 12px; color: var(--color-text-subtle); }

/* The audit trail: a day heading, then a line per entry — time, a mark coloured by severity, words. */
.vm-timeline { display: grid; gap: var(--space-3); min-width: 0; }
.vm-timeline-filters { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4); }
.vm-timeline-empty { margin: 0; padding: var(--space-4); border: 1px dashed var(--color-border-strong); border-radius: 10px; font-size: var(--text-sm); color: var(--color-text-muted); }
.vm-timeline-days, .vm-timeline-list { list-style: none; margin: 0; padding: 0; display: grid; }
.vm-timeline-days { gap: var(--space-4); }
.vm-timeline-day { margin: 0 0 var(--space-2); font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-text-subtle); }
.vm-timeline-list { border: 1px solid var(--color-border); border-radius: 10px; background: var(--color-surface); }
.vm-event { display: grid; grid-template-columns: 4.6rem 12px minmax(0, 1fr); gap: var(--space-3); padding: var(--space-3); border-bottom: 1px solid var(--color-border); min-width: 0; }
.vm-event:last-child { border-bottom: 0; }
.vm-event-time { font-size: 12px; color: var(--color-text-subtle); padding-top: 2px; white-space: nowrap; }
.vm-event-mark { width: 10px; height: 10px; margin-top: 5px; border-radius: 50%; background: var(--info-text); }
.vm-event[data-source="platform"] .vm-event-mark { background: transparent; box-shadow: inset 0 0 0 2px var(--color-text-muted); }
.vm-event[data-severity="warning"] .vm-event-mark { background: var(--warn-text); box-shadow: none; }
.vm-event[data-severity="critical"] .vm-event-mark { background: var(--critical-text); box-shadow: none; }
.vm-event[data-severity="critical"] { background: color-mix(in srgb, var(--critical-soft) 30%, var(--color-surface)); }
.vm-event-body { display: grid; gap: 2px; min-width: 0; }
.vm-event-title { margin: 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px var(--space-2); font-size: var(--text-sm); }
.vm-event-title strong { font-weight: 600; color: var(--color-text); overflow-wrap: anywhere; }
.vm-event-vm { font-size: 12px; font-family: var(--font-mono); }
.vm-event-detail { margin: 0; font-size: var(--text-sm); line-height: 1.5; color: var(--color-text-muted); overflow-wrap: anywhere; }
.vm-event-who { margin: 0; font-size: 11.5px; color: var(--color-text-subtle); }
@media (max-width: 520px) {
  .vm-event { grid-template-columns: 12px minmax(0, 1fr); }
  .vm-event-time { grid-column: 2; order: 2; padding-top: 0; }
}

/* Small pieces of the Security and System tabs. */
.vm-split { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr)); gap: var(--space-4); }
.vm-subhead { margin: 0 0 var(--space-2); font-size: var(--text-sm); font-weight: 600; color: var(--color-text); }
.vm-plain-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; font-size: var(--text-sm); color: var(--color-text); }
.vm-plain-list.vm-columns { grid-template-columns: repeat(auto-fill, minmax(min(100%, 16rem), 1fr)); gap: 2px var(--space-4); font-size: 12px; }
.vm-rebind { display: grid; gap: var(--space-2); padding: var(--space-3); border: 1px solid var(--warn-text); border-radius: 8px; background: color-mix(in srgb, var(--warn-soft) 40%, var(--color-surface)); }

/*
 * A machine's card holds its actions beneath the link, so a press on Move or Delete is never also a press
 * on the card. The card is a column so the actions sit on its bottom edge in a row of uneven cards.
 */
.vm-card { display: flex; flex-direction: column; }
.vm-card > .vm-card-link { flex: 1 1 auto; align-content: start; }
.vm-card-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--space-2); padding: var(--space-2) var(--space-4) var(--space-3); border-top: 1px solid var(--color-border); }

/* An environment's card names its machines, a dot each, worst first. */
.vm-env-machines { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 6px; }
.vm-env-machines > li { display: inline-flex; align-items: center; gap: 6px; min-width: 0; max-width: 100%; padding: 2px 9px; border: 1px solid var(--color-border); border-radius: 999px; background: var(--color-surface); font-size: 12px; color: var(--color-text); }
.vm-env-machine-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vm-env-machines > li.vm-env-machines-more { border-style: dashed; color: var(--color-text-subtle); }
.vm-env-open { font-size: 12.5px; font-weight: 600; color: var(--accent-text); }
.vm-env-link:hover .vm-env-open { text-decoration: none; }
.vm-confirm { display: grid; gap: var(--space-3); }

/* Moving machines: one environment for one machine, or several machines into one environment. */
.vm-move-field { display: grid; gap: 6px; font-size: var(--text-sm); font-weight: 500; color: var(--color-text); }
.vm-pick-list { list-style: none; margin: 0; padding: 0; max-height: 50vh; overflow-y: auto; overflow-x: hidden; border: 1px solid var(--color-border); border-radius: 10px; }
.vm-pick-list > li + li { border-top: 1px solid var(--color-border); }
.vm-pick { display: flex; flex-wrap: wrap; align-items: center; gap: 2px var(--space-3); padding: var(--space-2) var(--space-3); cursor: pointer; min-width: 0; }
.vm-pick:hover { background: var(--color-hover); }
.vm-pick-name { font-weight: 600; color: var(--color-text); overflow-wrap: anywhere; }
.vm-pick-where { margin-left: auto; font-size: 12px; color: var(--color-text-subtle); }

/*
 * The SRE agent on virtual machines: a machine's alerts with the agent's investigation of each, the
 * runbook split into look (read-only) and fix (changes the machine), the forecast table, and the
 * report kept per environment.
 */
.vm-alert-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-3); }
.vm-alert-card { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-3); min-width: 0; padding: var(--space-3) var(--space-4); border: 1px solid var(--color-border); border-left: 3px solid var(--color-border-strong); border-radius: 10px; background: var(--color-surface); }
.vm-alert-card[data-severity="critical"] { border-left-color: var(--critical-text); }
.vm-alert-card[data-severity="warning"] { border-left-color: var(--warn-text); }
.vm-alert-card.is-closed { border-left-color: var(--color-border-strong); background: var(--color-sunken); }
.vm-alert-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); min-width: 0; }
.vm-alert-title { flex: 1 1 18rem; min-width: 0; font-weight: 600; color: var(--color-text); overflow-wrap: anywhere; }
.vm-alert-reading { margin: 0; padding: var(--space-2) var(--space-3); border-radius: 8px; background: color-mix(in srgb, var(--info-soft) 45%, var(--color-surface)); font-size: var(--text-sm); line-height: 1.55; color: var(--color-text); overflow-wrap: anywhere; }
.vm-alert-reading-label { display: block; font-size: 11px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--info-text); }
.vm-alert-run > summary, .vm-alert-closed > summary, .vm-report-facts > summary, .vm-report-history > summary { cursor: pointer; font-size: var(--text-sm); font-weight: 600; color: var(--color-text-muted); }
.vm-alert-run[open] > summary, .vm-alert-closed[open] > summary { margin-bottom: var(--space-2); }
.vm-alert-closed { display: grid; gap: var(--space-2); }
.vm-runbook { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-2); min-width: 0; }
/* Fixed shrinkable columns, not auto-fit: a long command must scroll inside its block, never widen the page. */
.vm-runbook-cols { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-3); min-width: 0; }
@media (min-width: 900px) { .vm-runbook-cols { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.vm-runbook-cols > div { display: grid; grid-template-columns: minmax(0, 1fr); gap: 6px; min-width: 0; }
.vm-runbook-cols .code { min-width: 0; max-width: 100%; }
.vm-runbook-head { margin: 0; font-size: 11px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ok-text); }
.vm-runbook-head.is-fix { color: var(--warn-text); }
.vm-verdict { font-weight: 600; color: var(--color-text-muted); }
.vm-verdict[data-verdict="reaches"] { color: var(--warn-text); }
.vm-verdict[data-verdict="steady"] { color: var(--ok-text); font-weight: 500; }
.vm-card-agent { margin: 0; display: flex; flex-wrap: wrap; gap: 6px; }
.vm-card-alerts, .vm-card-forecast { display: inline-flex; align-items: center; padding: 2px 9px; border-radius: 999px; font-size: 12px; font-weight: 600; }
.vm-card-alerts { background: var(--warn-soft); color: var(--warn-text); }
.vm-card-alerts[data-severity="critical"] { background: var(--critical-soft); color: var(--critical-text); }
.vm-card-forecast { background: color-mix(in srgb, var(--info-soft) 80%, var(--color-sunken)); color: var(--info-text); }
.vm-agent-reports { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.vm-agent-report > a { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px var(--space-3); padding: var(--space-3) var(--space-4); border: 1px solid var(--color-border); border-radius: 10px; background: var(--color-surface); color: inherit; text-decoration: none; min-width: 0; }
.vm-agent-report > a:hover { border-color: var(--color-border-strong); }
.vm-agent-report > a:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; }
.vm-agent-report-headline { flex: 1 1 20rem; min-width: 0; color: var(--color-text-muted); overflow-wrap: anywhere; }
.vm-report { display: grid; gap: var(--space-4); min-width: 0; padding: var(--space-4); border: 1px solid var(--color-border); border-top: 3px solid var(--color-border-strong); border-radius: 12px; background: var(--color-surface); }
.vm-report[data-verdict="healthy"] { border-top-color: var(--ok-text); }
.vm-report[data-verdict="attention"] { border-top-color: var(--warn-text); }
.vm-report[data-verdict="critical"] { border-top-color: var(--critical-text); }
.vm-report > section { display: grid; gap: var(--space-2); min-width: 0; }
.vm-report-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2) var(--space-3); }
.vm-report-headline { flex: 1 1 20rem; margin: 0; font-size: var(--text-lg); font-weight: 600; color: var(--color-text); overflow-wrap: anywhere; }
.vm-report-summary { display: grid; gap: 4px; padding: var(--space-3); border-radius: 10px; background: var(--color-sunken); }
.vm-report-summary p { margin: 0; line-height: 1.6; color: var(--color-text); }
.vm-rec-group { display: grid; gap: var(--space-2); }
.vm-rec-priority { margin: 0; font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-text-subtle); }
.vm-rec-priority[data-priority="now"] { color: var(--critical-text); }
.vm-rec-priority[data-priority="soon"] { color: var(--warn-text); }
.vm-rec-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.vm-rec { min-width: 0; padding: var(--space-3); border: 1px solid var(--color-border); border-radius: 10px; }
.vm-rec > details { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-3); min-width: 0; }
.vm-rec > details > summary { cursor: pointer; line-height: 1.5; overflow-wrap: anywhere; }
.vm-rec > details[open] > summary { margin-bottom: var(--space-3); }
.vm-report-facts, .vm-report-history { display: grid; gap: var(--space-2); }
.vm-check-failed { font-weight: 600; color: var(--color-text); }

/*
 * AI governance · Agents: a card per agent — what it does, whether it is running for this account and
 * why not, how often, what it is working on — pressed to filter the run list below to that agent.
 */
.agent-budget { margin: 0 0 var(--space-3); font-size: var(--text-sm); color: var(--color-text-muted); }
.agent-cards { list-style: none; margin: 0 0 var(--space-5); padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 17rem), 1fr)); gap: var(--space-3); }
.agent-card { min-width: 0; display: flex; border: 1px solid var(--color-border); border-top: 3px solid var(--color-border-strong); border-radius: 12px; background: var(--color-surface); }
.agent-card[data-state="watching"] { border-top-color: var(--ok-text); }
.agent-card[data-state="off"] { border-top-color: var(--warn-text); }
.agent-card[data-chosen="true"] { border-color: var(--color-text-muted); box-shadow: inset 0 0 0 1px var(--color-text-muted); }
.agent-card-button { flex: 1 1 auto; display: grid; align-content: start; gap: var(--space-2); min-width: 0; padding: var(--space-3) var(--space-4); border: 0; border-radius: 12px; background: none; color: inherit; font: inherit; text-align: start; cursor: pointer; }
.agent-card-button:hover { background: var(--color-hover); }
.agent-card-button:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; }
.agent-card-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); min-width: 0; }
.agent-card-name { font-size: var(--text-base); font-weight: 600; color: var(--color-text); overflow-wrap: anywhere; }
.agent-card-what { font-size: 12.5px; line-height: 1.5; color: var(--color-text-muted); }
.agent-card-note { font-size: 12.5px; font-weight: 600; color: var(--color-text); }
.agent-card-note[data-state="watching"] { color: var(--ok-text); }
.agent-card-note[data-state="off"] { color: var(--warn-text); }
.agent-card-figures { margin: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px var(--space-3); }
.agent-card-figures > div { display: grid; gap: 1px; min-width: 0; }
.agent-card-figures dt { font-size: 10.5px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--color-text-subtle); }
.agent-card-figures dd { margin: 0; font-size: var(--text-sm); font-weight: 600; color: var(--color-text); }
.agent-card-figures dd[data-bad="true"] { color: var(--critical-text); }
.agent-card-foot { font-size: 12px; color: var(--color-text-subtle); }
.agent-card-why { font-size: 12px; line-height: 1.45; color: var(--warn-text); }
.agent-runs-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--space-2); margin-bottom: var(--space-3); }
.agent-runs-title { margin: 0; font-size: var(--text-base); font-weight: 600; color: var(--color-text); }
.agent-runs-all { padding: 0; border: 0; background: none; color: var(--accent-text); font: inherit; font-size: var(--text-sm); cursor: pointer; text-decoration: underline; }
.agent-runs-all:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; border-radius: 4px; }
.vm-link-button { padding: 0; border: 0; background: none; color: var(--accent-text); font: inherit; cursor: pointer; text-decoration: underline; }
.vm-link-button:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; border-radius: 4px; }

/*
 * `/console/locate` when more than one object answers to a name — a `workload`, or the same pod
 * name on two clusters. One exact link per object, with the cluster beside it, rather than a guess.
 */
.locate-choices { display: grid; gap: var(--space-2); max-width: 720px; }
.locate-choices ul { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.locate-choices li {
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  overflow-wrap: anywhere;
}

/* =====================================================================================
 * Databases on virtual machines — /vms/databases, /vms/databases/[dbId], and the
 * application page's Databases panel.
 *
 * `vdb-*` is the database pages and the pieces they share with the application panel (the route
 * chain, its checks, the evidence lines); `apx-db-*` is the panel's own frame. The route is a row of
 * pills that wraps rather than scrolls, so a five-hop chain stays inside a phone's width; every pill
 * breaks inside a long hostname rather than pushing the page sideways.
 * ===================================================================================== */

/* ---- the list: one card per database */
.vdb-grid { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 22rem), 1fr)); gap: var(--space-4); }
.vdb-card { display: grid; gap: var(--space-3); align-content: start; min-width: 0; padding: var(--space-4); border: 1px solid var(--color-border); border-left: 3px solid var(--color-border-strong); border-radius: 12px; background: var(--color-surface); }
.vdb-card[data-health="healthy"] { border-left-color: var(--ok-text); }
.vdb-card[data-health="degraded"] { border-left-color: var(--warn-text); }
.vdb-card[data-health="critical"] { border-left-color: var(--critical-text); }
.vdb-card h3 a { color: inherit; text-decoration: none; }
.vdb-card h3 a:hover { text-decoration: none; }
.vdb-card-on { margin: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 4px var(--space-2); font-size: var(--text-sm); color: var(--color-text-muted); }
.vdb-card-figs { margin: 0; display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-5); }
.vdb-card-figs > div { display: grid; gap: 2px; min-width: 0; }
.vdb-card-figs dt { font-size: 10.5px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-text-subtle); }
.vdb-card-figs dd { margin: 0; font-size: var(--text-md); font-weight: 600; color: var(--color-text); }
.vdb-card-figs dd small { font-size: var(--text-sm); font-weight: 400; color: var(--color-text-subtle); }

/* ---- "Used by": the applications, each saying whether it was declared or found */
.vdb-used-by { display: flex; flex-wrap: wrap; align-items: center; gap: 6px var(--space-2); margin: 0; padding-top: var(--space-3); border-top: 1px solid var(--color-border); }
.vdb-used-by-label { font-size: 10.5px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-text-subtle); margin-right: 2px; }
.vdb-chips { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 6px; min-width: 0; }
.vdb-chip { display: inline-flex; align-items: baseline; gap: 6px; max-width: 100%; padding: 2px 9px; border: 1px solid var(--color-border); border-radius: 999px; background: var(--color-surface); font-size: 12px; color: var(--color-text); text-decoration: none; overflow-wrap: anywhere; }
.vdb-chip:hover { border-color: var(--color-border-strong); background: var(--color-hover); }
.vdb-chip:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 1px; }
.vdb-chip-basis { font-size: 11px; color: var(--color-text-subtle); }

/* ---- the route: hops as pills, an arrow between each */
.vdb-route { display: grid; gap: var(--space-2); min-width: 0; }
.vdb-chain { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) 0; min-width: 0; }
.vdb-hop { display: inline-flex; align-items: center; min-width: 0; max-width: 100%; }
.vdb-hop-pill { display: inline-grid; gap: 1px; min-width: 0; max-width: 100%; padding: 5px 10px; border: 1px solid var(--color-border); border-radius: 8px; background: var(--color-sunken); color: var(--color-text); text-decoration: none; }
a.vdb-hop-pill:hover { border-color: var(--color-border-strong); background: var(--color-hover); }
a.vdb-hop-pill:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 1px; }
.vdb-hop[data-kind="database"] .vdb-hop-pill { border-color: color-mix(in srgb, var(--info) 45%, var(--color-border)); background: color-mix(in srgb, var(--info-soft) 35%, var(--color-surface)); }
.vdb-hop[data-kind="vm"] .vdb-hop-pill { background: var(--color-surface); }
.vdb-hop-kind { font-size: 10px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-text-subtle); }
.vdb-hop-label { font-family: var(--font-mono); font-size: 12.5px; overflow-wrap: anywhere; }
.vdb-hop-arrow { flex: none; padding: 0 var(--space-2); color: var(--color-text-subtle); }
.vdb-why { font-size: var(--text-sm); color: var(--color-text-muted); }
.vdb-why > summary { cursor: pointer; width: -moz-max-content; width: max-content; max-width: 100%; color: var(--color-text-subtle); }
.vdb-why > summary:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; border-radius: 4px; }
.vdb-why-list { margin: var(--space-2) 0 0; padding-left: 1.25rem; display: grid; gap: 4px; line-height: 1.5; overflow-wrap: anywhere; }
.vdb-why-list strong { font-weight: 600; color: var(--color-text); }

/* ---- the route's checks: a mark, the server's sentence, the state in a word */
.vdb-checks { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.vdb-check { --state: var(--color-text-subtle); display: grid; grid-template-columns: 1.25rem minmax(0, 1fr) auto; gap: var(--space-2) var(--space-3); align-items: start; padding: 6px var(--space-2); border-radius: 8px; }
.vdb-check[data-status="pass"] { --state: var(--ok); }
.vdb-check[data-status="warn"] { --state: var(--warn); }
.vdb-check[data-status="fail"] { --state: var(--critical); background: color-mix(in srgb, var(--critical) 7%, transparent); }
.vdb-check-mark { inline-size: 1.25rem; block-size: 1.25rem; display: grid; place-items: center; border-radius: 50%; font-size: 0.7rem; font-weight: 700; color: var(--state); background: color-mix(in srgb, var(--state) 14%, transparent); }
.vdb-check-body { display: grid; gap: 1px; min-width: 0; }
.vdb-check-name { font-size: var(--text-sm); font-weight: 500; color: var(--color-text); }
.vdb-check-detail { font-size: var(--text-sm); line-height: 1.5; color: var(--color-text-muted); overflow-wrap: anywhere; }
.vdb-check-state { font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.05em; color: var(--state); padding-top: 2px; white-space: nowrap; }

/* ---- the evidence: declared by a person, or found in a cluster */
.vdb-basis { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.vdb-basis-row { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: var(--space-2) var(--space-3); align-items: start; }
.vdb-basis-text { font-size: var(--text-sm); line-height: 1.55; color: var(--color-text-muted); overflow-wrap: anywhere; }
.vdb-basis-act { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
@media (max-width: 40rem) {
  .vdb-basis-row { grid-template-columns: auto minmax(0, 1fr); }
  .vdb-basis-act { grid-column: 2; }
}

/* ---- one database's page: the two halves of its connection, held apart */
.vdb-conn { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 24rem), 1fr)); gap: var(--space-4); min-width: 0; }
.vdb-half { display: grid; gap: var(--space-3); align-content: start; min-width: 0; padding: var(--space-4); border: 1px solid var(--color-border); border-radius: 12px; background: var(--color-surface); }
.vdb-half-title { margin: 0; font-size: var(--text-md); font-weight: 600; color: var(--color-text); }
.vdb-endpoints { list-style: none; margin: 0; padding: 0; border: 1px solid var(--color-border); border-radius: 10px; }
.vdb-endpoint { display: grid; grid-template-columns: minmax(0, auto) minmax(0, 1fr) auto; gap: 2px var(--space-3); align-items: center; padding: var(--space-2) var(--space-3); }
.vdb-endpoint + .vdb-endpoint { border-top: 1px solid var(--color-border); }
.vdb-endpoint-addr { font-size: var(--text-sm); font-weight: 600; color: var(--color-text); overflow-wrap: anywhere; }
.vdb-endpoint-note { font-size: var(--text-sm); color: var(--color-text-subtle); overflow-wrap: anywhere; }
.vdb-endpoint-form { display: grid; gap: var(--space-2); }
.vdb-endpoint-fields { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 7rem) minmax(0, 2fr); gap: 0 var(--space-3); }
@media (max-width: 40rem) {
  .vdb-endpoint { grid-template-columns: minmax(0, 1fr) auto; }
  .vdb-endpoint-note { grid-column: 1; }
  .vdb-endpoint-fields { grid-template-columns: minmax(0, 1fr); }
}

/* ---- one database's page: each application using it */
.vdb-apps { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-3); }
.vdb-app { display: grid; gap: var(--space-3); min-width: 0; padding: var(--space-4); border: 1px solid var(--color-border); border-radius: 12px; background: var(--color-surface); }
.vdb-app-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2); min-width: 0; }
.vdb-app-head h3 { margin: 0; font-size: var(--text-lg); font-weight: 600; overflow-wrap: anywhere; }
.vdb-tag { font-size: var(--text-xs); padding: 0 6px; border-radius: 6px; border: 1px solid var(--color-border); color: var(--color-text-subtle); }
.vdb-mine strong { font-weight: 600; color: var(--color-text); }
/* Wide enough to read a pod name in a table that scrolls sideways on a phone, rather than one that squeezes it to a column of letters. */
.vdb-owner { display: grid; gap: 2px; min-width: 12rem; overflow-wrap: anywhere; }
/* The client sessions an application owns, marked in the table of every client. */
.vm-table tr[data-mine="yes"] td { background: color-mix(in srgb, var(--ok-soft) 22%, transparent); }
.vm-table tr[data-mine="maybe"] td { background: color-mix(in srgb, var(--warn-soft) 18%, transparent); }

/* ---- the application page's Databases panel */
.apx-db-head-actions { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); }
.apx-db-list { list-style: none; margin: 0; padding: 0; display: grid; }
.apx-db { display: grid; gap: var(--space-3); min-width: 0; padding: var(--space-4) 0; }
.apx-db + .apx-db { border-top: 1px solid var(--color-border); }
.apx-db:first-child { padding-top: 0; }
.apx-db-head { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: var(--space-3); align-items: center; }
.apx-db-title { display: grid; gap: 2px; min-width: 0; }
.apx-db-name { font-size: var(--text-md); font-weight: 600; color: var(--color-text); overflow-wrap: anywhere; }
.apx-db-on { font-size: var(--text-sm); color: var(--color-text-subtle); overflow-wrap: anywhere; }
.apx-db-reason { margin: 0; font-size: var(--text-sm); line-height: 1.5; color: var(--warn-text); }
.apx-db[data-health="critical"] .apx-db-reason { color: var(--critical-text); }
.apx-db[data-health="unknown"] .apx-db-reason { color: var(--color-text-muted); }
.apx-db-figs { margin: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 8.5rem), 1fr)); gap: var(--space-3) var(--space-4); }
.apx-db-figs > div { display: grid; gap: 2px; min-width: 0; }
.apx-db-figs dt { font-size: 10.5px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-text-subtle); }
.apx-db-figs dd { margin: 0; font-size: var(--text-lg); font-weight: 600; color: var(--color-text); font-variant-numeric: tabular-nums; }
.apx-db-figs dd small { font-size: var(--text-sm); font-weight: 400; color: var(--color-text-subtle); }
.apx-db-scope { margin: 0; font-size: var(--text-sm); color: var(--color-text-subtle); }
.apx-db-findings { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; font-size: var(--text-sm); color: var(--color-text-muted); }
.apx-db-findings > li { padding-left: var(--space-3); border-left: 3px solid var(--info-text); }
.apx-db-findings > li[data-severity="critical"] { border-left-color: var(--critical-text); color: var(--color-text); }
.apx-db-findings > li[data-severity="warning"] { border-left-color: var(--warn-text); }
.apx-db-findings > li[data-severity="more"] { border-left-color: var(--color-border); }
.apx-db-foot { margin: 0; font-size: var(--text-sm); }

/* ---- the forecast window (apps/portal/app/forecast-window.tsx)
 *
 * One bar per forecast, all on one scale from today to the longest history any of them reads. Three
 * layers: the history the forecast learned from (a quiet track — as far as it could ever go), the
 * span asked for when that is further (a warm tint, the part it will not draw), and the reach (solid).
 * The tint is the whole point: "you asked for thirty and this one stops at fourteen" is visible before
 * anybody saves. A tint and not a hatch: the only sanctioned gradient is a chart's area fill, in SVG.
 */
.fw { display: grid; gap: var(--space-4); min-width: 0; }
.fw-now { margin: 0; font-size: var(--text-sm); color: var(--color-text-muted); }
.fw-choice { margin: 0; padding: 0; border: 0; min-width: 0; display: grid; gap: var(--space-3); }
.fw-legend { padding: 0; margin-bottom: var(--space-2); font-size: 10.5px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-text-subtle); }
.fw-days { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--space-3); }
.fw-days-field { display: grid; gap: 4px; font-size: var(--text-sm); color: var(--color-text-muted); }
.fw-days-field input { width: 7rem; font-variant-numeric: tabular-nums; }
.fw-presets { display: inline-flex; flex-wrap: wrap; gap: 6px; }
.fw-preset {
  padding: 4px 10px;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--color-surface);
  color: var(--color-text-muted);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.fw-preset:hover { color: var(--color-text); border-color: var(--accent); }
.fw-preset[aria-pressed="true"] { background: var(--accent-soft); border-color: var(--accent); color: var(--accent-text); }
.fw-preset:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; }
.fw-choice:disabled .fw-preset { cursor: not-allowed; opacity: 0.6; }
.fw-hint { flex-basis: 100%; margin: 0; font-size: 12px; color: var(--color-text-subtle); }
.fw-hint.is-error { color: var(--critical-text); }

.fw-reach { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-3); }
.fw-row { display: grid; grid-template-columns: minmax(0, 15rem) minmax(0, 1fr); gap: 4px var(--space-4); align-items: center; padding-top: var(--space-3); border-top: 1px solid var(--color-border); }
.fw-row-head { display: grid; gap: 2px; min-width: 0; }
.fw-name { font-size: var(--text-sm); font-weight: 600; color: var(--color-text); text-decoration: none; overflow-wrap: anywhere; }
.fw-name:hover { text-decoration: none; }
.fw-name:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; border-radius: 3px; }
.fw-what { font-size: 12px; color: var(--color-text-subtle); overflow-wrap: anywhere; }
.fw-bar { position: relative; height: 10px; border-radius: 999px; background: var(--color-border); overflow: hidden; }
.fw-bar > span { position: absolute; inset: 0 auto 0 0; border-radius: inherit; }
.fw-bar-history { background: color-mix(in srgb, var(--accent) 16%, transparent); }
.fw-bar-asked { background: color-mix(in srgb, var(--warn-text) 28%, transparent); }
.fw-bar-reach { background: var(--accent); }
.fw-row[data-short="yes"] .fw-bar-reach { background: var(--warn-text); }
.fw-row-say { grid-column: 2; margin: 0; font-size: 12px; color: var(--color-text-muted); }
.fw-row-say strong { color: var(--color-text); font-weight: 600; }
.fw-row-from { color: var(--color-text-subtle); }
.fw-aside { margin: 0; padding-left: var(--space-3); border-left: 3px solid var(--info-text); font-size: var(--text-sm); line-height: 1.55; color: var(--color-text-muted); }
@media (max-width: 640px) {
  .fw-row { grid-template-columns: minmax(0, 1fr); }
  .fw-row-say { grid-column: 1; }
}

/* The header slot that carries a page's own control beside the forecast window. */
.fw-actions { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
/* A plan computed for the previous window, shown while the new one is computed. */
.fw-recomputing { margin: 0; }

/* ==========================================================================
 * The deployment page (deploy/deployments/[app]) — Kairos's own layout.
 *
 * Replaces the Argo CD title bar and status cells. The delivery line is the signature: four stages
 * from commit to running, because in this product the second stage is a person approving on the
 * Changes page and that is where most deployments that "did nothing" are sitting. The map lays the
 * objects out in lanes — way in, workload, revisions, running now — with traffic drawn from the
 * EndpointSlices above them and ownership as solid lines between them. Built on the product tokens
 * only, so both themes and every palette change carry through.
 * ========================================================================== */
.kd-page { gap: var(--space-4); }
.kd-crumb { display: flex; gap: 6px; font-size: var(--text-sm); color: var(--text-muted); }
.kd-crumb a { color: var(--text-muted); text-decoration: none; }
.kd-crumb a:hover { color: var(--text-primary); }

.kd-head { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: var(--space-4); }
.kd-ident { display: flex; gap: 14px; min-width: 0; }
.kd-ident-text { min-width: 0; }
.kd-glyph { width: 48px; height: 48px; flex-shrink: 0; border-radius: var(--radius-md); background: var(--bg-surface); border: 1px solid var(--border-hairline); display: grid; place-items: center; color: var(--text-secondary); }
.kd-titlerow { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.kd-title { margin: 0; font-size: 24px; font-weight: 650; letter-spacing: -0.015em; color: var(--text-primary); overflow-wrap: anywhere; }
.kd-meta { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.kd-chip { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: var(--radius-full); border: 1px solid var(--border-hairline); background: var(--bg-sunken); color: var(--text-secondary); font-size: 12px; line-height: 18px; text-decoration: none; max-width: 100%; overflow-wrap: anywhere; }
.kd-chip-icon { display: inline-grid; flex: none; width: 13px; height: 13px; color: var(--text-muted); }
.kd-chip-icon svg { width: 13px; height: 13px; }
.kd-chip-sep { color: var(--text-muted); }
.kd-chip strong { font-weight: 600; color: var(--text-primary); }
a.kd-chip:hover { color: var(--text-primary); border-color: var(--border-strong); }

.kd-pill { display: inline-flex; align-items: center; gap: 6px; border-radius: var(--radius-full); padding: 3px 10px 3px 8px; font-size: 12.5px; font-weight: 600; white-space: nowrap; }
.kd-pill i { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.kd-pill.is-ok { background: var(--ok-soft); color: var(--ok-text); }
.kd-pill.is-warn { background: var(--warn-soft); color: var(--warn-text); }
.kd-pill.is-crit { background: var(--critical-soft); color: var(--critical-text); }
.kd-pill.is-info { background: var(--info-soft); color: var(--info-text); }
.kd-pill.is-plain { background: var(--bg-sunken); color: var(--text-secondary); border: 1px solid var(--border-hairline); }

.kd-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.kd-btn { display: inline-flex; align-items: center; gap: 7px; font: inherit; font-size: var(--text-sm); font-weight: 600; padding: 8px 14px; border-radius: var(--radius-sm); border: 1px solid var(--border-strong); background: var(--bg-surface); color: var(--text-primary); cursor: pointer; }
.kd-btn:hover:not(:disabled) { background: var(--bg-hover); }
.kd-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.kd-btn:focus-visible, .kd-tab:focus-visible, .kd-act:focus-visible, .kd-obj-main:focus-visible, .kd-guard:focus-visible, .kd-link:focus-visible, .kd-more:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.kd-btn.is-primary { background: var(--text-primary); color: var(--bg-page); border-color: var(--text-primary); }
.kd-btn.is-primary:hover:not(:disabled) { background: var(--text-secondary); border-color: var(--text-secondary); }
.kd-btn.is-danger { color: var(--critical-text); border-color: color-mix(in srgb, var(--critical) 50%, var(--border-hairline)); }
.kd-btn.is-quiet { padding: 8px 10px; color: var(--text-secondary); }
.kd-btn.is-quiet:hover:not(:disabled) { color: var(--critical-text); }
.kd-btn svg { width: 15px; height: 15px; }

/* the delivery line */
.kd-line { background: var(--bg-surface); border: 1px solid var(--border-hairline); border-radius: var(--radius-xl); padding: 18px 18px 14px; min-width: 0; }
.kd-line-head { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 8px; margin-bottom: 16px; }
.kd-eyebrow { font-size: 12px; font-weight: 650; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-muted); }
.kd-line-say { color: var(--text-primary); font-size: var(--text-sm); }
.kd-stages { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); }
.kd-stage { padding-right: 16px; min-width: 0; }
.kd-track { display: flex; align-items: center; margin-bottom: 12px; }
.kd-node { width: 26px; height: 26px; flex-shrink: 0; border-radius: 50%; display: grid; place-items: center; border: 2px solid var(--border-strong); background: var(--bg-surface); color: var(--text-muted); }
.kd-rail { flex: 1; height: 2px; margin-left: 6px; background: var(--border-strong); }
.kd-stage:last-child .kd-rail { display: none; }
.kd-stage[data-state="done"] .kd-node { background: var(--ok); border-color: var(--ok); color: var(--bg-page); }
.kd-stage[data-state="done"] .kd-rail { background: var(--ok); }
.kd-stage[data-state="ahead"] .kd-node { border-color: var(--info); background: var(--info-soft); color: var(--info-text); }
/* Dashed by a border, not a gradient: `tokens.test.ts` keeps gradients to the loading shimmer. */
.kd-stage[data-state="ahead"] .kd-rail { height: 0; background: none; border-top: 2px dashed var(--info); }
.kd-stage[data-state="wait"] .kd-node { border-color: var(--warn); background: var(--warn-soft); color: var(--warn-text); box-shadow: 0 0 0 5px var(--warn-soft); }
.kd-stage[data-state="fail"] .kd-node { border-color: var(--critical); background: var(--critical); color: var(--bg-page); box-shadow: 0 0 0 5px var(--critical-soft); }
.kd-stage-label { font-size: 12px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-muted); }
.kd-stage-main { margin-top: 2px; font-size: 15px; font-weight: 600; color: var(--text-primary); overflow-wrap: anywhere; }
.kd-stage-note { margin-top: 3px; font-size: 13px; color: var(--text-secondary); overflow-wrap: anywhere; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden; }
/* A commit hash is the one identifier here that stays mono, a size under the sans beside it so the two read as one line. */
.kd-stage-code { color: var(--text-primary); font-size: 12px; }
.kd-act { display: inline-block; margin-top: 8px; padding: 0; border: 0; background: none; font: inherit; font-size: 13px; font-weight: 600; color: var(--info-text); cursor: pointer; text-decoration: none; }
.kd-act:hover { text-decoration: none; }
.kd-line-foot { margin-top: 16px; padding-top: 12px; border-top: 1px solid var(--border-hairline); display: flex; flex-wrap: wrap; align-items: center; gap: 10px; color: var(--text-secondary); font-size: 13px; }
.kd-line-foot strong { color: var(--text-primary); font-weight: 600; }
@media (prefers-reduced-motion: no-preference) {
  .kd-stage[data-state="wait"] .kd-node { animation: kd-breathe 2.4s ease-in-out infinite; }
  @keyframes kd-breathe { 50% { box-shadow: 0 0 0 9px transparent; } }
}

/* tabs */
/*
 * The hairline is an inset shadow, not a border the tabs reach into with a negative margin. That margin
 * made the strip a pixel taller than its own box, and `overflow-x: auto` turns overflow-y to auto too,
 * so every deployment page carried a stray vertical scrollbar at the right end of its tabs.
 */
.kd-tabs { display: flex; gap: 2px; box-shadow: inset 0 -1px 0 var(--border-hairline); overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain; min-width: 0; }
.kd-tab { display: inline-flex; align-items: center; gap: 7px; padding: 10px 14px; border: 0; border-bottom: 2px solid transparent; background: none; font: inherit; font-size: var(--text-sm); color: var(--text-secondary); cursor: pointer; white-space: nowrap; }
.kd-tab:hover { color: var(--text-primary); }
.kd-tab.is-active { color: var(--text-primary); border-bottom-color: var(--text-primary); font-weight: 600; }
.kd-count { font-size: 11.5px; padding: 0 7px; border-radius: var(--radius-full); background: var(--bg-sunken); border: 1px solid var(--border-hairline); color: var(--text-secondary); }
.kd-count.is-alert { background: var(--critical-soft); border-color: transparent; color: var(--critical-text); }

/* overview */
.kd-overview { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-4); }
/*
 * The rail under the map: one band of panels that end on one line.
 *
 * It was `auto-fit` over a 300px minimum, aligned to the top, so the row took the tallest panel's
 * height and left voids under the other two — and at a middle width the third panel wrapped alone
 * into a half-empty row. The columns now answer to the rail's own width (the sidebar decides that,
 * not the window): three across, then two with the syncs taking the second row whole, then one.
 * Panels stretch, so a short one is a full-height card rather than a gap.
 *
 * `container-type` sits on a wrapper holding only these panels because it traps `position: fixed`
 * descendants (see `.cw-panel`), and nothing in the rail opens one.
 */
.kd-rail-wrap { container-type: inline-size; min-width: 0; }
.kd-rail-col { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-4); align-items: stretch; min-width: 0; }
@container (min-width: 640px) {
  .kd-rail-col { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .kd-rail-col > :last-child { grid-column: 1 / -1; }
}
@container (min-width: 960px) {
  .kd-rail-col { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .kd-rail-col > :last-child { grid-column: auto; }
}
.kd-panel { background: var(--bg-surface); border: 1px solid var(--border-hairline); border-radius: var(--radius-xl); padding: 16px; min-width: 0; }
.kd-panel-head { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: baseline; gap: 8px; margin-bottom: 12px; }
.kd-panel-title { margin: 0; font-size: 15px; font-weight: 600; color: var(--text-primary); }
.kd-panel > .kd-panel-title { margin-bottom: 12px; }
.kd-sub { margin: 0; font-size: 12.5px; color: var(--text-muted); }
.kd-link { padding: 0; border: 0; background: none; font: inherit; font-size: 12.5px; font-weight: 600; color: var(--info-text); cursor: pointer; }
.kd-need, .kd-needs > li { display: flex; gap: 10px; align-items: flex-start; }
.kd-needs { list-style: none; margin: 0; padding: 0; display: grid; gap: 14px; }
.kd-need strong { font-weight: 600; color: var(--text-primary); }
.kd-need p { margin: 2px 0 0; font-size: 13px; color: var(--text-secondary); }
.kd-need .kd-act { margin-top: 6px; }
.kd-need-ic { width: 30px; height: 30px; flex-shrink: 0; border-radius: 9px; display: grid; place-items: center; }
.kd-need.is-ok .kd-need-ic { background: var(--ok-soft); color: var(--ok-text); }
.kd-need.is-warn .kd-need-ic { background: var(--warn-soft); color: var(--warn-text); }
.kd-need.is-crit .kd-need-ic { background: var(--critical-soft); color: var(--critical-text); }
.kd-need.is-info .kd-need-ic { background: var(--info-soft); color: var(--info-text); }
.kd-kinds { list-style: none; margin: 0; padding: 0; display: grid; gap: 9px; }
.kd-kind { display: grid; grid-template-columns: 136px minmax(0, 1fr) 46px; gap: 10px; align-items: center; font-size: 13px; color: var(--text-primary); }
.kd-meter { height: 6px; border-radius: var(--radius-full); background: var(--bg-sunken); overflow: hidden; }
.kd-meter i { display: block; height: 100%; border-radius: var(--radius-full); background: var(--ok); }
.kd-kind.is-warn .kd-meter i { background: var(--warn); }
.kd-kind.is-crit .kd-meter i { background: var(--critical); }
.kd-kind-v { text-align: right; color: var(--text-secondary); }
.kd-syncs { list-style: none; margin: 0; padding: 0; }
.kd-sync { display: grid; grid-template-columns: 14px minmax(0, 1fr) auto; gap: 10px; align-items: start; padding: 7px 0; border-top: 1px solid var(--border-hairline); font-size: 13px; color: var(--text-primary); }
.kd-sync:first-child { border-top: 0; padding-top: 2px; }
.kd-sync-body { min-width: 0; overflow-wrap: anywhere; }
.kd-sync-mark { width: 12px; height: 12px; margin-top: 3px; border-radius: 50%; }
.kd-sync-mark.is-ok { background: var(--ok); }
.kd-sync-mark.is-crit { background: var(--critical); }
.kd-sync-mark.is-warn { background: var(--warn); }
.kd-sync-mark.is-dry { border: 1.5px dashed var(--text-muted); }
.kd-sync-when { white-space: nowrap; }
.kd-sync-body .kd-sub { margin-top: 1px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* the live map */
.kd-map-frame { display: grid; gap: 12px; min-width: 0; }
.kd-legend { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 14px; font-size: 12px; color: var(--text-muted); }
.kd-legend span { display: inline-flex; align-items: center; gap: 6px; }
.kd-map-scroll { overflow-x: auto; overscroll-behavior-x: contain; min-width: 0; }
.kd-map { position: relative; display: grid; gap: 36px; min-width: -moz-min-content; min-width: min-content; padding: 2px 2px 6px; }
.kd-wires { position: absolute; top: 0; left: 0; pointer-events: none; overflow: visible; }
.kd-wire { fill: none; stroke-width: 1.6; }
.kd-wire.is-own { stroke: var(--text-muted); }
.kd-wire.is-old { stroke: var(--text-muted); opacity: 0.4; }
.kd-wire.is-traffic { stroke: var(--info); stroke-dasharray: 5 4; }
.kd-lane { position: relative; display: flex; flex-direction: column; gap: 10px; min-width: 0; }
/* The margin is the traffic channel: the line runs between the lane names and the cards, never across a name. */
.kd-lane-name { align-self: flex-start; margin-bottom: 18px; font-size: 11.5px; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: var(--text-muted); }
.kd-obj { position: relative; border: 1px solid var(--border-hairline); border-radius: var(--radius-md); background: var(--bg-surface); }
.kd-obj:hover { border-color: var(--border-strong); background: var(--bg-hover); }
.kd-obj-main { display: grid; gap: 4px; width: 100%; padding: 10px 34px 10px 12px; border: 0; background: none; font: inherit; text-align: left; color: var(--text-primary); cursor: pointer; border-radius: inherit; }
.kd-obj-kind { display: flex; align-items: center; gap: 6px; font-size: 11.5px; font-weight: 600; color: var(--text-muted); }
.kd-obj-kind .kd-dot { margin-left: auto; }
.kd-obj-icon { display: inline-grid; width: 16px; height: 16px; color: var(--text-muted); }
.kd-obj-icon svg { width: 16px; height: 16px; }
/* A resource name is the card's title, set in the interface face as the OpenShift console sets it; mono is kept for hashes. */
.kd-obj-name { font-size: 13px; font-weight: 600; letter-spacing: -0.005em; line-height: 1.3; overflow-wrap: anywhere; }
.kd-obj-detail { font-size: 12px; color: var(--text-secondary); }
/* A warning event is history unless the object is unhealthy now; only then is it drawn in the critical colour. */
.kd-obj-event { display: inline-flex; align-items: center; gap: 5px; min-width: 0; font-size: 11.5px; color: var(--text-muted); }
.kd-obj-event > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kd-obj-event-mark { flex: none; color: var(--warn-text); }
.kd-obj-event.is-live { font-weight: 600; color: var(--critical-text); }
.kd-obj-event.is-live .kd-obj-event-mark { color: var(--critical-text); }
.kd-obj-changed { font-size: 11.5px; color: var(--info-text); }
/*
 * A zero-size anchor on the title row's centre line (10px padding + half its 16px row), so the menu's
 * dots — centred on this point by `.cdw-nmenu` — sit level with the status dot beside them. At `top: 6px`
 * the dots centred on a point 6px from the card's edge and hung 12px above the dot they belong with.
 */
.kd-obj-menu { position: absolute; top: 18px; right: 6px; width: 0; height: 0; }
/* The live revision, said in its title row rather than on a tag hung over the card's border. */
.kd-obj-badge { padding: 0 7px; border-radius: var(--radius-full); border: 1px solid var(--border-strong); background: var(--bg-sunken); color: var(--text-primary); font-size: 10.5px; font-weight: 600; line-height: 14px; letter-spacing: 0.02em; }
.kd-obj.is-faded { opacity: 0.55; }
.kd-obj.is-faded:hover { opacity: 1; }
.kd-obj.is-degraded { border-color: var(--critical); }
.kd-obj.is-progressing { border-color: var(--warn); }
.kd-obj.is-missing { border-style: dashed; }
.kd-dot { width: 8px; height: 8px; flex-shrink: 0; border-radius: 50%; background: var(--ok); }
.kd-dot.is-degraded { background: var(--critical); }
.kd-dot.is-progressing { background: var(--warn); }
.kd-dot.is-missing { background: transparent; box-shadow: inset 0 0 0 1.5px var(--text-muted); }
.kd-dot.is-unknown { background: var(--text-muted); }
.kd-more { padding: 8px 10px; border: 1px dashed var(--border-strong); border-radius: var(--radius-md); background: none; font: inherit; font-size: 12.5px; color: var(--text-secondary); cursor: pointer; text-align: left; }
.kd-guards { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 10px 12px; border: 1px dashed var(--border-strong); border-radius: var(--radius-md); background: var(--bg-sunken); }
.kd-guards-name { font-size: 11.5px; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: var(--text-muted); margin-right: 4px; }
.kd-guard { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border: 1px solid var(--border-hairline); border-radius: var(--radius-full); background: var(--bg-surface); font: inherit; font-size: 12.5px; color: var(--text-primary); cursor: pointer; max-width: 100%; }
.kd-guard-name { font-weight: 600; overflow-wrap: anywhere; }
.kd-guard:hover { border-color: var(--border-strong); }
.kd-guard-kind { color: var(--text-muted); }
.kd-map-empty { margin: 0; color: var(--text-secondary); font-size: var(--text-sm); }

@media (max-width: 760px) {
  .kd-stages { grid-template-columns: minmax(0, 1fr); gap: 14px; }
  .kd-rail { display: none; }
  .kd-kind { grid-template-columns: 112px minmax(0, 1fr) 42px; }
}

/* ---------------------------------------------------------------- capacity: traffic right now */

/* The usual range for this hour as a quiet band, the usual level and what the running pods carry as
   ticks, and now as the bar — so "how far past normal, and past what we can serve" is one glance. */
/* A surging service's card carries the warning edge, so it reads apart from the plans below it. */
.cp-surge {
  border-left: 3px solid var(--warn);
}
.cp-surge-meter {
  position: relative;
  height: 14px;
  border-radius: 7px;
  background: var(--color-sunken);
  overflow: hidden;
}
.cp-surge-usual {
  position: absolute;
  top: 0;
  bottom: 0;
  background: color-mix(in srgb, var(--color-text-muted) 22%, transparent);
}
.cp-surge-now {
  position: absolute;
  left: 0;
  top: 4px;
  height: 6px;
  border-radius: 3px;
  background: var(--color-primary);
}
.cp-surge-now.is-unusual {
  background: var(--warn);
}
.cp-surge-mark {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  margin-left: -1px;
  background: var(--color-text-muted);
}
.cp-surge-mark.is-carries {
  background: var(--critical);
}
.cp-surge-nodes {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}
.cp-surge-node {
  padding: 1px var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  font-size: var(--text-xs);
  font-family: var(--font-mono);
}
.cp-surge-calm {
  margin-top: var(--space-3);
}

/*
 * AI models (customer portal, `/ai/models`): which model answers each feature.
 *
 * Laid out as the data is shaped — one trunk (the default model, a card with its detail) and the
 * features as one list beneath it. A feature that follows the default is a quiet line; one with its
 * own model carries that model. Every row is the same three tracks, the last a fixed width, so the
 * buttons of nine separate rows line up as one column. Provider and model names are long unbreakable
 * tokens (`nvidia/nemotron-3-ultra-550b-a55b`), so each is bounded and wraps rather than widening a
 * track and pushing the buttons out of the card.
 *
 * `container-type` here traps `position: fixed` descendants (see `.cw-panel`); the dialogs and the
 * row menu on this page are both portalled to <body>, so nothing fixed lives inside it.
 */
.aim { display: grid; gap: var(--space-4); min-width: 0; container-type: inline-size; }
.aim-feature { font-size: var(--text-base); font-weight: 600; color: var(--text-primary); }
.aim-sub { font-size: var(--text-xs); color: var(--text-muted); }
.aim-clip { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.aim-provider { font-weight: 600; color: var(--text-primary); }
.aim-model { display: inline-block; max-width: 100%; overflow-wrap: anywhere; }
.aim-copy { margin: 0; font-size: var(--text-sm); line-height: var(--leading-relaxed); color: var(--text-secondary); }

/* The trunk: the default model, its name large and its particulars as labelled facts. */
.aim-trunk { display: grid; gap: var(--space-4); min-width: 0; }
.aim-trunk-name { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); min-width: 0; }
.aim-trunk-name .aim-provider { font-size: var(--text-lg); }
.aim-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr)); gap: var(--space-3) var(--space-5); margin: 0; min-width: 0; }
.aim-facts > div { display: grid; gap: 2px; min-width: 0; }
.aim-facts dt { font-size: var(--text-xs); color: var(--text-muted); }
.aim-facts dd { margin: 0; min-width: 0; font-size: var(--text-sm); color: var(--text-primary); }

/* The branches, and the stored keys, which share the row. */
.aim-rows { list-style: none; margin: 0; padding: 0; }
.aim-row {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr) 12rem;
  grid-template-areas: "feature model actions" "tested tested tested";
  align-items: center;
  gap: 0 var(--space-4);
  padding: var(--space-3) var(--space-5);
  min-width: 0;
}
.aim-row + .aim-row { border-top: 1px solid var(--border-hairline); }
.aim-row.is-readonly { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); grid-template-areas: "feature model" "tested tested"; }
.aim-row-feature { grid-area: feature; display: grid; gap: 2px; min-width: 0; }
.aim-row-model { grid-area: model; display: grid; gap: var(--space-1); min-width: 0; }
.aim-row-name { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1) var(--space-2); min-width: 0; font-size: var(--text-sm); }
.aim-row-marks { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1) var(--space-2); }
.aim-row-marks:empty { display: none; }
.aim-row-actions { grid-area: actions; display: flex; justify-content: flex-end; min-width: 0; }
.aim-row-tested { grid-area: tested; padding-top: var(--space-2); min-width: 0; }
/* A feature that follows the default is the ordinary case, so it is the quiet one. */
.aim-row.is-following .aim-feature { font-weight: 500; color: var(--text-secondary); }
.aim-follows { font-size: var(--text-sm); color: var(--text-muted); }
.aim-follows-model { margin-left: var(--space-1); font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-secondary); overflow-wrap: anywhere; }

/* One primary button and the row's menu, sized to the small button so the pair reads as one control. */
.aim-row-buttons { display: inline-flex; align-items: center; gap: var(--space-1); }
.aim-menu { position: relative; display: inline-block; width: var(--size-control-sm); height: var(--size-control-sm); flex: none; }
.aim-menu .cdw-nmenu { right: 0; }
.aim-menu .cdw-nmenu-dots,
.aim-menu-off {
  width: var(--size-control-sm);
  height: var(--size-control-sm);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  color: var(--text-secondary);
}
.aim-menu-off { display: inline-grid; place-items: center; padding: 0; font: inherit; cursor: not-allowed; opacity: 0.5; }

.aim-test { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-2); min-height: var(--size-control-sm); padding: var(--space-1) var(--space-1) var(--space-1) var(--space-3); border: 1px solid var(--border-hairline); border-left: 3px solid var(--ok); border-radius: var(--radius-md); background: var(--bg-sunken); font-size: var(--text-sm); color: var(--text-primary); }
.aim-test.is-failed { border-left-color: var(--critical); }

@container (max-width: 640px) {
  .aim-row,
  .aim-row.is-readonly { grid-template-columns: minmax(0, 1fr); grid-template-areas: "feature" "model" "actions" "tested"; gap: var(--space-2); }
  .aim-row-actions { justify-content: flex-start; }
  /* Stacked and left-aligned, a ghost button's invisible padding would indent its text past the column's edge. */
  .aim-row.is-following .aim-row-actions > .btn { margin-left: calc(-1 * var(--space-3) - 1px); }
}

/* The dialog. Field's own inline margin spaces the rows (trap 30); these only arrange them. */
.aim-form { display: grid; min-width: 0; }
.aim-inline { display: flex; gap: var(--space-2); align-items: center; }
.aim-inline > :first-child { flex: 1; min-width: 0; }
.aim-found { display: flex; flex-wrap: wrap; gap: var(--space-1); margin: calc(var(--space-2) * -1) 0 var(--space-4); }
.aim-chip { padding: 2px 8px; border: 1px solid var(--border-hairline); border-radius: var(--radius-full); background: var(--bg-sunken); color: var(--text-secondary); font: inherit; font-family: var(--font-mono); font-size: var(--text-xs); cursor: pointer; }
.aim-chip:hover { color: var(--text-primary); border-color: var(--border-strong); }
.aim-chip.is-on { border-color: var(--accent); background: var(--accent-soft); color: var(--text-primary); }
.aim-chip:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
.aim-limits { margin: 0 0 var(--space-4); padding: var(--space-3) var(--space-4) 0; border: 1px solid var(--border-hairline); border-radius: var(--radius-md); min-width: 0; }
.aim-limits > legend { padding: 0 var(--space-1); font-size: var(--text-sm); color: var(--text-secondary); }
.aim-limits-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(9rem, 100%), 1fr)); gap: 0 var(--space-3); }
.aim-egress { display: flex; gap: var(--space-3); align-items: flex-start; padding: var(--space-3); border: 1px solid var(--border-hairline); border-left: 3px solid var(--warn); border-radius: var(--radius-md); background: var(--bg-sunken); font-size: var(--text-sm); line-height: var(--leading-relaxed); cursor: pointer; }
.aim-egress input { margin-top: 4px; width: 16px; height: 16px; flex: none; }

/* The host, fine-tuning and knowledge pages share the AI models page's rows and cards; these are theirs alone. */
.aim-card-foot { margin: var(--space-3) 0 0; }
.aim-check { display: flex; gap: var(--space-2); align-items: center; margin-bottom: var(--space-3); font-size: var(--text-sm); color: var(--text-primary); cursor: pointer; }
.aim-check input { width: 16px; height: 16px; flex: none; }
/* The real file input is kept for its picker and its accessibility, and driven by the visible button. */
.aim-file { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.aim-log { list-style: none; margin: 0; padding: var(--space-2) var(--space-3); max-height: 16rem; overflow-y: auto; border: 1px solid var(--border-hairline); border-radius: var(--radius-md); background: var(--bg-sunken); font-family: var(--font-mono); font-size: var(--text-xs); }
.aim-log-line { display: grid; grid-template-columns: 6rem 7rem minmax(0, 1fr); gap: var(--space-2); padding: 2px 0; color: var(--text-secondary); }
.aim-log-line.is-warn .aim-log-msg { color: var(--warn-text); }
.aim-log-line.is-error .aim-log-msg { color: var(--critical-text); }
.aim-log-at { color: var(--text-muted); }
.aim-log-phase { color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.aim-log-msg { overflow-wrap: anywhere; }

/* ---------------------------------------------------------------- upgrade: status, investigation, timeline */

.uw-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.uw-version {
  font-size: var(--text-lg);
  font-weight: 600;
}
.uw-headline {
  margin: var(--space-2) 0 var(--space-3);
  color: var(--color-text);
}
.uw-bar {
  height: 6px;
  border-radius: 3px;
  background: var(--color-sunken);
  overflow: hidden;
  margin-bottom: var(--space-3);
}
.uw-bar > i {
  display: block;
  height: 100%;
  background: var(--color-primary);
}
.uw-pools {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}
.uw-pool-head {
  display: flex;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
  text-transform: capitalize;
}
.uw-nodes {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}
/* One chip per node, coloured by where it is in the machine-config rollout. */
.uw-node {
  padding: 2px var(--space-2);
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  border: 1px solid var(--color-border);
}
.uw-node.is-done {
  border-color: var(--status-ready-fill);
  color: var(--status-ready-fg);
}
.uw-node.is-working {
  border-color: var(--status-progressing-fill);
  color: var(--status-progressing-fg);
}
.uw-node.is-waiting {
  color: var(--color-text-muted);
}
.uw-node.is-degraded,
.uw-node.is-down {
  border-color: var(--critical);
  color: var(--critical-text);
}
.uw-stuck {
  margin: var(--space-1) 0 0;
  padding-left: var(--space-4);
}
.uw-ops-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  margin: var(--space-3) 0 var(--space-2);
}
.uw-link {
  margin-left: auto;
  background: none;
  border: 0;
  padding: 0;
  color: var(--color-primary-text);
  cursor: pointer;
  font-size: var(--text-sm);
}
.uw-ops {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
}
.uw-op {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
}
.uw-op-name {
  font-weight: 600;
  min-width: 12ch;
}
.uw-op-msg {
  flex-basis: 100%;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  overflow-wrap: anywhere;
}
.uw-root {
  display: grid;
  gap: var(--space-1);
  padding: var(--space-3);
  border-left: 3px solid var(--critical);
  background: var(--critical-soft);
  border-radius: var(--radius-card);
  margin-bottom: var(--space-3);
}
.uw-root p {
  margin: 0;
}
.uw-leads {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-3);
}
.uw-lead {
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
}
.uw-lead.is-dependency {
  border-color: var(--critical);
}
.uw-lead-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}
.uw-lead-summary {
  margin: var(--space-2) 0;
  overflow-wrap: anywhere;
}
.uw-evidence > summary {
  cursor: pointer;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.uw-evidence ul {
  margin: var(--space-2) 0 0;
  padding-left: var(--space-4);
  display: grid;
  gap: var(--space-2);
}
.uw-evidence li {
  display: grid;
  gap: 2px;
  font-size: var(--text-sm);
  overflow-wrap: anywhere;
}
.uw-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-3);
}
.uw-timeline {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
}
.uw-timeline > li {
  display: grid;
  grid-template-columns: 12ch 1fr;
  gap: var(--space-3);
  padding-left: var(--space-3);
  border-left: 3px solid var(--color-border);
}
.uw-timeline > li.is-ok {
  border-left-color: var(--status-ready-fill);
}
.uw-timeline > li.is-info {
  border-left-color: var(--status-progressing-fill);
}
.uw-timeline > li.is-warn {
  border-left-color: var(--warn);
}
.uw-timeline > li.is-crit {
  border-left-color: var(--critical);
}
.uw-when {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}
.uw-what {
  overflow-wrap: anywhere;
}
@media (max-width: 640px) {
  .uw-timeline > li {
    grid-template-columns: 1fr;
    gap: 0;
  }
}

/* ---------------------------------------------------------------- AI: what the agents know */

.mem-clusters {
  display: grid;
  gap: var(--space-5);
}
.mem-cluster-name {
  margin: 0 0 var(--space-2);
  font-size: var(--text-md);
}
.mem-section {
  margin-top: var(--space-3);
}
.mem-section-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}
.mem-rows {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
}
.mem-row {
  display: grid;
  gap: 2px;
  padding-left: var(--space-3);
  border-left: 2px solid var(--color-border);
  overflow-wrap: anywhere;
}
.mem-row[data-status="resolved"] {
  opacity: 0.7;
}
.mem-statement {
  color: var(--color-text);
}
.mem-resolved {
  margin-top: var(--space-3);
}
.mem-resolved > summary {
  cursor: pointer;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* ---------------------------------------------------------------- console: a workload's pods */

.wl-pods {
  display: grid;
  gap: var(--space-3);
}
.wl-pods-pick {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
}
.wl-pods-open {
  margin-left: auto;
  font-size: var(--text-sm);
  color: var(--color-primary-text);
}

/*
 * AI usage (`/ai/usage`): tokens per use case, per day and per model. It shares the AI pages' rows;
 * these are the figure-and-meter and the day bars, one scale for every bar so the week's shape reads.
 */
.aiu-window { margin: 0 0 var(--space-4); }
.aiu-figure { display: flex; align-items: center; gap: var(--space-3); min-width: 0; }
.aiu-total { flex: none; min-width: 4.5ch; font-size: var(--text-md); font-weight: 600; color: var(--text-primary); }
.aiu-meter { flex: 1; height: 6px; border-radius: var(--radius-full); background: var(--bg-sunken); overflow: hidden; }
.aiu-meter > i { display: block; height: 100%; border-radius: inherit; background: var(--accent); }
.aiu-days { list-style: none; margin: 0; padding: 0; display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: var(--space-2); align-items: end; height: 10rem; }
.aiu-day { display: grid; grid-template-rows: minmax(0, 1fr) auto auto; justify-items: center; gap: 2px; height: 100%; min-width: 0; }
.aiu-day-bar { align-self: end; width: min(100%, 2.5rem); border-radius: var(--radius-sm) var(--radius-sm) 0 0; background: var(--accent-soft); box-shadow: inset 0 2px 0 var(--accent); }
.aiu-day-value { font-size: var(--text-xs); color: var(--text-secondary); }
.aiu-day-label { font-size: var(--text-xs); color: var(--text-muted); white-space: nowrap; }
/* External providers: the token limit, two meters side by side, and what compaction and caching saved. */
.aiu-ext { display: grid; gap: var(--space-4); min-width: 0; }
.aiu-ext-meters { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr)); gap: var(--space-4); }
.aiu-ext-meter { display: grid; gap: var(--space-1); min-width: 0; }
.aiu-ext-meter-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-2); flex-wrap: wrap; }
.aiu-ext-meter-head strong { font-size: var(--text-md); font-weight: 600; color: var(--text-primary); }
.aiu-ext-meter.is-near .aiu-meter > i { background: var(--warn); }
.aiu-ext-meter.is-reached .aiu-meter > i { background: var(--critical); }
.aiu-ext-foot { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); flex-wrap: wrap; }
.aiu-ext-foot > p { margin: 0; flex: 1 1 20rem; min-width: 0; }
.aiu-ext-edit { display: grid; gap: var(--space-3); }
.aiu-ext-fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr)); gap: 0 var(--space-4); }
.aiu-ext-actions { display: flex; justify-content: flex-end; gap: var(--space-2); }
.aiu-ext-note { margin: 0; padding-top: var(--space-3); border-top: 1px solid var(--border-hairline); }
.aiu-ext-savings { list-style: none; margin: 0; padding: var(--space-3) 0 0; border-top: 1px solid var(--border-hairline); display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr)); gap: var(--space-3) var(--space-4); }
.aiu-ext-savings > li { display: grid; gap: 2px; min-width: 0; }
.aiu-ext-savings strong { font-size: var(--text-md); font-weight: 600; color: var(--text-primary); }


/* ---- deployment: CI pipelines tab (apps/portal/app/deploy/deployments/[app]/ci-pipelines.tsx)
 *
 * Three numbered steps and a log, at the reading measure: this tab is instructions, and a reader
 * follows them top to bottom with a CI's settings open beside it.
 */
.ci-pipes { display: grid; gap: var(--space-5); max-width: 980px; min-width: 0; }
.ci-pipes-lead { margin: 0; font-size: var(--text-md); line-height: var(--leading-relaxed); color: var(--text-secondary); text-wrap: pretty; }
.ci-pipes-note { margin: 0; font-size: var(--text-sm); line-height: 1.55; color: var(--text-muted); overflow-wrap: anywhere; }
.ci-pipes-step { display: grid; gap: var(--space-3); min-width: 0; padding-top: var(--space-4); border-top: 1px solid var(--border-hairline); }
.ci-pipes-step h3 { margin: 0; font-size: var(--text-md); font-weight: 600; color: var(--text-primary); }
.ci-pipes-form { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }
.ci-pipes-form > label { flex: 1 1 220px; min-width: 0; }
.ci-pipes-fault { margin: 0; font-size: var(--text-xs); color: var(--critical-text); }
.ci-pipes-kinds { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.ci-pipes-kind {
  padding: 6px 12px;
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-full);
  background: none;
  font: inherit;
  font-size: var(--text-sm);
  color: var(--text-secondary);
  cursor: pointer;
}
.ci-pipes-kind:hover { color: var(--text-primary); border-color: var(--border-strong); }
.ci-pipes-kind:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.ci-pipes-kind.is-on { color: var(--text-primary); border-color: var(--text-primary); font-weight: 600; }
.ci-pipes-flow { margin: 0; padding-left: 1.25rem; display: grid; gap: var(--space-2); font-size: var(--text-sm); line-height: 1.55; color: var(--text-secondary); }
.ci-pipes-table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.ci-pipes-table th { text-align: left; font-size: var(--text-xs); font-weight: 600; color: var(--text-muted); padding: 0 var(--space-3) var(--space-2) 0; border-bottom: 1px solid var(--border-hairline); }
.ci-pipes-table td { padding: var(--space-2) var(--space-3) var(--space-2) 0; vertical-align: top; border-bottom: 1px solid var(--border-hairline); overflow-wrap: anywhere; }
.ci-pipes-table tr.is-revoked td { color: var(--text-muted); text-decoration: line-through; text-decoration-color: var(--border-strong); }
.ci-pipes-msg { margin-top: var(--space-1); font-size: var(--text-xs); color: var(--text-muted); }
.ci-pipes .code-text { white-space: pre; }

/*
 * Repository settings. Each section is a heading column beside its controls, so every section's
 * inputs start on one vertical line and its purpose sits next to them instead of in a paragraph above.
 *
 * Fields line up by their TOP edge here, not their bottom. `.cdw-formrow` aligns to the end so a
 * button sits on the inputs' baseline, which drops any field without a hint below its neighbours —
 * the "Kind" box sitting lower than "Pattern" was exactly that.
 */
.cdw-set {
  display: grid;
  grid-template-columns: minmax(180px, 240px) minmax(0, 1fr);
  gap: var(--space-6);
  padding: var(--space-5);
}
.cdw-set-intro { min-width: 0; }
.cdw-set .cdw-panel-title { font-size: var(--text-md); margin: 0 0 var(--space-2); }
.cdw-set-lead { margin: 0; font-size: var(--text-sm); line-height: 1.5; color: var(--text-secondary); }
.cdw-set-body { display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; }
.cdw-set-body > p { margin: 0; }
.cdw-set-form { display: flex; flex-direction: column; gap: var(--space-4); }
/* A form under a table of what already exists is a separate step, so it gets a rule above it. */
.cdw-set-body > * + .cdw-set-form { border-top: 1px solid var(--border-hairline); padding-top: var(--space-4); }
.cdw-set .cdw-set-subhead { margin: 0; font-size: var(--text-sm); font-weight: 600; color: var(--text-primary); }

.cdw-set-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  -moz-column-gap: var(--space-4);
       column-gap: var(--space-4);
  align-items: start;
  /* `Field` carries an inline bottom margin (trap 30) that spaces the rows; take the last row's back. */
  margin-bottom: calc(-1 * var(--space-4));
}
.cdw-set-grid.is-three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.cdw-set-grid.is-rule { grid-template-columns: 140px minmax(0, 1fr) 160px; }
.cdw-set-span-2 { grid-column: span 2; }
.cdw-set-span-all { grid-column: 1 / -1; }

.cdw-set-groups { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-4); }
.cdw-set-group {
  margin: 0;
  min-width: 0;
  padding: var(--space-3) var(--space-4) var(--space-4);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.cdw-set-group > legend { padding: 0 var(--space-1); font-size: var(--text-sm); font-weight: 600; color: var(--text-primary); }

.cdw-set-check {
  display: grid;
  grid-template-columns: 16px minmax(0, 1fr);
  -moz-column-gap: var(--space-2);
       column-gap: var(--space-2);
  align-items: start;
  font-size: var(--text-sm);
  line-height: 1.4;
  color: var(--text-primary);
  cursor: pointer;
}
.cdw-set-check > input { width: 16px; height: 16px; margin: 2px 0 0; accent-color: var(--accent-solid); cursor: inherit; }
.cdw-set-check:has(> input:disabled) { cursor: default; color: var(--text-secondary); }
.cdw-set-check-note { display: block; margin-top: 2px; font-size: var(--text-xs); color: var(--text-muted); }

.cdw-set-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border-hairline);
}
.cdw-set-actions > .muted { font-size: var(--text-sm); }
.cdw-set-rowactions { display: flex; flex-wrap: wrap; gap: var(--space-2); justify-content: flex-end; }

.cdw-set-defs {
  margin: 0;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-2) var(--space-3);
  font-size: var(--text-sm);
  line-height: 1.5;
}
.cdw-set-defs > dt { font-weight: 600; color: var(--text-primary); }
.cdw-set-defs > dd { margin: 0; color: var(--text-secondary); }

@media (max-width: 960px) {
  .cdw-set { grid-template-columns: minmax(0, 1fr); gap: var(--space-4); padding: var(--space-4); }
}
@media (max-width: 640px) {
  .cdw-set-grid,
  .cdw-set-grid.is-three,
  .cdw-set-grid.is-rule,
  .cdw-set-groups { grid-template-columns: minmax(0, 1fr); }
  .cdw-set-span-2 { grid-column: auto; }
}

/*
 * Creating a repository: where the code lives, then which CI tests it.
 *
 * The form needs the full width once it has a server address and a CI to ask about, so the open
 * card spans both columns and "Open an existing repository" moves below it.
 */
.cdw-choose-card.is-on { grid-column: 1 / -1; }
.cdw-src,
.cdw-ci { margin: 0; padding: 0; border: 0; min-width: 0; display: grid; gap: var(--space-2); }
.cdw-src-legend { padding: 0; margin-bottom: var(--space-2); font-size: var(--text-sm); font-weight: 600; color: var(--text-primary); }
.cdw-src-options { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: var(--space-2); }
.cdw-src-option {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-rows: auto auto;
  -moz-column-gap: var(--space-3);
       column-gap: var(--space-3);
  row-gap: 2px;
  align-items: center;
  padding: var(--space-3);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  cursor: pointer;
  transition: border-color 120ms ease, background-color 120ms ease;
}
.cdw-src-option:hover { border-color: var(--border-strong); }
.cdw-src-option.is-on { border-color: var(--accent); background: var(--accent-soft); }
.cdw-src-option.is-locked { cursor: not-allowed; opacity: 0.6; }
.cdw-src-option.is-locked:hover { border-color: var(--border-hairline); }
/* The radio stays in the accessibility tree and takes focus; the tile is what is seen. */
.cdw-src-option > input { position: absolute; opacity: 0; inset: 0; margin: 0; cursor: inherit; }
.cdw-src-option:has(> input:focus-visible) { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.cdw-src-mark {
  grid-row: 1 / span 2;
  display: inline-grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 700;
  color: var(--text-primary);
  background: var(--bg-sunken);
  border: 1px solid var(--border-hairline);
}
.cdw-src-mark.is-kairos { color: var(--accent-text); background: var(--accent-soft); border-color: transparent; }
.cdw-src-name { font-size: var(--text-sm); font-weight: 600; color: var(--text-primary); }
.cdw-src-note { font-size: var(--text-xs); line-height: 1.4; color: var(--text-secondary); }

.cdw-create-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  -moz-column-gap: var(--space-3);
       column-gap: var(--space-3);
  align-items: start;
  /* `Field` spaces its rows with an inline bottom margin (trap 30); take the last row's back. */
  margin-bottom: calc(-1 * var(--space-4));
}
.cdw-create-span { grid-column: 1 / span 2; }

.cdw-ci-options { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.cdw-ci-option {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  padding: 0 var(--space-3);
  border: 1px solid var(--border-hairline);
  border-radius: 999px;
  font-size: var(--text-sm);
  color: var(--text-secondary);
  background: var(--bg-surface);
  cursor: pointer;
}
.cdw-ci-option:hover { border-color: var(--border-strong); color: var(--text-primary); }
.cdw-ci-option.is-on { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-text); font-weight: 600; }
.cdw-ci-option > input { position: absolute; opacity: 0; inset: 0; margin: 0; cursor: inherit; }
.cdw-ci-option:has(> input:focus-visible) { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.cdw-ci-note { margin: 0; font-size: var(--text-xs); line-height: 1.5; color: var(--text-secondary); max-width: 72ch; }

/* After a server is connected: what to do next, in order. */
.cdw-made { display: grid; gap: var(--space-3); }
.cdw-made-head { margin: 0; font-size: var(--text-sm); line-height: 1.5; color: var(--text-primary); }
.cdw-made-steps { margin: 0; padding-left: var(--space-5); display: grid; gap: var(--space-3); font-size: var(--text-sm); }
/* `list-item`, not grid: a grid item has no marker, and the numbers are the point of an ordered list. */
.cdw-made-steps > li { display: list-item; padding-left: var(--space-1); }
.cdw-made-steps > li::marker { font-weight: 600; color: var(--text-secondary); }
.cdw-made-steps > li > span { display: block; }
.cdw-made-steps > li > * + * { margin-top: var(--space-1); }
.cdw-made-title { font-weight: 600; color: var(--text-primary); }
.cdw-made-kv {
  margin: var(--space-1) 0 0;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-1) var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
}
.cdw-made-kv > dt { font-size: var(--text-xs); color: var(--text-secondary); align-self: center; }
.cdw-made-kv > dd { margin: 0; font-size: var(--text-xs); overflow-wrap: anywhere; -webkit-user-select: all; -moz-user-select: all; user-select: all; color: var(--text-primary); }

/* The repository list's "Source & CI" cell. */
.cdw-index-src { display: grid; gap: 2px; min-width: 0; }
.cdw-index-src-line { display: inline-flex; align-items: center; gap: var(--space-2); min-width: 0; font-size: var(--text-sm); }
.cdw-index-src-line .cdw-src-mark { width: 22px; height: 22px; font-size: 10px; flex: none; }
.cdw-index-src-line .mono { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

@media (max-width: 720px) {
  .cdw-create-grid { grid-template-columns: minmax(0, 1fr); }
  .cdw-create-span { grid-column: auto; }
}

/*
 * The repository page's Code → Tests → Deploy strip. Three stages of one path, so they read left to
 * right with an arrow between them, and each ends in the link that changes it.
 */
.cdw-path {
  list-style: none;
  margin: 0 0 var(--space-4);
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-5);
}
.cdw-path-stage {
  position: relative;
  display: grid;
  align-content: start;
  gap: var(--space-1);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  min-width: 0;
}
.cdw-path-stage + .cdw-path-stage::before {
  content: "→";
  position: absolute;
  left: calc(-1 * var(--space-5));
  width: var(--space-5);
  top: 50%;
  transform: translateY(-50%);
  text-align: center;
  color: var(--text-muted);
}
.cdw-path-kicker { font-size: var(--text-xs); font-weight: 600; color: var(--text-secondary); }
.cdw-path-value { font-size: var(--text-md); font-weight: 600; color: var(--text-primary); overflow-wrap: anywhere; }
.cdw-path-note { font-size: var(--text-xs); line-height: 1.45; color: var(--text-secondary); }
.cdw-path-link { width: -moz-fit-content; width: fit-content; margin-top: var(--space-1); font-size: var(--text-sm); }

@media (max-width: 860px) {
  .cdw-path { grid-template-columns: minmax(0, 1fr); gap: var(--space-2); }
  .cdw-path-stage + .cdw-path-stage::before { content: none; }
}

/*
 * The CI tab's build view: what runs now, what each pipeline handed over, and how far each got.
 * The chain is four stages of one path, read left to right; a stage's state is its colour and its
 * mark, never colour alone.
 */
.ci-pipes-now { border-top: 0; padding-top: 0; }
.ci-pipes-running { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.ci-pipes-running > li {
  display: grid;
  gap: 2px;
  padding: var(--space-3);
  border: 1px solid var(--border-hairline);
  border-left: 3px solid var(--ok-text);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  min-width: 0;
}
.ci-pipes-running-ref { font-size: var(--text-sm); color: var(--text-primary); overflow-wrap: anywhere; }

.ci-pipes-build { display: grid; gap: var(--space-2); max-width: 640px; }
.ci-pipes-check { display: grid; grid-template-columns: 16px minmax(0, 1fr); -moz-column-gap: var(--space-2); column-gap: var(--space-2); align-items: start; font-size: var(--text-sm); color: var(--text-primary); cursor: pointer; }
.ci-pipes-check > input { width: 16px; height: 16px; margin: 2px 0 0; accent-color: var(--accent-solid); }
.ci-pipes-check-note { display: block; margin-top: 2px; font-size: var(--text-xs); color: var(--text-muted); }

.ci-pipes-artifacts { list-style: none; margin: var(--space-1) 0 0; padding: 0; display: grid; gap: 2px; }
.ci-pipes-artifact { display: flex; gap: var(--space-2); align-items: baseline; min-width: 0; font-size: var(--text-xs); }
.ci-pipes-artifact-kind {
  flex: none;
  min-width: 3.25rem;
  font-size: 10px;
  font-weight: 600;
  color: var(--text-muted);
}
.ci-pipes-artifact-ref { min-width: 0; overflow-wrap: anywhere; color: var(--text-secondary); }
.ci-pipes-artifact.is-image .ci-pipes-artifact-ref { color: var(--text-primary); }

.ci-pipes-chain { list-style: none; margin: 0 0 var(--space-2); padding: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 2px var(--space-1); }
.ci-pipes-stage { display: inline-flex; align-items: center; gap: 4px; font-size: var(--text-xs); color: var(--text-muted); }
.ci-pipes-stage + .ci-pipes-stage::before { content: "→"; color: var(--border-strong); margin-right: 2px; }
.ci-pipes-stage.is-done { color: var(--ok-text); }
.ci-pipes-stage.is-done::after { content: "✓"; }
.ci-pipes-stage.is-now { color: var(--text-primary); font-weight: 600; }
.ci-pipes-stage.is-now::after { content: "…"; }
.ci-pipes-stage.is-fail { color: var(--critical-text); font-weight: 600; }
.ci-pipes-stage.is-fail::after { content: "✕"; }
.ci-pipes-stage.is-todo { opacity: 0.7; }

/* ------------------------------------------------------------------------------------------------
 * The working cluster: the platform home after sign-in (`select-cluster/page.tsx`, `.wc-*`) and the
 * switcher in the top bar (`cluster-scope.tsx`, `.cs-*`). One choice every page opens on.
 * --------------------------------------------------------------------------------------------- */
.wc-page { width: 100%; max-width: 64rem; margin: 0 auto; display: grid; gap: var(--space-6); }
.wc-head { display: grid; gap: var(--space-2); }
.wc-title { margin: 0; font-size: clamp(1.5rem, 1.2rem + 1.2vw, 2rem); font-weight: 600; letter-spacing: -0.01em; }
.wc-lede { margin: 0; color: var(--text-secondary); font-size: var(--text-md); }

.wc-all,
.wc-card {
  width: 100%;
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-lg);
  background: var(--bg-surface);
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  box-shadow: var(--elev-rest);
  transition: border-color var(--motion-fast) var(--ease-out), box-shadow var(--motion-fast) var(--ease-out), transform var(--motion-fast) var(--ease-out);
}
.wc-all:hover,
.wc-card:hover { border-color: var(--border-strong); box-shadow: var(--elev-hover); transform: translateY(-1px); }
.wc-all:focus-visible,
.wc-card:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.wc-all.is-last,
.wc-card.is-last { border-color: var(--accent); }

/* Provisioning and the Catalogue: the platform-level tools, above the clusters they act across. */
.wc-tools { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 22rem), 1fr)); gap: var(--space-3); }
.wc-tool {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  height: 100%;
  padding: var(--space-4);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-lg);
  background: var(--bg-surface);
  color: inherit;
  text-decoration: none;
  box-shadow: var(--elev-rest);
  transition: border-color var(--motion-fast) var(--ease-out), box-shadow var(--motion-fast) var(--ease-out), transform var(--motion-fast) var(--ease-out);
}
.wc-tool:hover { border-color: var(--border-strong); box-shadow: var(--elev-hover); transform: translateY(-1px); text-decoration: none; }
.wc-tool:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.wc-tool-icon { flex: none; display: inline-grid; place-items: center; width: 2.25rem; height: 2.25rem; border-radius: var(--radius-md); background: var(--accent-soft); color: var(--accent-text); }
.wc-tool-text { display: grid; gap: 2px; min-width: 0; }
.wc-tool-text > strong { font-size: var(--text-md); font-weight: 600; }
.wc-tool-text > span { color: var(--text-secondary); font-size: var(--text-sm); }

.wc-all { display: flex; align-items: center; gap: var(--space-4); padding: var(--space-4) var(--space-5); }
.wc-all-text { display: grid; gap: 2px; flex: 1; min-width: 0; }
.wc-all-text > strong { font-size: var(--text-md); font-weight: 600; }
.wc-all-text > span { color: var(--text-secondary); font-size: var(--text-sm); }
/* Overlapping discs, one per cluster up to four: "all of them, together". */
.wc-all-stack { display: inline-flex; flex: none; padding-left: 8px; }
.wc-all-stack > i { width: 26px; height: 26px; margin-left: -8px; border-radius: var(--radius-full); border: 2px solid var(--bg-surface); background: var(--bg-sunken); box-shadow: inset 0 0 0 1px var(--border-strong); }

.wc-group { display: grid; gap: var(--space-3); }
.wc-group-title { display: flex; align-items: center; gap: var(--space-2); margin: 0; font-size: var(--text-sm); font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-secondary); }
.wc-group-count { display: inline-grid; place-items: center; min-width: 1.5rem; height: 1.25rem; padding: 0 var(--space-1); border-radius: var(--radius-full); background: var(--bg-sunken); color: var(--text-secondary); font-size: var(--text-xs); letter-spacing: 0; }
.wc-grid { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 18rem), 1fr)); gap: var(--space-3); }
.wc-card { display: grid; gap: var(--space-2); height: 100%; padding: var(--space-4); }
.wc-card-top { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); min-width: 0; }
.wc-name { font-size: var(--text-md); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wc-meta { color: var(--text-secondary); font-size: var(--text-sm); min-height: 1.25em; }
.wc-card-foot { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); flex-wrap: wrap; padding-top: var(--space-2); border-top: 1px solid var(--border-hairline); }
.wc-signal { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-sm); color: var(--text-secondary); }
.wc-signal::before { content: ""; width: 8px; height: 8px; border-radius: var(--radius-full); background: var(--text-muted); }
.wc-signal.is-ok::before { background: var(--ok); box-shadow: 0 0 0 3px var(--ok-soft); }
.wc-signal.is-warn::before { background: var(--warn); box-shadow: 0 0 0 3px var(--warn-soft); }
.wc-critical { font-size: var(--text-xs); font-weight: 600; color: var(--critical-text); background: var(--critical-soft); padding: 2px var(--space-2); border-radius: var(--radius-full); }
.wc-last { flex: none; font-size: var(--text-xs); font-weight: 500; color: var(--accent-text); background: var(--accent-soft); padding: 2px var(--space-2); border-radius: var(--radius-full); }

.cs-root { position: relative; flex: none; margin-left: var(--space-3); }
.cs-trigger {
  display: flex; align-items: center; gap: var(--space-2);
  height: var(--size-control); max-width: 15rem; padding: 0 var(--space-2) 0 var(--space-3);
  border: 1px solid var(--border-hairline); border-radius: var(--radius-md);
  background: var(--bg-surface); color: var(--text-primary); font: inherit; cursor: pointer;
  transition: background-color var(--motion-fast) var(--ease-out), border-color var(--motion-fast) var(--ease-out);
}
.cs-trigger:hover,
.cs-trigger[aria-expanded="true"] { background: var(--color-hover); border-color: var(--border-strong); }
.cs-trigger:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.cs-dot { flex: none; width: 8px; height: 8px; border-radius: var(--radius-full); background: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.cs-dot.is-all { background: transparent; box-shadow: inset 0 0 0 2px var(--accent); }
.cs-text { display: grid; min-width: 0; line-height: 1.15; text-align: left; }
.cs-name { font-size: var(--text-sm); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cs-sub { font-size: var(--text-xs); color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cs-chevron { flex: none; color: var(--text-muted); transition: transform var(--motion-base) var(--ease-out); }
.cs-trigger[aria-expanded="true"] .cs-chevron { transform: rotate(180deg); }
.cs-panel {
  position: absolute; top: calc(100% + var(--space-2)); left: 0; z-index: var(--z-overlay);
  width: 16rem; max-height: calc(100dvh - var(--size-topbar) - var(--space-4)); overflow-y: auto;
  padding: var(--space-1); border: 1px solid var(--border-hairline); border-radius: var(--radius-md);
  background: var(--bg-surface); box-shadow: var(--elev-overlay);
}
.cs-group { padding: var(--space-2) var(--space-2) var(--space-1); font-size: var(--text-xs); font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-muted); }
.cs-item {
  display: flex; align-items: center; gap: var(--space-2); width: 100%;
  height: var(--size-control-sm); padding: 0 var(--space-2);
  border: none; border-radius: var(--radius-sm); background: none; color: var(--text-primary);
  font: inherit; font-size: var(--text-sm); text-align: left; cursor: pointer;
}
.cs-item:hover,
.cs-item:focus-visible { background: var(--color-hover); outline: none; }
.cs-item[aria-checked="true"] { font-weight: 600; }
.cs-item-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cs-item-env { flex: none; font-size: var(--text-xs); color: var(--text-muted); }
.cs-check { flex: none; color: var(--accent); }
.cs-more { display: block; margin-top: var(--space-1); padding: var(--space-2); border-top: 1px solid var(--border-hairline); font-size: var(--text-sm); color: var(--accent-text); text-decoration: none; }
.cs-more:hover,
.cs-more:focus-visible { text-decoration: none; }
/* On a phone the bar has no room for the name; the dot and chevron still say there is a choice. */
@media (max-width: 640px) {
  .cs-root { margin-left: var(--space-1); }
  .cs-text { display: none; }
  .cs-panel { left: auto; right: calc(-1 * var(--space-8)); }
}
@media (prefers-reduced-motion: reduce) {
  .wc-all, .wc-card, .cs-trigger, .cs-chevron { transition: none; }
  .wc-all:hover, .wc-card:hover { transform: none; }
}

/*
 * Team activity. Four figures, the account's unowned work, then one row per person with a timeline
 * that opens under it. Holding counts are amber because they are work in somebody's name; the
 * unowned panel is the one that matters most, so it carries the severity colour.
 */
.team { display: grid; gap: var(--space-4); min-width: 0; }
.team-bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-3); }
.team-pending-item {
  display: grid;
  gap: 2px;
  align-content: start;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  min-width: 0;
}
.team-figure-value { font-size: var(--text-lg); font-weight: 700; color: var(--text-primary); }
.team-figure-label { font-size: var(--text-sm); font-weight: 600; color: var(--text-primary); }
.team-figure-note { font-size: var(--text-xs); line-height: 1.4; color: var(--text-muted); overflow-wrap: anywhere; }
.team-pending { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-3); }
.team-pending-item { color: inherit; text-decoration: none; border-left-width: 3px; }
.team-pending-item:hover { text-decoration: none; border-color: var(--border-strong); }
.team-pending-item:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.team-pending-item.is-ok { border-left-color: var(--ok-text); }
.team-pending-item.is-warning { border-left-color: var(--warn-text); }
.team-pending-item.is-critical { border-left-color: var(--critical-text); }
.team-person {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  display: grid;
  gap: 2px;
  cursor: pointer;
}
.team-person:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; border-radius: var(--radius-sm); }
.team-name { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); font-weight: 600; color: var(--text-primary); }
.team-person:hover .team-name { text-decoration: none; }
.team-row.is-open > td { border-bottom-color: transparent; }
.team-counts { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px; font-size: var(--text-xs); color: var(--text-secondary); }
.team-counts strong { color: var(--text-primary); }
.team-counts.is-warn strong { color: var(--warn-text); }
.team-detail > td { padding: 0 var(--space-4) var(--space-4); background: var(--bg-sunken); }
.team-detail-body { padding-top: var(--space-3); }
.team-detail-cols { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr); gap: var(--space-5); }
.team-detail-cols h4 { margin: 0 0 var(--space-2); font-size: var(--text-sm); color: var(--text-primary); }
.team-detail-cols h4:not(:first-child) { margin-top: var(--space-3); }
.team-sessions,
.team-events { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-1); font-size: var(--text-sm); }
.team-events { max-height: 22rem; overflow-y: auto; }
.team-events > li { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2); min-width: 0; }
.team-event-when { min-width: 6.5rem; }
.team-event-what { font-size: var(--text-xs); color: var(--text-primary); }
.team-event-detail { overflow-wrap: anywhere; }
.team-events > li.is-refused .team-event-what { color: var(--critical-text); }
.team-foot { margin: 0; }

@media (max-width: 900px) {
  .team-pending,
  .team-detail-cols { grid-template-columns: minmax(0, 1fr); }
}

/* ==========================================================================
   Layout choices: navigation, content width, and the shared preview tiles
   ========================================================================== */
/* Navigation layout "hidden": the in-flow sidebar is not drawn and the hamburger opens it as the drawer.
   The phone mode hides it with a media query; a wide window does not match that, so this does the same
   for the chosen layout. The drawer's own spacer would leave a 56px gap with nothing beside it. */
:root[data-nav="drawer"] .app-sidebar:not(.app-sidebar--drawer),
:root[data-nav="drawer"] .app-sidebar-spacer {
  display: none;
}

/* Content width. Applied to the page region's direct children, so every page follows without being
   edited; "full" is the bare default and has no rule. */
:root[data-width="centered"] .page-region > * { width: 100%; max-width: 1180px; margin-inline: auto; }
:root[data-width="wide"] .page-region > * { width: 100%; max-width: 1560px; margin-inline: auto; }

.pick-tiles { display: grid; gap: var(--space-2); padding: var(--space-1); }
.pick-tile {
  display: grid;
  gap: var(--space-1);
  min-width: 0;
  padding: 5px;
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  background: none;
  color: var(--color-text);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  text-align: left;
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--ease-out), transform var(--motion-fast) var(--ease-out);
}
.pick-tile:hover { border-color: var(--border-strong); transform: translateY(-1px); }
.pick-tile:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.pick-tile[data-selected="yes"] { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.pick-glyph { display: block; width: 100%; height: auto; }
.pick-tile-name { display: flex; align-items: center; gap: var(--space-1); padding: 0 3px 2px; min-width: 0; }
.pick-tile-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pick-tile[data-selected="yes"] .pick-tile-label { font-weight: 600; }
.user-menu-tiles-label { padding: var(--space-1) var(--space-2) 0; }
.user-menu-sub:has(.pick-tiles) { width: 268px; }
@media (prefers-reduced-motion: reduce) { .pick-tile, .theme-tile { transition: none; } .pick-tile:hover, .theme-tile:hover { transform: none; } }

/* A page header's view switcher: a label and a small row of tiles that sits beside the page actions. */
.view-switch { display: inline-flex; align-items: center; gap: var(--space-2); }
.view-switch-label { font-size: var(--text-xs); color: var(--text-muted); }
.view-switch .pick-tiles { padding: 0; gap: var(--space-1); }
.view-switch .pick-tile { width: 46px; padding: 3px; }
.view-switch .pick-tile-name { display: none; }
.view-switch .pick-tile[data-selected="yes"] { background: var(--accent-soft); }

/* The clusters board in its three forms. Cards is the bare default. */
.cc-list[data-view="grid"] { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 440px), 1fr)); align-items: start; }
.cc-list[data-view="rows"] .cc-sub,
.cc-list[data-view="rows"] .cc-bars { display: none; }
.cc-list[data-view="rows"] .cc { padding: var(--space-2) var(--space-4); gap: var(--space-2); }
/* The applications board as cards, beside its table. */
.apx-cards { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 280px), 1fr)); gap: var(--space-3); }
.apx-card {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  background: var(--color-surface);
  cursor: pointer;
}
.apx-card:hover { border-color: var(--border-strong); }
.apx-card:focus-within { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.apx-card-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); min-width: 0; }
.apx-card-facts { display: flex; flex-wrap: wrap; gap: var(--space-3); margin: 0; font-size: var(--text-xs); color: var(--text-muted); }

/* ==========================================================================
   Board widgets: ranked lists, rings, threshold bars and use-against-request meters
   (apps/portal/app/monitoring/custom/board-parts.tsx)
   ==========================================================================
   Flat fills on tokens only. Severity colour comes from one variable, `--wv-c`, set by the tone class,
   so a ring, a bar and a dot can never disagree about what colour a value is. */
.wv-ok { --wv-c: var(--severity-ok-fill); }
.wv-warning { --wv-c: var(--severity-warning-fill); }
.wv-critical { --wv-c: var(--severity-critical-fill); }
.wv-unknown { --wv-c: var(--text-muted); }
.wv-accent { --wv-c: var(--accent); }

.wv-rank { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.wv-row {
  display: grid;
  grid-template-columns: 26px minmax(0, 1fr) auto auto;
  grid-template-areas: "n id fig aside" ". bar bar bar";
  align-items: center;
  -moz-column-gap: var(--space-3);
       column-gap: var(--space-3);
  row-gap: var(--space-2);
  padding: var(--space-3) var(--space-2);
  border-radius: var(--radius-md);
  transition: background-color var(--motion-fast) var(--ease-out);
}
.wv-row:hover { background: var(--bg-hover); }
.wv-row + .wv-row { border-top: 1px solid var(--border-hairline); }
.wv-row-n { grid-area: n; font-size: var(--text-xs); color: var(--text-muted); text-align: center; }
.wv-row[data-lead="yes"] .wv-row-n {
  color: var(--on-accent, var(--bg-page));
  background: var(--accent);
  border-radius: var(--radius-full);
  width: 22px;
  height: 22px;
  line-height: 22px;
  font-weight: 700;
}
.wv-row-id { grid-area: id; display: grid; gap: 3px; min-width: 0; }
.wv-row-name { font-size: var(--text-sm); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wv-row-meta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1); min-width: 0; }
.wv-chip {
  padding: 0 var(--space-2);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-full);
  font-size: var(--text-xs);
  line-height: 18px;
  color: var(--text-secondary);
  white-space: nowrap;
}
.wv-asof { font-size: var(--text-xs); color: var(--text-muted); }
.wv-asof-warn { color: var(--severity-warning-fg); }
.wv-row-fig { grid-area: fig; display: flex; align-items: baseline; gap: var(--space-1); justify-self: end; }
.wv-row-fig b { font-family: var(--font-mono); font-size: var(--text-lg, 1.125rem); font-weight: 600; }
.wv-row-fig small { font-size: var(--text-xs); color: var(--text-muted); }
.wv-row-aside { grid-area: aside; display: flex; }
.wv-row-bar { grid-area: bar; min-width: 0; }

.wv-ring { position: relative; display: inline-grid; place-items: center; flex: 0 0 auto; }
.wv-ring svg { display: block; }
.wv-ring-track { stroke: var(--border-hairline); }
.wv-ring-fill { stroke: var(--wv-c); transition: stroke-dasharray 400ms var(--ease-out); }
.wv-ring-figure { position: absolute; font-family: var(--font-mono); font-size: var(--text-sm); font-weight: 600; }
.wv-ring-figure small { font-size: 0.6em; color: var(--text-muted); margin-left: 1px; }

.wv-tbar, .wv-rbar, .wv-meter-track, .wv-split-bar {
  position: relative;
  display: block;
  height: 8px;
  border-radius: var(--radius-full);
  background: var(--border-hairline);
  overflow: hidden;
}
.wv-tbar-fill, .wv-rbar-fill, .wv-meter-fill { display: block; height: 100%; border-radius: inherit; background: var(--wv-c, var(--accent)); transition: width 400ms var(--ease-out); }
.wv-rbar-fill { background: var(--accent); opacity: 0.85; }
.wv-tbar { height: 6px; overflow: visible; }
.wv-tbar-tick { position: absolute; top: -2px; bottom: -2px; width: 1px; background: var(--text-muted); opacity: 0.6; }

.wv-ns { display: grid; gap: var(--space-4); padding: var(--space-1) var(--space-1) 0; }
.wv-ns-top { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--space-4); align-items: center; }
.wv-ns-count { display: grid; justify-items: center; line-height: 1; }
.wv-ns-count b { font-family: var(--font-mono); font-size: 2.75rem; font-weight: 600; }
.wv-ns-count span { font-size: var(--text-xs); color: var(--text-muted); margin-top: var(--space-1); }
.wv-split { display: grid; gap: var(--space-2); min-width: 0; }
.wv-split-bar { display: flex; height: 12px; }
.wv-split-ok { background: var(--severity-ok-fill); }
.wv-split-bad { background: var(--severity-warning-fill); }
.wv-split-legend { display: flex; flex-wrap: wrap; gap: var(--space-3); font-size: var(--text-xs); color: var(--text-secondary); }
.wv-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--wv-c); margin-right: 4px; }
.wv-meter { display: grid; gap: var(--space-2); }
.wv-meter-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-2); }
.wv-meter-label { font-size: var(--text-xs); font-weight: 600; color: var(--text-muted); }
.wv-meter-used { font-family: var(--font-mono); font-size: var(--text-base, 1rem); font-weight: 600; }
.wv-meter-track { height: 10px; overflow: visible; }
.wv-meter-mark { position: absolute; top: -4px; bottom: -4px; width: 2px; background: var(--text-primary); border-radius: 1px; }
.wv-meter-foot { display: flex; justify-content: space-between; gap: var(--space-2); font-size: var(--text-xs); color: var(--text-muted); }
.wv-verdict { color: var(--text-secondary); }
.wv-verdict-warn { color: var(--severity-warning-fg); }
.wv-ns-warn { margin: 0; display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-xs); color: var(--text-secondary); }

/* The wall: full screen shows data only, so it gets a header with the place, the time and a live mark,
   and the type steps up because it is read from a distance. */
.bw-canvas[data-tv="yes"] { padding: var(--space-2) var(--space-4) var(--space-4); background: var(--bg-page); }
.wv-wall-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); flex-wrap: wrap; padding-bottom: var(--space-2); margin-bottom: var(--space-2); border-bottom: 1px solid var(--border-hairline); }
.wv-wall-id { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; min-width: 0; }
.wv-wall-title { font-size: var(--text-lg); font-weight: 700; }
.wv-wall-scope { font-size: var(--text-sm); color: var(--text-muted); }
.wv-live { display: inline-flex; align-items: center; gap: 6px; font-size: var(--text-xs); font-weight: 600; color: var(--severity-ok-fg); }
.wv-live-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--severity-ok-fill); }
.wv-wall-clock { display: flex; align-items: center; gap: var(--space-3); }
.wv-wall-time { font-family: var(--font-mono); font-size: var(--text-lg); font-weight: 600; }
.wv-wall-date { font-size: var(--text-xs); color: var(--text-muted); }
.wv-exit {
  height: var(--size-control-sm);
  padding: 0 var(--space-3);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  background: none;
  color: var(--text-secondary);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  cursor: pointer;
  align-self: center;
}
.wv-exit:hover { border-color: var(--border-strong); color: var(--text-primary); }
.bw-canvas[data-tv="yes"] .wv-row { padding-block: var(--space-4); }
.bw-canvas[data-tv="yes"] .wv-row-name { font-size: var(--text-base, 1rem); }
.bw-canvas[data-tv="yes"] .wv-row-fig b { font-size: 1.375rem; }
.bw-canvas[data-tv="yes"] .wv-ns-count b { font-size: 3.75rem; }
.bw-canvas[data-tv="yes"] .wv-meter-used { font-size: 1.25rem; }
.bw-canvas[data-tv="yes"] .wv-tbar, .bw-canvas[data-tv="yes"] .wv-rbar { height: 10px; }
@media (prefers-reduced-motion: no-preference) {
  .wv-live-dot { animation: wv-pulse 2s ease-in-out infinite; }
}
@keyframes wv-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
@media (prefers-reduced-motion: reduce) {
  .wv-ring-fill, .wv-tbar-fill, .wv-rbar-fill, .wv-meter-fill { transition: none; }
}

/* ---- board: filter bar, section rows, figures, banner and namespace table, templates ------------- */
.wv-scope { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); padding: var(--space-2) var(--space-3); border: 1px solid var(--border-hairline); border-radius: var(--radius-md); background: var(--bg-surface); }
.wv-scope-field { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-xs); }
.wv-scope-field > span { font-weight: 600; color: var(--text-muted); }
.wv-scope-clear { border: none; background: none; color: var(--accent-text); font-family: var(--font-sans); font-size: var(--text-xs); cursor: pointer; text-decoration: underline; }

/* Each section heading and the grid under it are one group; the gap between groups is what separates them. */
.wv-group { display: grid; gap: var(--space-3); }
.wv-group + .wv-group { margin-top: var(--space-4); }
.wv-cell-section { min-width: 0; }
.wv-section { display: flex; align-items: center; gap: var(--space-2); padding-bottom: var(--space-1); border-bottom: 1px solid var(--border-hairline); }
.wv-section-title { margin: 0; font-size: var(--text-lg); font-weight: 600; flex: 1; min-width: 0; }
.wv-section-input { flex: 1; min-width: 0; height: var(--size-control-sm); padding: 0 var(--space-2); border: 1px solid var(--border-hairline); border-radius: var(--radius-sm); background: var(--bg-sunken); color: var(--color-text); font-family: var(--font-sans); font-size: var(--text-base); font-weight: 600; }
.wv-section--edit { cursor: grab; }

.wv-figures { display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); gap: var(--space-3); margin: 0; }
.wv-figure { display: grid; gap: 2px; padding: var(--space-2) 0; border-top: 2px solid var(--accent); }
.wv-figure dt { font-size: var(--text-xs); font-weight: 600; color: var(--text-muted); }
.wv-figure dd { margin: 0; font-family: var(--font-mono); font-size: 1.5rem; font-weight: 600; }
.wv-figure small { font-size: var(--text-xs); color: var(--text-muted); margin-left: 4px; font-family: var(--font-sans); }


.wv-total-row td { border-top: 2px solid var(--border-strong); font-weight: 600; }

.wv-templates { display: grid; gap: var(--space-4); }
.wv-template-list { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr)); gap: var(--space-3); }
.wv-template { display: grid; gap: var(--space-2); align-content: start; padding: var(--space-4); border: 1px solid var(--border-hairline); border-radius: var(--radius-card); background: var(--bg-surface); }
.wv-template p { margin: 0; font-size: var(--text-sm); color: var(--text-secondary); }

/* ---------------------------------------------------------------- Data & storage (monitoring/storage) */
.dstore { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-4); min-width: 0; }
.dstore-pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-4); align-items: start; }
.dstore-actions { display: flex; gap: var(--space-2); }
.dstore-kind { font-weight: 600; }
.dstore-cost { margin-top: var(--space-1); font-size: var(--text-sm); color: var(--warn-text); }
.dstore-window { display: inline-grid; grid-template-columns: 5.5rem 6.5rem; gap: var(--space-2); align-items: center; }
.dstore-help { padding: var(--space-3) var(--space-4); border-top: 1px solid var(--border-hairline); font-size: var(--text-sm); }
.dstore-help summary { cursor: pointer; color: var(--text-secondary); }
.dstore-help dl { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: var(--space-2) var(--space-4); margin: var(--space-3) 0 0; }
.dstore-help dt { font-weight: 600; }
.dstore-help dd { margin: 0; color: var(--text-secondary); }
.dstore-flush { display: grid; gap: var(--space-3); }
.dstore-flush p { margin: 0; }
.dstore-radio { display: flex; gap: var(--space-2); align-items: center; }
.dstore-older { display: inline-flex; align-items: center; gap: var(--space-2); }
.dstore-older-field { display: block; width: 6rem; }
.dstore-confirm { display: grid; gap: var(--space-1); }
.dstore-space { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: var(--space-4); align-items: start; }
.dstore-defs { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: var(--space-2) var(--space-3); margin: 0; font-size: var(--text-sm); }
.dstore-defs dt { color: var(--text-secondary); }
.dstore-defs dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
.dstore-budget { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: flex-end; margin-top: var(--space-4); padding-top: var(--space-4); border-top: 1px solid var(--border-hairline); }
.dstore-budget label { display: grid; gap: var(--space-1); width: 12rem; }
.dstore-budget .muted-sm { flex: 1 1 16rem; }
.dstore-empty { padding: var(--space-4); margin: 0; }
.dstore-sweep { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }
@media (max-width: 760px) {
  .dstore-pair, .dstore-space { grid-template-columns: minmax(0, 1fr); }
}

/* ---- board: capacity rings, node heatmap, namespace treemap ------------------------------------- */
.wv-ratios { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-3); }
.wv-ratio { display: grid; justify-items: center; align-content: start; gap: 3px; padding: var(--space-3) var(--space-2); border: 1px solid var(--border-hairline); border-radius: var(--radius-md); text-align: center; }
.wv-ratio[data-tone="ok"] { --wv-c: var(--severity-ok-fill); }
.wv-ratio[data-tone="warning"] { --wv-c: var(--severity-warning-fill); }
.wv-ratio[data-tone="critical"] { --wv-c: var(--severity-critical-fill); }
.wv-ratio[data-tone="unknown"] { --wv-c: var(--text-muted); }
.wv-ratio { border-top: 3px solid var(--wv-c); }
.wv-ratio .wv-ring-figure { font-size: var(--text-lg); }
.wv-ratio-label { margin-top: var(--space-1); font-size: var(--text-xs); font-weight: 700; }
.wv-ratio-sub { font-size: var(--text-xs); color: var(--text-secondary); }
.wv-ratio-note { font-size: var(--text-xs); color: var(--text-muted); }


.wv-tree { position: relative; width: 100%; aspect-ratio: 2.4 / 1; min-height: 220px; }
.wv-tree-cell { position: absolute; padding: 2px; box-sizing: border-box; min-width: 0; }
.wv-tree-inner {
  display: grid;
  align-content: start;
  gap: 2px;
  height: 100%;
  padding: var(--space-2);
  border: 1px solid var(--wv-c);
  border-radius: var(--radius-sm);
  background: var(--wv-bg);
  overflow: hidden;
  box-sizing: border-box;
}
.wv-tree-cell[data-state="fit"] { --wv-c: var(--severity-ok-fill); --wv-bg: var(--severity-ok-bg); }
.wv-tree-cell[data-state="over"] { --wv-c: var(--severity-warning-fill); --wv-bg: var(--severity-warning-bg); }
.wv-tree-cell[data-state="idle"] { --wv-c: var(--border-strong); --wv-bg: var(--bg-sunken); }
.wv-tree-cell[data-state="none"] { --wv-c: var(--border-hairline); --wv-bg: var(--bg-hover); }
.wv-tree-name { font-size: var(--text-sm); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wv-tree-fig { font-size: var(--text-xs); color: var(--text-secondary); }
.wv-tree-fig small { color: var(--text-muted); font-size: inherit; }
@media (max-width: 560px) { .wv-ratios { grid-template-columns: 1fr; } }
.wv-name-input {
  min-width: 0;
  width: 220px;
  height: var(--size-control);
  padding: 0 var(--space-2);
  border: 1px solid transparent;
  /* A dashed underline says "you can rename this" without a separate edit button. */
  border-bottom: 1px dashed var(--border-strong);
  border-radius: var(--radius-sm);
  background: none;
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: var(--text-lg);
  font-weight: 700;
}
.wv-name-input:hover:not(:disabled) { border-color: var(--border-hairline); }
.wv-name-input:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; border-color: var(--border-strong); }

/* ==========================================================================
   Board, live register: counters, pressure columns and the horizon
   ==========================================================================
   Surfaces stay neutral. Colour appears only where it is measuring something — a strip, a fill, a
   line — so a glance at the board reads state, not decoration. */

.wv-banner { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr)); gap: var(--space-5); }
.wv-counter { display: grid; gap: var(--space-2); align-content: start; min-width: 0; }
.wv-counter-label { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-sm); color: var(--text-secondary); }
.wv-counter-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); }
.wv-counter[data-tone="ok"] .wv-counter-dot { background: var(--severity-ok-fill); }
.wv-counter[data-tone="warning"] .wv-counter-dot { background: var(--severity-warning-fill); }
.wv-counter[data-tone="critical"] .wv-counter-dot { background: var(--severity-critical-fill); }
.wv-counter-value { font-family: var(--font-mono); font-size: clamp(2.5rem, 5vw, 4rem); font-weight: 600; line-height: 1; letter-spacing: -0.03em; }
.wv-counter-sub { font-size: var(--text-xs); color: var(--text-muted); }
.wv-strip { display: flex; gap: 2px; height: 6px; border-radius: var(--radius-full); overflow: hidden; background: var(--border-hairline); }
.wv-strip > i { display: block; min-width: 3px; }
.wv-strip--cells { gap: 3px; background: none; }
.wv-strip--cells > i { border-radius: 2px; }
.bw-canvas[data-tv="yes"] .wv-counter-value { font-size: clamp(3rem, 6vw, 5.5rem); }

/* Pressure columns: a node is a vessel filling from the bottom, its colour on a continuous ramp
   (`loadColour`), with the 75% and 90% lines drawn on the glass. */
.wv-heat { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); gap: var(--space-3); align-items: end; }
.wv-col { display: grid; justify-items: center; gap: var(--space-1); min-width: 0; text-align: center; }
.wv-col-pct { font-family: var(--font-mono); font-size: var(--text-lg); font-weight: 600; color: var(--text-primary); }
.wv-col-track { position: relative; width: 100%; max-width: 64px; height: 120px; border-radius: var(--radius-sm); background: var(--bg-sunken); border: 1px solid var(--border-hairline); overflow: hidden; }
.wv-col-fill { position: absolute; left: 0; right: 0; bottom: 0; height: var(--wv-h); background: var(--wv-fill); transition: height 700ms var(--ease-out), background-color 700ms var(--ease-out); }
.wv-col-mark { position: absolute; left: 0; right: 0; height: 1px; background: var(--text-muted); opacity: 0.45; }
.wv-col-name { font-size: var(--text-sm); font-weight: 600; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wv-col-sub { font-size: var(--text-xs); color: var(--text-muted); }
.bw-canvas[data-tv="yes"] .wv-col-track { height: 180px; }

/* The horizon: lanes share one time axis; a single "now" line runs through all of them, and the
   forecast continues past it as a dashed path inside its own range. */
.hz { position: relative; display: grid; gap: var(--space-1); }
.hz-axis { position: relative; display: grid; grid-template-columns: minmax(150px, 220px) minmax(0, 1fr) minmax(110px, 150px); font-size: var(--text-xs); color: var(--text-muted); gap: var(--space-4); }
.hz-axis-track { position: relative; height: 16px; }
.hz-axis-track > span { position: absolute; top: 0; white-space: nowrap; }
.hz-axis-now { left: var(--hz-now); transform: translateX(-50%); color: var(--text-primary); font-weight: 600; }
.hz-axis-end { right: 0; }
.hz-lanes { list-style: none; margin: 0; padding: 0; display: grid; }
.hz-lane {
  display: grid;
  grid-template-columns: minmax(150px, 220px) minmax(0, 1fr) minmax(110px, 150px);
  gap: var(--space-4);
  align-items: center;
  padding: var(--space-2) 0;
  border-top: 1px solid var(--border-hairline);
}
.hz-what, .hz-next { display: grid; gap: 1px; min-width: 0; }
.hz-label { font-size: var(--text-sm); color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hz-now-value { font-family: var(--font-mono); font-size: var(--text-lg); font-weight: 600; color: var(--hz-c); }
.hz-change { font-size: var(--text-xs); color: var(--text-muted); }
.hz-track { position: relative; height: 56px; min-width: 0; }
.hz-track svg { display: block; width: 100%; height: 100%; overflow: visible; }
/* The present: one line, the same x in every lane because they share the axis. */
.hz-track::after { content: ""; position: absolute; top: -8px; bottom: -8px; left: var(--hz-now); width: 1px; background: var(--text-secondary); opacity: 0.55; }
.hz-past { fill: none; stroke: var(--hz-c); stroke-width: 2; vector-effect: non-scaling-stroke; stroke-linejoin: round; }
.hz-future { fill: none; stroke: var(--hz-f); stroke-width: 2; stroke-dasharray: 5 5; vector-effect: non-scaling-stroke; }
.hz-band { fill: var(--hz-f); opacity: 0.14; stroke: none; }
.hz-dot { position: absolute; left: var(--hz-now); width: 10px; height: 10px; margin: -5px 0 0 -5px; border-radius: 50%; background: var(--hz-c); box-shadow: 0 0 0 3px var(--bg-surface); }
.hz-next-when { font-size: var(--text-xs); color: var(--text-muted); }
.hz-next-value { font-family: var(--font-mono); font-size: var(--text-lg); font-weight: 600; color: var(--hz-f); }
.hz-next-word { font-size: var(--text-xs); color: var(--text-secondary); }
.hz-next-none { font-size: var(--text-xs); color: var(--text-muted); }
@media (prefers-reduced-motion: no-preference) {
  /* The one moving thing on the board: the present breathes, so "now" is findable from across a room. */
  .hz-dot { animation: hz-breathe 2.4s ease-in-out infinite; }
}
@keyframes hz-breathe { 0%, 100% { box-shadow: 0 0 0 3px var(--bg-surface); } 50% { box-shadow: 0 0 0 3px var(--bg-surface), 0 0 0 7px color-mix(in srgb, var(--hz-c) 30%, transparent); } }
@media (prefers-reduced-motion: reduce) { .wv-col-fill { transition: none; } }
@media (max-width: 720px) {
  .hz-lane, .hz-axis { grid-template-columns: minmax(0, 1fr); gap: var(--space-1); }
  .hz-axis { display: none; }
}

/* ---- board: view/edit bar, hint and the add-a-widget gallery ------------------------------------- */
.wv-bar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.wv-bar--edit { padding: var(--space-2) var(--space-3); border: 1px solid var(--accent); border-radius: var(--radius-md); background: var(--accent-soft); }
.wv-bar-actions { margin-left: auto; display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.wv-bar-note { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-xs); color: var(--text-muted); }
.wv-hint { margin: 0; font-size: var(--text-sm); color: var(--text-secondary); }

.wv-gallery { display: grid; gap: var(--space-5); }
.wv-gallery-group h3 { margin: 0 0 var(--space-2); font-size: var(--text-sm); font-weight: 600; color: var(--text-secondary); }
.wv-gallery-group ul { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 200px), 1fr)); gap: var(--space-2); }
.wv-gallery-item {
  display: grid;
  grid-template-columns: 64px minmax(0, 1fr);
  grid-template-rows: auto auto;
  -moz-column-gap: var(--space-3);
       column-gap: var(--space-3);
  row-gap: 2px;
  align-items: start;
  width: 100%;
  height: 100%;
  padding: var(--space-3);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  color: var(--color-text);
  font-family: var(--font-sans);
  text-align: left;
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--ease-out), background-color var(--motion-fast) var(--ease-out);
}
.wv-gallery-item:hover { border-color: var(--accent); background: var(--bg-hover); }
.wv-gallery-item:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.wv-gallery-item .pick-glyph { grid-row: 1 / 3; width: 64px; }
.wv-gallery-name { font-size: var(--text-sm); font-weight: 600; }
.wv-gallery-blurb { font-size: var(--text-xs); color: var(--text-secondary); line-height: 1.4; }
@media (prefers-reduced-motion: reduce) { .wv-gallery-item { transition: none; } }

/* ==========================================================================
   Cluster card, live register (apps/portal/app/clusters/page.tsx)
   ==========================================================================
   The card's surface stays neutral and colour moves into the things that measure: a dot on each
   problem, a pressure strip sized by the counts, availability bars that warm as the share falls. In
   every theme the colours are the theme's own severity tokens, so the card adapts with it. */
.cc { transition: border-color var(--motion-fast) var(--ease-out); }
.cc:hover { border-color: var(--border-strong); }
.cc-stat-label { text-transform: none; letter-spacing: 0; font-size: var(--text-xs); color: var(--text-muted); }

.cc-env {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 1px var(--space-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-full);
  font-size: var(--text-xs);
  color: var(--text-secondary);
}
.cc-env::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--text-muted); }
.cc-env[data-env="production"] { border-color: var(--accent); color: var(--text-primary); }
.cc-env[data-env="production"]::before { background: var(--accent); }

.cc-pressure { display: flex; gap: 2px; height: 6px; margin-top: var(--space-1); border-radius: var(--radius-full); overflow: hidden; }
.cc-pressure > i { display: block; min-width: 6px; }
.cc-pressure-critical { background: var(--severity-critical-fill); }
.cc-pressure-warning { background: var(--severity-warning-fill); }

.cc-pending { gap: 6px; margin-top: var(--space-2); }
.cc-pending-item,
.cc-pending-critical {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px var(--space-2) 3px 7px;
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-full);
  background: var(--bg-sunken);
  color: var(--text-secondary);
  font-size: var(--text-xs);
  font-weight: 400;
}
.cc-pending-item::before { content: ""; flex: 0 0 auto; width: 7px; height: 7px; border-radius: 50%; background: var(--severity-warning-fill); }
.cc-pending-critical::before { background: var(--severity-critical-fill); }
.cc-pending-count { color: var(--text-primary); font-weight: 600; }
.cc-pending-open { box-shadow: none; }
.cc-pending-open:hover,
.cc-pending-open:focus-visible { filter: none; box-shadow: none; border-color: var(--border-strong); background: var(--bg-hover); color: var(--text-primary); }

.cc-bar-label, .cc-bar-figures { font-size: var(--text-xs); }
.cc-bar-track { height: 6px; border-radius: var(--radius-full); }
.cc-bar-fill { border-radius: var(--radius-full); transition: width 600ms var(--ease-out); }
@media (prefers-reduced-motion: reduce) { .cc, .cc-bar-fill { transition: none; } }

/* ------------------------------------------------------------------------------------------------
 * The graph board (2026-10-20): App graph nodes as module plates, cables that plug into jacks, and
 * nodes that can be dragged (`topology/drag.ts`). Later in the sheet than the TraceMesh block, so
 * these replace its node rules where they overlap; the edges, particles and causal overlay are kept.
 * --------------------------------------------------------------------------------------------- */
.tm-plate {
  fill: color-mix(in srgb, var(--band) 7%, var(--tm-body));
  stroke: color-mix(in srgb, var(--band) 55%, transparent);
  stroke-width: 1.5;
  transition: stroke var(--motion-fast) var(--ease-out), fill var(--motion-fast) var(--ease-out);
}
.tm-node[data-band="unknown"] .tm-plate {
  fill: var(--tm-body);
  stroke: color-mix(in srgb, var(--tm-unknown) 45%, transparent);
  stroke-dasharray: 5 5;
}
.tm-node:hover .tm-plate { stroke: var(--band); stroke-width: 2.25; }
.tm-node:focus-visible .tm-plate { stroke: var(--color-focus-ring); stroke-width: 3; }
.tm-node:focus-visible .tm-node-shape { stroke: var(--band); stroke-width: 2.5; }

/* The mark sits inside the plate now; a lighter hand than when it was the whole node. */
.tm-node .tm-node-shape { stroke-width: 2; }
.tm-node:hover .tm-node-shape { stroke-width: 2.5; }

.tm-rail-track { fill: color-mix(in srgb, var(--color-text) 10%, transparent); }
.tm-node[data-band="unknown"] .tm-rail-track {
  fill: none;
  stroke: color-mix(in srgb, var(--tm-unknown) 55%, transparent);
  stroke-width: 1;
  stroke-dasharray: 3 3;
}
.tm-rail-fill { fill: var(--band); }

/* Inside a plate there is nothing under a name to halo it against, so the halo stroke goes. */
.tm-node .tm-node-name {
  text-anchor: start;
  font-size: 19px;
  font-weight: 600;
  stroke: none;
}
.tm-node .tm-node-figure {
  text-anchor: start;
  font-size: 15px;
  stroke: none;
}

.tm-node .tm-node-ring { fill: none; stroke: var(--color-text); stroke-width: 2; opacity: 0.9; }
/* The blamed plate: a dashed outline that marches, rather than a ring that turns. */
.tm-node .tm-node-blame {
  fill: none;
  stroke: var(--tm-critical);
  stroke-width: 2.5;
  stroke-dasharray: 8 8;
  animation: gb-march 1.4s linear infinite;
  transform: none;
}
@keyframes gb-march { to { stroke-dashoffset: -16; } }
.tm-node .tm-node-glow { transform-origin: center; }

/* The jack where a cable leaves its plate: a socket in the wire's own tone. */
.tm-jack { fill: var(--tm-ground); stroke: var(--tm-wire); stroke-width: 2; }
.tm-edge[data-tone="degraded"] .tm-jack { stroke: var(--tm-warn); }
.tm-edge[data-tone="critical"] .tm-jack { stroke: var(--tm-critical); }
.tm-edge[data-unmeasured="yes"] .tm-jack { stroke: color-mix(in srgb, var(--color-text) 22%, transparent); }

/* Dragging: the plate lifts — a heavier outline and a soft shadow — and the board stops reacting to hover. */
.tm-node[data-drag-node] { cursor: grab; }
.tm-node[data-dragging="yes"] { cursor: grabbing; }
.tm-node[data-dragging="yes"] .tm-plate {
  stroke: var(--band);
  stroke-width: 2.5;
  filter: drop-shadow(0 10px 18px color-mix(in srgb, #000 35%, transparent));
}
.tm-canvas[data-dragging="yes"] { cursor: grabbing; }
.tm-canvas[data-dragging="yes"] .tm-edge { transition: none; }
/* A node placed by hand carries a small pin in its corner. */
.tm-pin { fill: var(--accent); }

/* "N moved by hand · Reset layout" — floats in the board's top-right corner while there is anything to reset. */
.gb-arrange {
  position: absolute;
  bottom: var(--space-3);
  left: 50%;
  transform: translateX(-50%);
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-1) var(--space-1) var(--space-3);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-full);
  background: var(--bg-surface);
  box-shadow: var(--elev-rest);
  font-size: var(--text-xs);
  color: var(--text-secondary);
}
.gb-arrange::before { content: ""; width: 7px; height: 7px; border-radius: var(--radius-full); background: var(--accent); }
.gb-arrange-reset {
  height: 24px;
  padding: 0 var(--space-3);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-full);
  background: var(--bg-sunken);
  color: var(--text-primary);
  font: inherit;
  font-weight: 500;
  cursor: pointer;
}
.gb-arrange-reset:hover { border-color: var(--border-strong); }
.gb-arrange-reset:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  .tm-node .tm-node-blame { animation: none; }
  .tm-plate { transition: none; }
}

/* Dependencies chain: a state rail on each box, ports where its lines plug in, and drag (`drag.ts`). */
.chain-node-rail { pointer-events: none; }
.chain-port { fill: var(--map-ground); stroke-width: 1.5; pointer-events: none; }
.chain-node[data-drag-node] { cursor: grab; }
.chain-node[data-dragging="yes"] { cursor: grabbing; }
.chain-node[data-dragging="yes"] .chain-node-box {
  stroke-width: 2.4;
  fill: rgba(255, 255, 255, 0.08);
  filter: drop-shadow(0 10px 18px rgba(0, 0, 0, 0.55));
}
.chain-node[data-moved="yes"] .chain-node-box { stroke-dasharray: none; }
.chain-canvas[data-dragging="yes"] { cursor: grabbing; }
/*
 * The chain sits inside the board's furniture rather than under it: room below for the legend and to
 * the right for the zoom controls, which are absolutely placed over the board. A short chain is centred
 * at its natural size (the canvas carries a max-width of its own drawing).
 */
.chain-view { padding: var(--space-2) 56px 64px var(--space-2); }
.chain-view .chain-canvas { margin-inline: auto; }

/* ==========================================================================
   The assistant's reply, chat style (apps/portal/app/sre/chat.tsx)
   ==========================================================================
   The question is a bubble on the right; the reply is prose on the left beside a small mark, with no
   card around it. Sources sit under the answer as numbered chips; the working is folded below. */
.sre-reply { display: grid; grid-template-columns: 28px minmax(0, 1fr); gap: var(--space-3); align-items: start; min-width: 0; }
.sre-avatar {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent-text);
}
.sre-avatar svg { width: 16px; height: 16px; }
.sre-reply-body { display: grid; gap: var(--space-3); min-width: 0; padding-top: 3px; }
.sre-reply-fail { display: flex; gap: var(--space-2); align-items: flex-start; margin: 0; font-size: var(--text-sm); line-height: 1.55; color: var(--text-secondary); }
.sre-reply-fail svg { flex: 0 0 auto; margin-top: 3px; color: var(--severity-warning-fill); }
.sre-reply-fail strong { color: var(--text-primary); }

.sre-sources { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1); }
.sre-sources-label { font-size: var(--text-xs); color: var(--text-muted); margin-right: var(--space-1); }
.sre-source {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 100%;
  padding: 2px var(--space-2) 2px 3px;
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-full);
  background: var(--bg-surface);
  color: var(--text-secondary);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sre-source:hover { border-color: var(--accent); color: var(--text-primary); }
.sre-source:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
.sre-source-n { display: inline-grid; place-items: center; min-width: 18px; height: 18px; border-radius: 50%; background: var(--accent-soft); color: var(--accent-text); font-size: 11px; font-weight: 600; }

.sre-work, .sre-steps { min-width: 0; }
.sre-work > summary, .sre-steps > summary {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--text-secondary);
  cursor: pointer;
  list-style: none;
}
.sre-work > summary::-webkit-details-marker, .sre-steps > summary::-webkit-details-marker { display: none; }
.sre-work > summary::before, .sre-steps > summary::before {
  content: "";
  width: 6px;
  height: 6px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg);
  transition: transform var(--motion-fast) var(--ease-out);
}
.sre-work[open] > summary::before, .sre-steps[open] > summary::before { transform: rotate(45deg); }
.sre-work > summary:hover, .sre-steps > summary:hover { color: var(--text-primary); }
.sre-work > summary:focus-visible, .sre-steps > summary:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; border-radius: var(--radius-sm); }
.sre-work-meta { font-size: var(--text-xs); color: var(--text-muted); }
.sre-work-body { display: grid; gap: var(--space-3); margin-top: var(--space-2); padding-left: var(--space-3); border-left: 2px solid var(--border-hairline); min-width: 0; }
.sre-steps { font-size: var(--text-sm); }
.sre-steps > summary { font-size: var(--text-xs); }
.sre-steps[open] > :not(summary) { margin-top: var(--space-2); }
.sre-reply-foot { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); }
.sre-reply-foot .sre-provenance { margin: 0; }
@media (prefers-reduced-motion: reduce) { .sre-work > summary::before, .sre-steps > summary::before { transition: none; } }
.sre-block.is-focused { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-md); }

/*
 * The timeline, made easy to grab (2026-10-20). It is the one control on the App graph a reader drags,
 * and it was a 4px rail with a 14px handle on the window's bottom edge. Now: a 32px hit area, a 6px
 * rail, an 18px handle that turns amber while replaying, and markers with a real target.
 */
.tm-timeline { padding: var(--space-3) var(--space-5) var(--space-4); gap: var(--space-3); }
.tm-tl-track { padding-block-start: 1.5rem; }
.tm-tl-ticks { block-size: 1.25rem; }
.tm-tl-tick { font-size: 0.6875rem; }
.tm-tl-markers { inset-block-start: calc(1.5rem + 2px); block-size: 1.5rem; }
.tm-tl-marker { font-size: 0.8125rem; padding: 4px 5px; }
.tm-tl-range { block-size: 2rem; }
.tm-tl-range::-webkit-slider-runnable-track { block-size: 6px; }
.tm-tl-range::-moz-range-track { block-size: 6px; }
.tm-tl-range::-webkit-slider-thumb {
  inline-size: 18px;
  block-size: 18px;
  margin-block-start: -6px;
  border-width: 3px;
  box-shadow: 0 0 0 1px var(--color-border-strong), 0 2px 6px color-mix(in srgb, #000 25%, transparent);
  cursor: grab;
}
.tm-tl-range::-moz-range-thumb {
  inline-size: 18px;
  block-size: 18px;
  border-width: 3px;
  box-shadow: 0 0 0 1px var(--color-border-strong), 0 2px 6px color-mix(in srgb, #000 25%, transparent);
  cursor: grab;
}
.tm-tl-range:active::-webkit-slider-thumb { cursor: grabbing; }
.tm-timeline[data-live="no"] .tm-tl-range::-webkit-slider-thumb { background: var(--warn); }
.tm-timeline[data-live="no"] .tm-tl-range::-moz-range-thumb { background: var(--warn); }
/* The timeline as one row: the time, the track, the actions. Wraps to two rows on a narrow window. */
.tm-tl-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: end;
  gap: var(--space-4);
}
.tm-tl-row > .tm-tl-when { align-self: end; margin-block-end: 4px; }
.tm-tl-row > .tm-tl-controls { align-self: end; margin-block-end: 2px; flex-wrap: nowrap; }
.tm-tl-row .tm-tl-hint { white-space: nowrap; }
@media (max-width: 900px) {
  .tm-tl-row { grid-template-columns: auto auto; }
  .tm-tl-row > .tm-tl-track { grid-column: 1 / -1; grid-row: 2; }
  .tm-tl-row > .tm-tl-controls { justify-self: end; }
}
/* On a laptop-width board the captions are what wrapped the toolbar onto a second row; each group still
   carries its name for assistive tech (`aria-label`), and the buttons say what they are. */
@media (max-width: 1600px) {
  .tm-bar .tm-bar-label { display: none; }
}

/* ---------------------------------------------------------------------------------------------
 * The conversation column.
 *
 * The dock opens nearly as wide as the window, and every turn used to span all of it: the answer
 * held to its 68ch measure on the left, the question bubble pinned to the far right, and a band of
 * empty sheet between them wider than either. Read on a laptop that is two things a screen apart
 * that belong to one exchange. Every chat a customer already uses sets the transcript, the
 * suggestions and the box in one centred column, so the question sits over the answer it asked for
 * and the eye travels down, not across. The scroller stays full width so its bar keeps to the edge.
 * ------------------------------------------------------------------------------------------- */

.sre { --sre-col: 46rem; }
.sre-thread-items,
.sre > .sre-examples,
.sre > .sre-composer,
.sre > .sre-foot {
  width: 100%;
  max-width: var(--sre-col);
  margin-inline: auto;
}

/* Inside the column the column is the measure: the answer fills it, so its right edge lines up with
   the question's and the composer's instead of stopping short of both. */
.sre-reply .sre-answer p,
.sre-reply .sre-answer-list { max-width: none; }

/* Two suggestions to a row, each one line. Wrapped, a long one took a row to itself and the strip's
   cap cut the rest off mid-chip; in a grid two rows always fit the cap whole and the others scroll. */
.sre > .sre-examples {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  /* A clipped chip has no minimum height of its own, so without this the rows shrink to fit the cap
     and every chip is cut through its text instead of the strip scrolling. */
  grid-auto-rows: max-content;
}
@media (max-width: 640px) {
  .sre > .sre-examples { grid-template-columns: minmax(0, 1fr); }
}

/* Sentence case, like every other label the product writes. Capitals shouted the two section names
   louder than the answer they divide. */
.sre-part-title {
  font-size: var(--text-sm);
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-primary);
}
.chatdock-readout-label { letter-spacing: 0; text-transform: none; }

/* ------------------------------------------------------------------------------------------------
 * The SRE console's Recents rail (`sre/chat.tsx` RecentRail): New chat and the person's saved
 * conversations down the left, the thread beside them — the layout people know from any chat
 * assistant. The console itself (`.sre`) is unchanged inside it.
 * --------------------------------------------------------------------------------------------- */
.sre-shell {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr);
  gap: var(--space-4);
  min-height: 0;
  min-width: 0;
}
.sre-shell > .sre { min-height: 0; min-width: 0; }
.sre-recents {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  inline-size: 15rem;
  min-height: 0;
  padding-inline-end: var(--space-3);
  border-inline-end: 1px solid var(--border-hairline);
}
.sre-recents[data-open="no"] { inline-size: auto; }
.sre-recents-top { display: flex; align-items: center; gap: var(--space-1); }
.sre-recents[data-open="no"] .sre-recents-top { flex-direction: column; }
.sre-recents-toggle,
.sre-recents-new {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: var(--size-control-sm);
  padding: 0 var(--space-2);
  border: none;
  border-radius: var(--radius-md);
  background: none;
  color: var(--text-secondary);
  font: inherit;
  font-size: var(--text-sm);
  cursor: pointer;
}
.sre-recents-new { flex: 1; color: var(--text-primary); font-weight: 500; }
.sre-recents[data-open="no"] .sre-recents-new { flex: none; }
.sre-recents-toggle:hover,
.sre-recents-new:hover:not(:disabled) { background: var(--color-hover); color: var(--text-primary); }
.sre-recents-toggle:focus-visible,
.sre-recents-new:focus-visible,
.sre-recent-open:focus-visible,
.sre-recent-delete:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
.sre-recents-new:disabled { opacity: 0.5; cursor: default; }
.sre-recents-label {
  margin: var(--space-3) 0 var(--space-1);
  padding-inline: var(--space-2);
  font-size: var(--text-xs);
  font-weight: 500;
  color: var(--text-muted);
}
.sre-recents-note { margin: 0; padding: var(--space-1) var(--space-2); font-size: var(--text-xs); color: var(--text-muted); }
.sre-recents-list { list-style: none; margin: 0; padding: 0; overflow-y: auto; min-height: 0; display: grid; gap: 1px; align-content: start; }
.sre-recent { position: relative; display: flex; align-items: stretch; border-radius: var(--radius-md); }
.sre-recent:hover,
.sre-recent:focus-within { background: var(--color-hover); }
.sre-recent[data-active="yes"] { background: var(--bg-sunken); box-shadow: inset 2px 0 0 var(--accent); }
.sre-recent-open {
  flex: 1;
  min-width: 0;
  display: grid;
  gap: 1px;
  padding: var(--space-2) var(--space-6) var(--space-2) var(--space-2);
  border: none;
  background: none;
  color: var(--text-primary);
  font: inherit;
  text-align: left;
  cursor: pointer;
  border-radius: var(--radius-md);
}
.sre-recent-open:disabled { cursor: default; }
.sre-recent-title { font-size: var(--text-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sre-recent[data-active="yes"] .sre-recent-title { font-weight: 600; }
.sre-recent-meta { font-size: var(--text-xs); color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sre-recent-delete {
  position: absolute;
  inset-block: 0;
  inset-inline-end: var(--space-1);
  margin-block: auto;
  display: grid;
  place-items: center;
  inline-size: 24px;
  block-size: 24px;
  border: none;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--text-muted);
  cursor: pointer;
  opacity: 0;
}
.sre-recent:hover .sre-recent-delete,
.sre-recent:focus-within .sre-recent-delete { opacity: 1; }
.sre-recent-delete:hover { color: var(--critical-text); background: var(--critical-soft); }
.sre-recent-delete svg { inline-size: 14px; block-size: 14px; }
/* A general answer's quiet note, and a reopened answer's "measured then, not now". */
.sre-provenance--general { color: var(--text-muted); }
.sre-saved-note { margin: 0 0 var(--space-1); font-size: var(--text-xs); color: var(--warn-text); }
@media (max-width: 720px) {
  .sre-shell { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); }
  .sre-recents { inline-size: auto; border-inline-end: none; border-block-end: 1px solid var(--border-hairline); padding: 0 0 var(--space-2); max-block-size: 40vh; }
  .sre-recents[data-open="no"] .sre-recents-top { flex-direction: row; }
}

/* ------------------------------------------------- precedents: what was done during similar incidents */
/* Who wrote the cause above it: a person, a verified fix, or the model at the time. */
.sim-cause-by { display: block; margin-top: 2px; font-size: var(--text-xs); color: var(--color-text-muted); }
/* One line per change made during that incident, with its verification verdict. */
.sim-fixes { list-style: none; margin: var(--space-2) 0 0; padding: 0; display: grid; gap: 2px; font-size: var(--text-sm); min-width: 0; }
.sim-fixes li { min-width: 0; overflow-wrap: anywhere; }
.sim-verdict { display: inline-block; padding: 0 6px; border-radius: var(--radius-full); font-size: var(--text-xs); background: var(--bg-surface); color: var(--color-text-muted); border: 1px solid var(--border-hairline); }
.sim-verdict--recovered { background: var(--severity-ok-bg); color: var(--severity-ok-fg); border-color: transparent; }
.sim-verdict--worsened { background: var(--severity-critical-bg); color: var(--severity-critical-fg); border-color: transparent; }
/* The record across all of them, above the rows: what to try, and what not to. */
.sim-outcomes { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--space-2); }
.sim-outcome { display: inline-flex; gap: var(--space-2); align-items: baseline; padding: 4px 10px; border: 1px solid var(--border-hairline); border-radius: var(--radius-md); font-size: var(--text-sm); background: var(--bg-sunken); }
.sim-outcome.is-good { border-left: 3px solid var(--severity-ok-fg); }
.sim-outcome.is-bad { border-left: 3px solid var(--severity-critical-fg); }

/* ------------------------------------------------------------------------------------------------
 * Likely cause (`alerts/likely-cause.tsx`): the incident's dependency ranking, as the path from the
 * application down to the candidate. Each hop's dot carries its health — the same four verdicts the
 * delivery chain uses — and the link between hops is the relation, in words, so the walk reads as a
 * sentence top to bottom. Factors are signed: what argues for the candidate and what against it.
 * --------------------------------------------------------------------------------------------- */
.lc { display: grid; gap: var(--space-2); min-width: 0; padding: var(--space-3); border: 1px solid var(--border-hairline); border-radius: var(--radius-md); background: var(--bg-sunken); }
.lc--none { gap: var(--space-1); }
.lc-none { margin: 0; color: var(--text-secondary); font-size: var(--text-sm); }
.lc-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-1) var(--space-2); min-width: 0; }
.lc-eyebrow { font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted); }
.lc-name { font-size: var(--text-md); overflow-wrap: anywhere; min-width: 0; }
.lc-meta { font-size: var(--text-xs); color: var(--text-muted); }
.lc-why { margin: 0; font-size: var(--text-sm); overflow-wrap: anywhere; }
.lc-path { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; min-width: 0; }
.lc-hop { display: grid; gap: 1px; padding-left: var(--space-3); border-left: 2px solid var(--border-hairline); min-width: 0; }
.lc-hop:last-child { border-left-color: var(--accent); }
.lc-hop-link { font-size: var(--text-xs); color: var(--text-muted); font-style: italic; }
.lc-hop-name { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-sm); min-width: 0; overflow-wrap: anywhere; }
.lc-hop-ev { font-size: var(--text-xs); color: var(--text-secondary); overflow-wrap: anywhere; }
.lc-dot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--status-unknown-fill); }
.lc-hop[data-health="healthy"] .lc-dot { background: var(--severity-ok-fill); }
.lc-hop[data-health="degraded"] .lc-dot { background: var(--severity-warning-fill); }
.lc-hop[data-health="failed"] .lc-dot { background: var(--severity-critical-fill); }
.lc-factors { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
.lc-factor { display: grid; grid-template-columns: 5.5rem 3.2rem minmax(0, 1fr); gap: var(--space-2); align-items: baseline; font-size: var(--text-xs); }
.lc-factor-rule { color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.04em; }
.lc-factor-pts { text-align: right; font-weight: 600; }
.lc-factor.is-for .lc-factor-pts { color: var(--severity-ok-fg); }
.lc-factor.is-against .lc-factor-pts { color: var(--severity-critical-fg); }
.lc-factor-text { color: var(--text-secondary); overflow-wrap: anywhere; }
.lc-note { margin: 0; font-size: var(--text-xs); color: var(--text-secondary); }
.lc-more > summary { cursor: pointer; font-size: var(--text-sm); color: var(--accent-text); }
.lc-more > summary:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; border-radius: 2px; }
.lc-others { margin: var(--space-2) 0 0; padding: 0; list-style: none; display: grid; gap: var(--space-2); }
.lc-other { display: grid; gap: var(--space-1); padding: var(--space-2); border: 1px solid var(--border-hairline); border-radius: var(--radius-sm); background: var(--bg-surface); min-width: 0; }
.lc-other-head { display: flex; flex-wrap: wrap; justify-content: space-between; gap: var(--space-2); font-size: var(--text-sm); min-width: 0; overflow-wrap: anywhere; }
.lc-blind { margin: var(--space-2) 0 0; padding-left: var(--space-4); font-size: var(--text-xs); color: var(--text-secondary); display: grid; gap: 2px; }
@media (max-width: 520px) {
  .lc-factor { grid-template-columns: minmax(0, 1fr) auto; }
  .lc-factor-text { grid-column: 1 / -1; }
}

/* ------------------------------------------------------- Replay set (ai/fine-tuning/replay.tsx) */
.rpl { display: grid; gap: var(--space-3); padding-bottom: var(--space-3); min-width: 0; }
.rpl-head { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-3); align-items: center; padding: var(--space-3) var(--space-4) 0; }
.rpl-head .aim-sub { flex: 1 1 22rem; min-width: 0; }
.rpl > .notice { margin: 0 var(--space-4); }
.rpl-models { display: grid; gap: var(--space-2); padding: 0 var(--space-4); }
.rpl-model { display: flex; justify-content: space-between; align-items: center; gap: var(--space-3); padding: var(--space-2) var(--space-3); border: 1px solid var(--border-hairline); border-radius: var(--radius-md); min-width: 0; }
.rpl-title { margin: var(--space-2) var(--space-4) 0; font-size: var(--text-sm); font-weight: 600; }
.rpl-pad { padding: 0 var(--space-4); margin: 0; }
.rpl-score { font-weight: 600; }
.rpl-cases { padding: 0 var(--space-4); }
.rpl-cases > summary { cursor: pointer; font-size: var(--text-sm); color: var(--text-secondary); }
.rpl-case-list { list-style: none; margin: var(--space-2) 0 0; padding: 0; display: grid; gap: var(--space-2); }
.rpl-case { padding: var(--space-2) var(--space-3); border: 1px solid var(--border-hairline); border-radius: var(--radius-md); min-width: 0; }
.rpl-case.is-excluded { opacity: 0.7; }
.rpl-case-head { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }
.rpl-evidence { margin: var(--space-2) 0 0; padding: var(--space-2); max-height: 16rem; overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain; white-space: pre-wrap; overflow-wrap: anywhere; font-size: var(--text-xs); background: var(--bg-sunken); border-radius: var(--radius-sm); }
.rpl-results { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.rpl-results th, .rpl-results td { text-align: left; padding: var(--space-2); border-bottom: 1px solid var(--border-hairline); vertical-align: top; }
.rpl-reason { display: grid; gap: var(--space-1); }
@media (max-width: 640px) {
  .rpl-model { flex-direction: column; align-items: flex-start; }
}

/* ------------------------------------------------------------------------------------------------
 * The floating bastion shell as a window (`shell-route.tsx`): macOS-style lights, a minimise that
 * shrinks it into a tab in the corner (the session stays connected), and a zoom that grows it to the
 * page. The lights keep macOS's own colours — they are a convention people recognise by colour.
 * --------------------------------------------------------------------------------------------- */
.shell-float {
  transform-origin: 100% 100%;
  transition:
    transform 340ms cubic-bezier(0.2, 0.8, 0.2, 1),
    opacity 260ms var(--ease-out),
    width 260ms var(--ease-out),
    height 260ms var(--ease-out),
    visibility 0s linear 0s;
}
/* Down into the corner where the tab appears: smaller, lower, gone — then out of the way entirely. */
.shell-float[data-minimized="yes"] {
  transform: translate(-24px, 8px) scale(0.06);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition:
    transform 340ms cubic-bezier(0.55, 0, 0.75, 0.3),
    opacity 300ms var(--ease-out),
    visibility 0s linear 340ms;
}
.shell-float[data-zoomed="yes"] {
  width: calc(100vw - 2 * var(--space-4));
  height: calc(100vh - var(--size-topbar) - 2 * var(--space-4));
}

/*
 * The shell is dark in every theme — the window, its header and its foot as well as the screen.
 *
 * A terminal is the one surface here that people already know in one colour, and the light screen
 * it had (white ground, its own sixteen-colour ramp) was asked to go: output from `oc` is written for
 * a dark ground, and a light window around a black screen read as two things. So the window carries
 * the dark theme's tokens itself.
 *
 * The block is the dark theme's own, re-declared on this element, and it has to be the whole of it:
 * the `--color-*`, `--severity-*` and `--status-*` aliases are `var()` references that resolve where
 * they are declared, so re-declaring only `--text-primary` here would leave every alias inheriting
 * the light value from the root. `bastion/theme.test.ts` fails if this block and
 * `:root[data-theme="dark"]` differ by a single property. `--overlay-surface` is declared on bare
 * `:root` from `--bg-surface`, so it is resolved again here for the same reason.
 */
.bastion-dark {
  --bg-page: #111315;
  --bg-surface: #22262B;
  --bg-sunken: #1A1D21;
  --bg-hover: #2B3036;
  --border-hairline: #383E45;
  --border-strong: #4B525B;
  --text-primary: #F5F7FA;
  --text-secondary: #AEB5BE;
  --text-muted: #9097A1;
  --accent: #F5F7FA;
  --accent-hover: #FFFFFF;
  --accent-soft: #2B3036;
  --accent-text: #F5F7FA;
  --accent-solid: #F5F7FA;
  --on-accent: #111315;
  --focus-ring: #8E9093;
  --scrim: rgba(10, 11, 13, 0.62);
  --grid: rgba(245, 247, 250, 0.05);
  --chart-fill: rgba(245, 247, 250, 0.10);
  --brand: #EE0000;
  --brand-deep: #A30000;
  --elev-rest: none;
  --elev-hover: none;
  --elev-overlay: 0 8px 32px rgba(0, 0, 0, 0.6);
  --ok: #3FB950;
  --ok-soft: rgba(63, 185, 80, 0.28);
  --ok-text: #62DF70;
  --info: #58A6FF;
  --info-soft: rgba(88, 166, 255, 0.28);
  --info-text: #89D0FF;
  --warn: #D29922;
  --warn-soft: rgba(210, 153, 34, 0.28);
  --warn-text: #F1C14F;
  --critical: #F85149;
  --critical-soft: rgba(248, 81, 73, 0.28);
  --critical-text: #FFA198;
  /* v4: hover is a grayscale response — brighter text, stronger border, raised fill. The old
     values pointed all three at --critical, which washed every hover in the product red: a chrome
     hue (blacklist 1), and one that made a *real* critical state harder to spot, because the eye
     had been taught that red means "the pointer is here". */
  --hover-accent: var(--border-strong);
  --hover-accent-text: var(--text-primary);
  --hover-accent-soft: var(--bg-hover);
  --color-background: var(--bg-page);
  --color-surface: var(--bg-surface);
  --color-surface-raised: var(--bg-surface);
  --color-sunken: var(--bg-sunken);
  --color-hover: var(--bg-hover);
  --color-border: var(--border-hairline);
  --color-border-strong: var(--border-strong);
  --color-text: var(--text-primary);
  --color-text-muted: var(--text-secondary);
  --color-text-subtle: var(--text-muted);
  --color-primary: var(--accent);
  --color-primary-hover: var(--accent-hover);
  --color-primary-soft: var(--accent-soft);
  --color-primary-text: var(--accent-text);
  --color-primary-solid: var(--accent-solid);
  --color-on-primary: var(--on-accent);
  --color-focus-ring: var(--focus-ring);
  --color-scrim: var(--scrim);
  --color-grid: var(--grid);
  --color-chart-fill: var(--chart-fill);
  --color-brand: var(--brand);
  --color-brand-deep: var(--brand-deep);
  --color-accent2: var(--ok);
  --elev-card: var(--elev-rest);
  --elev-dropdown: var(--elev-overlay);
  --elev-modal: var(--elev-overlay);
  --severity-ok-fg: var(--ok-text);
  --severity-ok-bg: var(--ok-soft);
  --severity-ok-on: var(--ok-text);
  --severity-ok-fill: var(--ok);
  --severity-info-fg: var(--info-text);
  --severity-info-bg: var(--info-soft);
  --severity-info-on: var(--info-text);
  --severity-info-fill: var(--info);
  --severity-warning-fg: var(--warn-text);
  --severity-warning-bg: var(--warn-soft);
  --severity-warning-on: var(--warn-text);
  --severity-warning-fill: var(--warn);
  --severity-critical-fg: var(--critical-text);
  --severity-critical-bg: var(--critical-soft);
  --severity-critical-on: var(--critical-text);
  --severity-critical-fill: var(--critical);
  --status-ready-fg: var(--ok-text);
  --status-ready-bg: var(--ok-soft);
  --status-ready-fill: var(--ok);
  --status-succeeded-fg: var(--ok-text);
  --status-succeeded-bg: var(--ok-soft);
  --status-succeeded-fill: var(--ok);
  --status-progressing-fg: var(--info-text);
  --status-progressing-bg: var(--info-soft);
  --status-progressing-fill: var(--info);
  --status-running-fg: var(--info-text);
  --status-running-bg: var(--info-soft);
  --status-running-fill: var(--info);
  --status-pending-fg: var(--info-text);
  --status-pending-bg: var(--info-soft);
  --status-pending-fill: var(--info);
  --status-degraded-fg: var(--warn-text);
  --status-degraded-bg: var(--warn-soft);
  --status-degraded-fill: var(--warn);
  --status-failed-fg: var(--critical-text);
  --status-failed-bg: var(--critical-soft);
  --status-failed-fill: var(--critical);
  --status-unknown-fg: var(--text-muted);
  --status-unknown-bg: var(--bg-sunken);
  --status-unknown-fill: var(--text-muted);
  --chart-1: #58A6FF;
  --chart-2: #3FB950;
  --chart-3: #BC8CFF;
  --chart-4: #39C5CF;
  --chart-5: #D29922;
  --chart-6: #FF7B72;
  --overlay-surface: var(--bg-surface);
  color-scheme: dark;
  color: var(--text-primary);
}

/*
 * A docked shell paints its own window.
 *
 * The tokens above make its text, borders and buttons the dark theme's — light text — and the
 * floating window paints the dark ground under them with `.shell-float`. Docked beside a fix list,
 * an answer or the foresight tools, nothing painted the ground: in the light theme the header read
 * as near-white words on a white dialog, "Bastion shell" and Close all but invisible. So wherever a
 * terminal sits that is not the floating window, it is its own dark panel.
 */
.bastion.bastion-dark:not(.shell-float) {
  background: var(--overlay-surface);
  color: var(--text-primary);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-lg);
  padding: var(--space-3);
}

/*
 * Docked: the shell takes the right of the screen and what it was opened from moves to the left.
 *
 * The floating window sat in the bottom-right corner over whatever dialog sent it a command — the
 * incident's fix steps, the root cause — so the step being followed was underneath the terminal
 * running it. Now an open shell is a full-height column on the right and `shell-route.tsx` stamps
 * `data-shell="docked"` on the root (the rail's own pattern, `data-rail="open"`). Two things give way:
 * a dialog re-centres in what is left (`--modal-clear-end`, read by `Modal`'s overlay — its padding
 * is inline, trap 30, so a property is the only way in), and the page's scrolling region pads its
 * right edge so nothing on the page is hidden under the column either.
 *
 * Only where there is room for both. Below the breakpoint the shell keeps its corner and nothing is
 * moved: a 400px dialog beside a 400px terminal is two things too narrow to read.
 */
:root {
  --shell-dock-width: min(720px, 46vw);
}
@media (min-width: 1100px) {
  .shell-float[data-zoomed="no"] {
    top: var(--space-4);
    height: auto;
    width: var(--shell-dock-width);
  }
  :root[data-shell="docked"] {
    --modal-clear-end: calc(var(--shell-dock-width) + 2 * var(--space-4));
  }
  :root[data-shell="docked"] .page-region {
    padding-right: calc(var(--shell-dock-width) + 2 * var(--space-4));
  }
}

.win-lights { display: inline-flex; align-items: center; gap: 8px; margin-inline-end: var(--space-1); }
.win-light {
  display: grid;
  place-items: center;
  inline-size: 13px;
  block-size: 13px;
  padding: 0;
  border: none;
  border-radius: 50%;
  cursor: pointer;
  box-shadow: inset 0 0 0 0.5px rgba(0, 0, 0, 0.25);
}
.win-light--close { background: #ff5f57; }
.win-light--min { background: #febc2e; }
.win-light--zoom { background: #28c840; }
/* The glyphs appear when the pointer is over the group, the way they do on a Mac. */
.win-light svg { inline-size: 9px; block-size: 9px; fill: none; stroke: rgba(0, 0, 0, 0.6); stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; opacity: 0; transition: opacity var(--motion-fast) var(--ease-out); }
.win-lights:hover .win-light svg,
.win-light:focus-visible svg { opacity: 1; }
.win-light:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.win-light:active { filter: brightness(0.85); }

/* The minimised shell: a tab in the bottom-right corner, above the page's own corner pill. */
.shell-tab {
  position: fixed;
  right: var(--space-4);
  bottom: calc(var(--space-4) + 44px);
  z-index: var(--z-shell);
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  max-inline-size: min(420px, calc(100vw - 2 * var(--space-4)));
  height: 36px;
  padding: 0 var(--space-2) 0 var(--space-3);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-full);
  background: var(--overlay-surface);
  color: var(--text-primary);
  font: inherit;
  font-size: var(--text-sm);
  cursor: pointer;
  animation: shell-tab-in 280ms cubic-bezier(0.2, 0.8, 0.2, 1) 120ms both;
}
.shell-tab:hover { border-color: var(--border-strong); }
.shell-tab:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.shell-tab-dot { inline-size: 8px; block-size: 8px; border-radius: 50%; background: var(--text-muted); flex: none; }
.shell-tab[data-live="yes"] .shell-tab-dot { background: var(--ok); box-shadow: 0 0 0 3px var(--ok-soft); }
.shell-tab-name { font-weight: 600; white-space: nowrap; }
.shell-tab-addr { font-size: var(--text-xs); color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.shell-tab-open { display: grid; place-items: center; inline-size: 24px; block-size: 24px; border-radius: 50%; background: var(--bg-sunken); flex: none; }
.shell-tab-open svg { inline-size: 12px; block-size: 12px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
@keyframes shell-tab-in {
  from { opacity: 0; transform: translateY(8px) scale(0.9); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .shell-float,
  .shell-float[data-minimized="yes"] { transition: none; }
  .shell-tab { animation: none; }
  .win-light svg { transition: none; }
}

/* ------------------------------------------------------------ Changes: the approval time limit */
.approval-limit { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); padding: var(--space-3) var(--space-4); border: 1px solid var(--border-hairline); border-radius: var(--radius-md); background: var(--bg-surface); font-size: var(--text-sm); min-width: 0; }
.approval-limit-text { flex: 1 1 22rem; min-width: 0; color: var(--text-secondary); }
.approval-limit-edit { display: inline-flex; align-items: center; gap: var(--space-2); }
.approval-limit-field { display: block; width: 5.5rem; }
.approval-limit-warn { flex-basis: 100%; color: var(--warn-text); }
/* Replay set: which feature's model is scored. */
.rpl-feature { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); padding: var(--space-3) var(--space-4) 0; }
.rpl-feature-pick { display: inline-flex; align-items: center; gap: var(--space-2); }
.rpl-feature > .aim-sub { flex: 1 1 18rem; min-width: 0; }

/* ------------------------------------------------------- Slack approve / reject buttons (integrations) */
.slk-buttons { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin-top: var(--space-2); }
.slk-state { font-size: var(--text-xs); color: var(--color-text-muted); }
.slk-state.is-on { color: var(--ok-text); font-weight: 600; }
.slk-meta { font-size: var(--text-xs); color: var(--color-text-muted); }
.slk-dialog { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-3); min-width: 0; overflow-wrap: anywhere; }
.slk-dialog p { margin: var(--space-1) 0 0; }
.slk-steps { margin: 0; padding-left: var(--space-5); display: grid; gap: var(--space-1); font-size: var(--text-sm); }

/* ================================================================== "the issue has been fixed"
 *
 * `apps/portal/app/alerts/fixed-notice.tsx`: the popup that meets an incident as it closes. The one
 * green moment on the Alerts page — the check is the ok colour because it is a status, not decoration.
 */
.fixed-body {
  display: grid;
  gap: var(--space-4);
}
.fixed-hero {
  display: flex;
  gap: var(--space-4);
  align-items: center;
  padding: var(--space-4);
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--severity-ok-fg) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--severity-ok-fg) 30%, transparent);
}
.fixed-check {
  flex: none;
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  color: var(--severity-ok-fg);
  background: color-mix(in srgb, var(--severity-ok-fg) 16%, transparent);
}
.fixed-was {
  margin: 0 0 4px;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.fixed-was span {
  margin-right: 6px;
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-subtle);
}
.fixed-how {
  margin: 0;
  font-size: var(--text-lg);
  color: var(--color-text);
}
.fixed-changes h3 {
  margin: 0 0 var(--space-2);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-subtle);
}
.fixed-changes ul {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 6px;
}
.fixed-changes li {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  font-size: var(--text-sm);
}
.fixed-target {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--color-text);
}
.fixed-verdict {
  margin-left: auto;
  flex: none;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.fixed-verdict[data-verdict="recovered"] {
  color: var(--severity-ok-fg);
  font-weight: 600;
}
.fixed-verdict[data-verdict="worsened"] {
  color: var(--severity-critical-fg);
  font-weight: 600;
}
.fixed-muted {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.fixed-error {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--severity-critical-fg);
}
.fixed-foot {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
}

/* ================================================================== trace setup
 *
 * `apps/portal/app/traces/setup.tsx`: connecting applications, on the Traces page. A real sequence,
 * so the steps are numbered; a done step trades its number for a check and goes quiet.
 */
.tsu {
  display: grid;
  gap: var(--space-4);
  margin-bottom: var(--space-5);
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
}
.tsu[data-fresh="yes"] {
  padding: var(--space-5) var(--space-6);
}
.tsu-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
}
.tsu-head h2 {
  margin: 0 0 4px;
  font-size: var(--text-lg);
  font-weight: 650;
}
.tsu[data-fresh="yes"] .tsu-head h2 {
  font-size: var(--text-xl);
}
.tsu-summary {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.tsu-summary b {
  color: var(--color-text);
  font-weight: 600;
}
.tsu-summary b[data-state="off"] {
  color: var(--severity-warning-fg);
}
.tsu-summary b[data-state="on"] {
  color: var(--severity-ok-fg);
}
.tsu-dot {
  margin: 0 8px;
  color: var(--color-text-subtle);
}
.tsu-toggle {
  padding: 6px 14px;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--color-text);
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
}
.tsu-toggle:hover {
  background: var(--bg-hover);
}
.tsu-toggle:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}
.tsu-loading {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}
.tsu-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-5);
}
.tsu-step {
  display: grid;
  grid-template-columns: 32px minmax(0, 1fr);
  gap: var(--space-3);
}
.tsu-step-n {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 1.5px solid var(--color-border-strong);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text);
}
.tsu-step[data-done="yes"] .tsu-step-n {
  border-color: transparent;
  color: var(--severity-ok-fg);
  background: color-mix(in srgb, var(--severity-ok-fg) 14%, transparent);
}
.tsu-step-body {
  display: grid;
  gap: var(--space-3);
  min-width: 0;
}
.tsu-step-body h3 {
  margin: 4px 0 0;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-md);
  font-weight: 600;
}
.tsu-step-body p {
  margin: 0;
  max-width: 75ch;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.tsu-done {
  font-size: var(--text-xs);
  font-weight: 500;
  color: var(--severity-ok-fg);
}
.tsu-caution {
  padding: var(--space-2) var(--space-3);
  border-left: 3px solid var(--severity-warning-fg);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--severity-warning-fg) 8%, transparent);
}
.tsu-more > summary {
  cursor: pointer;
  font-size: var(--text-sm);
  color: var(--color-primary);
}
.tsu-more[open] > summary {
  margin-bottom: var(--space-2);
}
.tsu-namespaces {
  display: grid;
  gap: var(--space-2);
}
.tsu-ns {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}
.tsu-ns > summary {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding: var(--space-2) var(--space-3);
  cursor: pointer;
}
.tsu-ns-name {
  font-weight: 600;
  font-size: var(--text-sm);
}
.tsu-ns-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.tsu-chip {
  padding: 1px 8px;
  border-radius: var(--radius-full);
  font-size: var(--text-xs);
  font-weight: 500;
  white-space: nowrap;
  color: var(--color-text-muted);
  background: color-mix(in srgb, var(--color-text) 7%, transparent);
}
.tsu-chip[data-standing="sending"] {
  color: var(--severity-ok-fg);
  background: color-mix(in srgb, var(--severity-ok-fg) 13%, transparent);
}
.tsu-chip[data-standing="silent"] {
  color: var(--severity-warning-fg);
  background: color-mix(in srgb, var(--severity-warning-fg) 13%, transparent);
}
.tsu-chip[data-standing="ready"] {
  color: var(--color-primary);
  background: color-mix(in srgb, var(--color-primary) 12%, transparent);
}
.tsu-ns-body {
  display: grid;
  gap: var(--space-3);
  padding: 0 var(--space-3) var(--space-3);
}
.tsu-workloads {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 4px;
}
.tsu-workloads li {
  display: grid;
  grid-template-columns: 140px minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
}
.tsu-wl {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--font-mono);
}
.tsu-wl-lang {
  font-size: var(--text-xs);
  color: var(--color-text-subtle);
}
.tsu-muted {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.tsu-live {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 4px;
}
.tsu-live li {
  display: grid;
  grid-template-columns: minmax(120px, 200px) minmax(0, 1fr);
  gap: var(--space-3);
  font-size: var(--text-sm);
}
.tsu-live-ns {
  font-weight: 600;
}
.tsu-live li[data-state="on"] .tsu-live-what {
  color: var(--severity-ok-fg);
}
.tsu-live li[data-state="off"] .tsu-live-what {
  color: var(--color-text-subtle);
}
@media (max-width: 640px) {
  .tsu,
  .tsu[data-fresh="yes"] {
    padding: var(--space-4);
  }
  .tsu-workloads li {
    grid-template-columns: minmax(0, 1fr);
  }
  .tsu-live li {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
  }
}
.tsu-workloads .tsu-chip {
  justify-self: start;
}

/*!**********************************************************************************************************************************************************************************************************************************************************************!*\
  !*** css ../../node_modules/next/dist/build/webpack/loaders/css-loader/src/index.js??ruleSet[1].rules[14].oneOf[12].use[2]!../../node_modules/next/dist/build/webpack/loaders/postcss-loader/src/index.js??ruleSet[1].rules[14].oneOf[12].use[3]!./app/tailwind.css ***!
  \**********************************************************************************************************************************************************************************************************************************************************************/
/*
 * The utility layer, for `components/ui/` only.
 *
 * `@tailwind base` is deliberately absent, and its absence is the whole safety argument: the base
 * layer is a global reset that would strip the platform stylesheet's own headings, lists and buttons
 * on every page of this app. `corePlugins.preflight` is off in the config for the same reason, so
 * this is belt and braces.
 *
 * Imported after `@platform/ui/styles.css` in the layout, so where a shadcn component and the
 * platform stylesheet both have an opinion about the same element, the component wins inside itself.
 */
.visible {
    visibility: visible
}
.collapse {
    visibility: collapse
}
.fixed {
    position: fixed
}
.absolute {
    position: absolute
}
.relative {
    position: relative
}
.bottom-4 {
    bottom: 1rem
}
.left-\[50\%\] {
    left: 50%
}
.mx-auto {
    margin-left: auto;
    margin-right: auto
}
.my-2 {
    margin-top: 0.5rem;
    margin-bottom: 0.5rem
}
.mt-10 {
    margin-top: 2.5rem
}
.block {
    display: block
}
.inline {
    display: inline
}
.flex {
    display: flex
}
.inline-flex {
    display: inline-flex
}
.grid {
    display: grid
}
.size-4 {
    width: 1rem;
    height: 1rem
}
.h-10 {
    height: 2.5rem
}
.h-11 {
    height: 2.75rem
}
.h-9 {
    height: 2.25rem
}
.w-10 {
    width: 2.5rem
}
.w-full {
    width: 100%
}
.max-w-md {
    max-width: 28rem
}
.max-w-xs {
    max-width: 20rem
}
.flex-1 {
    flex: 1 1 0%
}
.translate-x-\[-50\%\] {
    --tw-translate-x: -50%;
    transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))
}
.resize {
    resize: both
}
.items-center {
    align-items: center
}
.justify-start {
    justify-content: flex-start
}
.justify-end {
    justify-content: flex-end
}
.justify-center {
    justify-content: center
}
.overflow-hidden {
    overflow: hidden
}
.overflow-y-auto {
    overflow-y: auto
}
.whitespace-nowrap {
    white-space: nowrap
}
.rounded-full {
    border-radius: 9999px
}
.rounded-lg {
    border-radius: var(--radius-lg)
}
.rounded-md {
    border-radius: var(--radius-md)
}
.border {
    border-width: 1px
}
.border-input {
    border-color: var(--color-border)
}
.bg-background {
    background-color: var(--bg-page)
}
.bg-blue-500 {
    --tw-bg-opacity: 1;
    background-color: rgb(59 130 246 / var(--tw-bg-opacity, 1))
}
.bg-destructive {
    background-color: var(--critical)
}
.bg-gray-200 {
    --tw-bg-opacity: 1;
    background-color: rgb(229 231 235 / var(--tw-bg-opacity, 1))
}
.bg-primary {
    background-color: var(--accent)
}
.bg-secondary {
    background-color: var(--bg-sunken)
}
.p-0 {
    padding: 0px
}
.p-2 {
    padding: 0.5rem
}
.p-4 {
    padding: 1rem
}
.px-3 {
    padding-left: 0.75rem;
    padding-right: 0.75rem
}
.px-4 {
    padding-left: 1rem;
    padding-right: 1rem
}
.px-8 {
    padding-left: 2rem;
    padding-right: 2rem
}
.py-2 {
    padding-top: 0.5rem;
    padding-bottom: 0.5rem
}
.text-sm {
    font-size: 0.875rem;
    line-height: 1.25rem
}
.font-medium {
    font-weight: 500
}
.text-destructive-foreground {
    color: var(--on-accent)
}
.text-gray-800 {
    --tw-text-opacity: 1;
    color: rgb(31 41 55 / var(--tw-text-opacity, 1))
}
.text-primary {
    color: var(--accent)
}
.text-primary-foreground {
    color: var(--accent-solid)
}
.text-secondary-foreground {
    color: var(--text-primary)
}
.text-white {
    --tw-text-opacity: 1;
    color: rgb(255 255 255 / var(--tw-text-opacity, 1))
}
.underline-offset-4 {
    text-underline-offset: 4px
}
.shadow-md {
    --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
    --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
    box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)
}
.outline {
    outline-style: solid
}
.ring-offset-background {
    --tw-ring-offset-color: var(--bg-page)
}
.filter {
    filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)
}
.transition-colors {
    transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
    transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
    transition-duration: 150ms
}
.hover\:bg-accent:hover {
    background-color: var(--accent-soft)
}
.hover\:text-accent-foreground:hover {
    color: var(--accent-text)
}
.hover\:underline:hover {
    text-decoration-line: underline
}
.focus-visible\:outline-none:focus-visible {
    outline: 2px solid transparent;
    outline-offset: 2px
}
.focus-visible\:ring-2:focus-visible {
    --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
    --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
    box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000)
}
.focus-visible\:ring-ring:focus-visible {
    --tw-ring-color: var(--focus-ring)
}
.focus-visible\:ring-offset-2:focus-visible {
    --tw-ring-offset-width: 2px
}
.disabled\:pointer-events-none:disabled {
    pointer-events: none
}
.disabled\:opacity-50:disabled {
    opacity: 0.5
}

