:root {
  /* One type scale for every screen (2026-11-10): sizes are picked from it, never typed. */
  --fs-2xs: 0.72rem;
  --fs-xs: 0.76rem;
  --fs-sm: 0.82rem;
  --fs-base: 0.9rem;
  --fs-md: 1rem;
  --fs-lg: 1.15rem;
  --fs-xl: 1.35rem;
  --fs-2xl: 1.6rem;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --bg: #f5f7fb;
  --surface: #ffffff;
  --border: #e3e8f0;
  --ink: #101a2e;
  --ink-muted: #5b677d;
  --delivered: #1c7c4d;
  --delivered-bg: #e6f5ec;
  --filtered: #a3620f;
  --filtered-bg: #fbf0df;
  --skipped: #4c5a63;
  --skipped-bg: #e9edf0;
  --failed: #b5484f;
  --failed-bg: #fbe9ea;
  /* The brand: one blue for every action and selection, in every environment. */
  --accent: #1a5fd6;
  --accent-tint: #e8f0fd;
  --brand-ink: #14234a;
  /* The lift a card, a table or a panel has off the page, on every screen. */
  --card-shadow: 0 1px 3px rgba(16, 26, 46, 0.05);
  /* environment colour - overridden per :root[data-env] below */
  --env-accent: #1c7c4d;
  --env-tint: #e6f5ec;
}

/* The environment is told by its badge under the logo, not by recolouring the
   screen: green = Development, grey = Test, light red = Production. Buttons
   and selections stay the brand blue everywhere, so a screen looks the same
   wherever it is used and only the badge says where that is. */
:root[data-env="dev"]  { --env-accent: #1c7c4d; --env-tint: #e6f5ec; }
:root[data-env="test"] { --env-accent: #55605f; --env-tint: #e9edf0; }
:root[data-env="prod"] { --env-accent: #d64545; --env-tint: #fde9e9; }
/* Production also carries a line across the very top and a tint behind the
   logo, so it shows with the menu collapsed and from the corner of an eye:
   nobody should make a change there without knowing where they are. */
:root[data-env="prod"] body::before {
  content: "";
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 4px;
  background: #f0a3a3;
  z-index: 1000;
  pointer-events: none;
}
:root[data-env="prod"] .sidebar { background: linear-gradient(#fff5f5, var(--surface) 150px); }
* { box-sizing: border-box; }
/* Form controls take the portal's font, not the browser's own (Arial at 13.33px on Windows) - the same text
   everywhere; a class that sizes one still does. */
button, input, select, textarea { font-family: inherit; font-size: var(--fs-base); }
body {
  margin: 0;
  font-family: -apple-system, "Segoe UI", Roboto, sans-serif;
  background: var(--bg);
  color: var(--ink);
}
/* A link in the text is a name that can be opened (a map, a partner), so it
   reads as the text around it and only the underline says it opens. A link
   drawn as a button or a chip keeps its own look: its class sets colour and
   font, and a class outranks this. */
a { color: inherit; font: inherit; text-decoration: underline; }
header {
  padding: 1.5rem 2rem 1rem;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}
header h1 {
  margin: 0 0 0.25rem;
  font-size: var(--fs-xl);
}
header p {
  margin: 0;
  color: var(--ink-muted);
  font-size: var(--fs-base);
}
main {
  padding: 1.5rem 2rem;
  display: grid;
  gap: 1.5rem;
}
/* `display: grid` above outranks the UA [hidden] rule - reassert it so a
   toggled <main> (Monitoring's Messages/Server subtabs) actually hides. */
main[hidden] { display: none; }
table {
  width: 100%;
  border-collapse: collapse;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: var(--card-shadow);
  overflow: hidden;
}
thead th {
  text-align: left;
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-muted);
  padding: 0.6rem 0.9rem;
  border-bottom: 1px solid var(--border);
  background: var(--bg);
}
tbody td {
  padding: 0.55rem 0.9rem;
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-base);
}
tbody tr:last-child td { border-bottom: none; }
tbody tr { cursor: pointer; }
tbody tr:hover td { background: #eef2f8; }
tbody tr.selected td { background: var(--accent-tint); }
.status-pill {
  display: inline-block;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  font-size: var(--fs-2xs);
  font-weight: 600;
}
.status-DELIVERED { background: var(--delivered-bg); color: var(--delivered); }
/* A message's status and what is said about it (Unusual, Verified) one under
   the other, so a row never mixes one pill on a line with two on the next. */
/* One width for every pill in the list - the longest status, "Detection
   failed", fits - so a column of them lines up whatever each one says. */
td.mon-status .status-pill { display: block; width: 7.5rem; text-align: center; box-sizing: border-box; }
td.mon-ack .status-pill { min-width: 7.5rem; text-align: center; box-sizing: border-box; }
td.mon-status .status-pill + .status-pill { margin-top: 0.2rem; }
.status-FILTERED_TEST { background: var(--filtered-bg); color: var(--filtered); }
.status-SKIPPED_PARTNER { background: var(--skipped-bg); color: var(--skipped); }
.status-FAILED { background: var(--failed-bg); color: var(--failed); }
.status-RUNNING { background: var(--delivered-bg); color: var(--delivered); }
.status-PAUSED { background: var(--filtered-bg); color: var(--filtered); }
.status-DUPLICATE, .status-OLDER_CHANGE { background: var(--skipped-bg); color: var(--skipped); }
.status-WATCHED { background: var(--accent-tint); color: var(--accent); }
#empty {
  padding: 2rem;
  text-align: center;
  color: var(--ink-muted);
  font-size: var(--fs-base);
}
/* Monitoring: a row's details expand inline right below it */
tr.detail-row td {
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  padding: 0.9rem 1.1rem;
  cursor: default;
}
tr.detail-row:hover td { background: var(--bg); }
.mon-row.selected td { background: var(--accent-tint); }
.mon-error {
  background: var(--failed-bg);
  border: 1px solid var(--failed);
  border-radius: 6px;
  padding: 0.55rem 0.8rem;
  margin-bottom: 0.7rem;
}
.mon-error .field { color: var(--failed); margin-top: 0; }
.mon-error pre {
  margin: 0.25rem 0 0; font-size: var(--fs-sm); color: var(--ink);
  white-space: pre-wrap; word-break: break-word;
}
/* What went wrong, in words, where the stack trace used to be. The trace is a
   button away - it is the answer to a different question, asked by a different
   person. */
.mon-diagnosis { font-size: var(--fs-sm); color: var(--ink); }
.mon-diagnosis .field { margin-top: 0.55rem; }
.mon-diagnosis .field:first-child { margin-top: 0.15rem; }
.mon-diagnosis-pending {
  margin: 0.25rem 0 0; font-size: var(--fs-sm); white-space: pre-wrap; word-break: break-word;
}
.mon-diagnosis-list { margin: 0.2rem 0 0; padding-left: 1.1rem; }
.mon-diagnosis-list li { margin: 0.15rem 0; }
.mon-diagnosis-ai p { margin: 0.35rem 0 0; }
.mon-diagnosis-note { margin: 0.45rem 0 0; font-size: var(--fs-sm); color: var(--ink-muted); font-style: italic; }
.mon-error-actions {
  display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.6rem;
}
/* One column each for what the step was, when it was reached and its payload,
   so the times and the buttons line up down the list instead of sitting
   wherever the label happened to end. */
.mon-checkpoints ol {
  margin: 0.3rem 0 0; padding-left: 1.2rem; display: flex; flex-direction: column; gap: 0.3rem;
}
.mon-checkpoints li {
  font-size: var(--fs-sm);
  display: grid;
  grid-template-columns: minmax(0, 1fr) 6rem max-content;
  align-items: center;
  gap: 0.6rem;
}
.mon-cp-label { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mon-cp-time { color: var(--ink-muted); font-size: var(--fs-xs); font-variant-numeric: tabular-nums; }
.view-payload.btn-small { padding: 0.2rem 0.55rem; font-size: var(--fs-xs); }

/* Monitoring: reprocess action + transient banner */
.mon-actions-col { width: 1%; }
td.mon-actions { text-align: right; white-space: nowrap; }
/* Always visible. It used to appear only while its row was hovered, which meant
   nobody could tell it was there - an action you have to find by sweeping the
   mouse across a table is an action that does not exist. Hovering still brings it
   forward, so the row you are on is still the one that stands out. */
.reprocess-btn {
  font: inherit; font-size: var(--fs-xs); padding: 0.25rem 0.6rem;
  border: 1px solid var(--border); border-radius: 6px;
  background: var(--surface); color: var(--ink-muted); cursor: pointer;
  opacity: 0.7; transition: opacity 0.12s;
}
tr.mon-row:hover .reprocess-btn, tr.mon-row.selected .reprocess-btn { opacity: 1; }
.reprocess-btn:hover { border-color: var(--accent); color: var(--accent); opacity: 1; }
.reprocess-btn:disabled { opacity: 0.35; cursor: not-allowed; }
tr.mon-row:hover .reprocess-btn:disabled { opacity: 0.35; }
.mon-flash {
  margin-bottom: 0.75rem; padding: 0.55rem 0.85rem; border-radius: 6px;
  background: var(--delivered-bg); color: var(--delivered);
  border: 1px solid var(--delivered); font-size: var(--fs-sm);
}
.mon-flash.mon-flash-error {
  background: var(--failed-bg); color: var(--failed); border-color: var(--failed);
}

.mon-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}
.mon-filters label {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-muted);
}
.mon-filters select,
.mon-filters input {
  font-size: var(--fs-sm);
  padding: 0.3rem 0.4rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
  color: var(--ink);
}
.mon-filters #f-clear { margin-bottom: 0.1rem; }
/* From and To are one range: when the bar wraps they go to the next row
   together, rather than From at the end of one row and To on the next. */
.mon-filters .mon-range { display: flex; align-items: flex-end; gap: 0.75rem; }
.field {
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-muted);
  margin-top: 0.75rem;
}
.meta-row {
  display: flex;
  gap: 1.5rem;
  flex-wrap: wrap;
  font-size: var(--fs-sm);
  color: var(--ink-muted);
  margin-bottom: 0.5rem;
}
.payload-buttons {
  display: flex;
  gap: 0.6rem;
  margin-top: 0.85rem;
}
button.view-payload {
  font: inherit;
  font-size: var(--fs-sm);
  padding: 0.4rem 0.85rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}
button.view-payload:hover { border-color: var(--accent); color: var(--accent); }
button.view-payload:disabled { opacity: 0.4; cursor: not-allowed; }
/* The message window: one transaction's detail, in its own window so the list
   behind it can go on refreshing without moving what is being read. Wide, since
   a stack trace and a debug trace are both in here. */
dialog.msg-dialog {
  width: min(78rem, 96vw);
  max-height: 90vh;
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 0;
  overflow: auto;
  color: var(--ink);
}
dialog.msg-dialog::backdrop { background: rgba(23, 33, 31, 0.45); }

/* Header: what the message is, in a line, and the reference to quote. */
.msg-head {
  display: flex; align-items: center; gap: 0.9rem; padding: 1rem 1.4rem;
  border-bottom: 1px solid var(--border); position: sticky; top: 0; background: var(--surface); z-index: 2;
}
.msg-head-main { display: flex; align-items: center; gap: 0.9rem; flex: 1; min-width: 0; }
.msg-head-text { flex: 1; min-width: 0; }
.msg-head-title { font-size: var(--fs-lg); font-weight: 650; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.msg-head-sub { color: var(--ink-muted); font-size: var(--fs-sm); margin-top: 0.15rem; }
.msg-head-ref { display: flex; flex-direction: column; align-items: flex-end; gap: 0.15rem; font-size: var(--fs-xs); color: var(--ink-muted); }
.msg-head-ref code { font-size: var(--fs-xs); color: var(--ink); margin-right: 0.35rem; }
.msg-head-close { border: 0; background: none; font-size: var(--fs-lg); color: var(--ink-muted); cursor: pointer; padding: 0.2rem 0.4rem; }

/* Actions: every button the message has, in one row, the same size. */
.msg-bar {
  display: flex; gap: 0.5rem; align-items: center; padding: 0.6rem 1.4rem;
  background: var(--bg); border-bottom: 1px solid var(--border);
}
.msg-bar .btn-secondary, .msg-bar .btn-primary { font: inherit; font-size: var(--fs-sm); padding: 0.4rem 0.85rem; border-radius: 6px; cursor: pointer; }
.msg-bar-gap { flex: 1; }
.msg-body { padding: 1.1rem 1.4rem 1.4rem; }

/* Sections: a heading in small capitals, then the content. */
.msg-section { border: 1px solid var(--border); border-radius: 10px; margin-bottom: 1rem; background: var(--surface); }
.msg-section-head {
  margin: 0; padding: 0.6rem 0.9rem; font-size: var(--fs-xs); font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--ink-muted); border-bottom: 1px solid var(--border);
  display: flex; align-items: center; justify-content: space-between; gap: 0.8rem;
}
.msg-section-head .status-pill { text-transform: none; letter-spacing: 0; }
.msg-section-body { padding: 0.9rem; }
.msg-fail { border-color: #f0c9cc; }
.msg-fail > .msg-section-head { color: var(--failed); background: var(--failed-bg); border-bottom-color: #f0c9cc; border-radius: 10px 10px 0 0; }
.msg-buttons { display: flex; gap: 0.5rem; margin-top: 0.7rem; }
.msg-verify { display: flex; gap: 0.5rem; align-items: center; margin-top: 0.8rem; padding-top: 0.8rem; border-top: 1px solid var(--border); font-size: var(--fs-sm); }
.msg-section-body > .msg-verify:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
.msg-verify input { flex: 1; font: inherit; font-size: var(--fs-sm); padding: 0.35rem 0.55rem; border: 1px solid var(--border); border-radius: 6px; }

/* Facts: label left, value right, aligned in two columns. */
.msg-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-bottom: 1rem; }
.msg-cols .msg-section { margin-bottom: 0; }
@media (max-width: 900px) { .msg-cols { grid-template-columns: 1fr; } }
.msg-facts { display: grid; grid-template-columns: 9.5rem 1fr; margin: 0; row-gap: 0.55rem; column-gap: 0.8rem; font-size: var(--fs-sm); }
.msg-facts dt { color: var(--ink-muted); }
.msg-facts dd { margin: 0; display: flex; align-items: center; flex-wrap: wrap; gap: 0.45rem; min-width: 0; }
.msg-facts code { font-size: var(--fs-sm); background: var(--bg); padding: 0.05rem 0.35rem; border-radius: 4px; word-break: break-all; }
.msg-fields { display: grid; grid-template-columns: auto 1fr; column-gap: 0.7rem; row-gap: 0.2rem; }
.msg-muted { color: var(--ink-muted); }
.msg-hit { font-weight: 600; }
.msg-none { color: var(--failed); }
.msg-link { border: 0; background: none; padding: 0; font: inherit; color: var(--accent); text-decoration: underline; cursor: pointer; }

/* The process flow: compact steps, six to a row; more scroll sideways. */
.msg-flow { display: flex; align-items: stretch; overflow-x: auto; padding-bottom: 0.3rem; }
.msg-step {
  flex: 1 0 8.2rem; min-width: 8.2rem; border: 1px solid var(--border); border-radius: 8px;
  padding: 0.5rem 0.6rem; background: var(--surface); cursor: default;
}
.msg-step.this { border: 2px solid var(--accent); background: var(--accent-tint); padding: calc(0.5rem - 1px) calc(0.6rem - 1px); }
.msg-step.missing { border: 1.5px dashed var(--failed); }
.msg-step.waiting { border: 1.5px dashed var(--filtered); }
.msg-step.later { border-style: dashed; background: var(--bg); }
.msg-step-n { display: flex; justify-content: space-between; align-items: center; min-height: 1.3rem;
  font-size: var(--fs-2xs); color: var(--ink-muted); text-transform: uppercase; letter-spacing: 0.05em; }
.msg-step-n .btn-small { text-transform: none; letter-spacing: 0; padding: 0.05rem 0.45rem; font-size: var(--fs-2xs); }
.msg-step-this { color: var(--accent); font-weight: 700; }
.msg-step-name { font-weight: 650; font-size: var(--fs-sm); margin: 0.1rem 0 0.35rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.msg-step-t { font-size: var(--fs-2xs); color: var(--ink-muted); margin-top: 0.3rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.msg-step-t b { color: var(--ink); font-weight: 600; }
.msg-step-ack { background: none !important; font-weight: 600; }
.msg-arrow { flex: 0 0 2.6rem; align-self: center; display: flex; flex-direction: column; align-items: center; font-size: 0.64rem; color: var(--ink-muted); }
.msg-arrow span { min-height: 0.85rem; white-space: nowrap; }
.msg-arrow-line { width: 100%; height: 2px; background: var(--border); position: relative; margin: 0.15rem 0; }
.msg-arrow-line::after { content: ""; position: absolute; right: -1px; top: -4px; border: 5px solid transparent; border-left: 6px solid #b9c3d3; }
.msg-arrow.late { color: var(--failed); }
.msg-issues { list-style: none; margin: 0.8rem 0 0; padding: 0.6rem 0 0; border-top: 1px solid var(--border); font-size: var(--fs-sm); }
.msg-issues li { display: flex; gap: 0.5rem; align-items: baseline; padding: 0.15rem 0; }
.msg-flow-foot { margin-top: 0.6rem; font-size: var(--fs-xs); color: var(--ink-muted); }
.msg-links { margin: 0.2rem 0 0.5rem 1.1rem; padding: 0; line-height: 1.6; }
.msg-links b { color: var(--ink); }
.msg-link-step { color: var(--ink); }

.msg-timeline { list-style: none; margin: 0; padding: 0; font-size: var(--fs-sm); }
.msg-timeline li { display: grid; grid-template-columns: 6rem 1fr auto; gap: 0.8rem; align-items: center; padding: 0.4rem 0; border-bottom: 1px solid var(--border); }
.msg-timeline li:last-child { border-bottom: 0; }

dialog#payload-dialog {
  width: min(800px, 90vw);
  max-height: 80vh;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0;
}
dialog#payload-dialog::backdrop {
  background: rgba(23, 33, 31, 0.45);
}
.dialog-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.85rem 1.1rem;
  border-bottom: 1px solid var(--border);
}
.dialog-header h3 { margin: 0; font-size: var(--fs-base); }
.dialog-header button {
  font: inherit;
  border: none;
  background: none;
  cursor: pointer;
  font-size: var(--fs-lg);
  color: var(--ink-muted);
  line-height: 1;
}
dialog#payload-dialog pre {
  margin: 0;
  padding: 1rem 1.1rem;
  max-height: calc(80vh - 3.2rem);
  overflow: auto;
  font-size: var(--fs-sm);
  white-space: pre-wrap;
  word-break: break-word;
}
section.panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: var(--card-shadow);
  padding: 1rem 1.25rem;
}
/* A table inside a panel is already on a card; a second lift would stack. */
section.panel table { box-shadow: none; border-radius: 8px; }
/* [hidden] is a UA display:none; assert it so a hidden detail tab panel
   actually disappears regardless of any future section.panel display. */
.wf-tab[hidden], .map-tab[hidden] { display: none; }
section.panel h2 {
  margin: 0 0 0.85rem;
  font-size: var(--fs-md);
}
/* A heading after a form starts a new topic - not the form's last line. */
section.panel .detail-form + h2 { margin-top: 1.8rem; }

/* A tab body that's really several distinct topics (the integration cockpit's
   Overview / Versions & promotion tabs: pipeline, maps, partners, schedules,
   versions, promotion history, AI plan log...) - each topic gets its own
   section.panel card instead of being stacked under repeated <h2>s inside one
   big panel, so they read as separate blocks of information, not one wall of
   text. This wrapper is a plain layout container, not a panel itself. */
.int-tab {
  display: grid;
  gap: 1.25rem;
}
/* display: grid above outranks the UA [hidden] rule (same issue main[hidden]
   above this already has to reassert) - without this, the subtab click
   handler's `panel.hidden = ...` toggle stops actually hiding anything, and
   every cockpit tab (Overview/Runs/Versions/Plan) renders stacked at once,
   which looks exactly like "clicking a tab does nothing" since there's no
   visible change left to make. */
.int-tab[hidden] {
  display: none;
}

/* App shell: sidebar + tabbed workspace */
.shell {
  display: flex;
  height: 100vh;
  overflow: hidden;
}

.sidebar {
  width: 220px;
  flex: 0 0 auto;
  background: var(--surface);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  transition: width 0.15s ease;
  overflow: hidden;
}
.sidebar.collapsed { width: 64px; }

.sidebar-brand {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  padding: 1.1rem 1rem;
  font-weight: 600;
  font-size: var(--fs-lg);
  line-height: 26px;
  letter-spacing: -0.01em;
  color: var(--brand-ink);
  white-space: nowrap;
}
/* The Composa mark (composa-icon.svg), held here so the sign-in page and the
   two-factor page can show it too: this stylesheet is the one thing all three
   may load before anybody has signed in. */
.brand-mark {
  display: inline-block;
  width: 38px;
  height: 26px;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='22.5 17 42 28'%3E%3Cdefs%3E%3ClinearGradient id='o' gradientUnits='userSpaceOnUse' x1='23' y1='31' x2='64' y2='31'%3E%3Cstop offset='0' stop-color='%23FB8A2A'/%3E%3Cstop offset='1' stop-color='%23F4661A'/%3E%3C/linearGradient%3E%3C/defs%3E%3Cg fill='none' stroke='url(%23o)' stroke-width='3.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M41 24.5 L39.68 23.18 Q36.5 20 33.29 23.15 L28.51 27.85 Q25.3 31 28.51 34.15 L33.29 38.85 Q36.5 42 39.68 38.82 L48.3 30.2'/%3E%3Cpath d='M46 37.5 L47.32 38.82 Q50.5 42 53.71 38.85 L58.49 34.15 Q61.7 31 58.49 27.85 L53.71 23.15 Q50.5 20 47.32 23.18 L38.7 31.8'/%3E%3C/g%3E%3C/svg%3E") center / contain no-repeat;
  flex: 0 0 auto;
}
.sidebar.collapsed .sidebar-brand { padding-left: 0; padding-right: 0; justify-content: center; }
/* Lifted a touch: centred by the numbers, the word reads as sitting low
   against the mark, because lower-case letters carry their weight low. */
.brand-label { position: relative; top: -2px; }
.sidebar.collapsed .brand-label { display: none; }

.sidebar-env {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0 0.75rem 0.35rem;
  padding: 0.4rem 0.6rem;
  border-radius: 6px;
  background: var(--env-tint);
  border: 1px solid var(--env-accent);
  color: var(--env-accent);
  font-size: var(--fs-xs);
  font-weight: 600;
  white-space: nowrap;
  cursor: default;
  user-select: none;
}
.sidebar-env .env-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--env-accent);
  flex: 0 0 auto;
}
.sidebar-env .env-name { flex: 1; overflow: hidden; text-overflow: ellipsis; }
.sidebar-env .env-short {
  display: none;
  letter-spacing: 0.04em;
  font-size: var(--fs-2xs);
}
.sidebar.collapsed .sidebar-env {
  justify-content: center;
  padding: 0.4rem 0;
  gap: 0;
}
.sidebar.collapsed .sidebar-env .env-name,
.sidebar.collapsed .sidebar-env .env-dot { display: none; }
.sidebar.collapsed .sidebar-env .env-short { display: inline; }

.env-switch { display: flex; flex-wrap: wrap; gap: 0.5rem; }

/* A slide switch: an on/off that is kept, not a delete - a Wizard lesson
   switched off stays listed. A checkbox underneath, so it is keyboard- and
   screen-reader-usable as one. */
.switch-row { display: inline-flex; align-items: center; gap: 0.4rem; cursor: pointer; margin: 0; }
.switch { position: relative; display: inline-block; width: 32px; height: 18px; flex: none; }
.switch input { position: absolute; opacity: 0; width: 0; height: 0; margin: 0; }
.switch > span {
  position: absolute; inset: 0; border-radius: 9px; background: var(--border);
  transition: background 0.15s;
}
.switch > span::before {
  content: ""; position: absolute; left: 2px; top: 2px; width: 14px; height: 14px; border-radius: 50%;
  background: var(--surface); box-shadow: 0 1px 2px rgba(16, 26, 46, 0.3); transition: transform 0.15s;
}
.switch input:checked + span { background: var(--accent); }
.switch input:checked + span::before { transform: translateX(14px); }
.switch input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }
.env-chip {
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--ink-muted);
  border-radius: 999px;
  padding: 0.35rem 0.9rem;
  font: inherit;
  font-size: var(--fs-sm);
  cursor: pointer;
}
.env-chip:hover { border-color: var(--env-accent); color: var(--ink); }
.env-chip.active {
  background: var(--env-tint);
  border-color: var(--env-accent);
  color: var(--env-accent);
  font-weight: 600;
  cursor: default;
}

.mon-pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin-top: 0.85rem;
  font-size: var(--fs-sm);
  color: var(--ink-muted);
}
.mon-pager-btns { display: flex; align-items: center; gap: 0.4rem; }
.mon-pager-pos { padding: 0 0.4rem; }

.promote-menu { display: inline-block; }
/* A version row's buttons (Open in editor, Promote, Delete) wear the Promote
   button's look, side by side with a little room between them. */
.wf-promote-cell > * { vertical-align: middle; }
.wf-promote-cell > * + * { margin-left: 0.35rem; }
.promote-menu .promote-btn, .wf-promote-cell > .promote-btn {
  border: 1px solid var(--border);
  background: var(--surface);
  border-radius: 4px;
  padding: 0.2rem 0.5rem;
  font: inherit;
  font-size: var(--fs-xs);
  color: var(--ink-muted);
  cursor: pointer;
  white-space: nowrap;
}
.promote-menu .promote-btn:hover, .wf-promote-cell > .promote-btn:hover { border-color: var(--accent); color: var(--accent); }

/* Rendered on document.body and positioned from the button's rect (see
   promoteMenu in util.js), so a table row can't clip it. */
.promote-list {
  position: fixed;
  z-index: 1000;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 6px;
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18);
  min-width: 160px;
  padding: 0.25rem;
}
.promote-list button {
  display: block;
  width: 100%;
  text-align: left;
  border: none;
  background: none;
  font: inherit;
  font-size: var(--fs-sm);
  padding: 0.45rem 0.6rem;
  border-radius: 4px;
  cursor: pointer;
  color: var(--ink);
}
.promote-list button:hover { background: var(--bg); color: var(--accent); }
/* The same floating list as a combobox's (util.js combobox): long, so it scrolls,
   and the entry the arrow keys are on is marked like a hovered one. */
.promote-list.combo-list { max-height: 20rem; overflow: auto; width: max-content; }
.promote-list.combo-list button { white-space: nowrap; }
/* What an entry means, beside it - a path's source field and sample value. */
.promote-list .combo-hint { text-transform: none; margin: 0 0 0 1.2rem; }
.promote-list .combo-note { text-transform: none; margin: 0.3rem 0.6rem; }
/* A combobox in columns: every row shares the list's grid, so the columns line
   up down the whole list however long each entry is. */
.promote-list.combo-grid { display: grid; column-gap: 1.2rem; }
.combo-grid > button, .combo-grid > .combo-head, .combo-grid > .combo-section, .combo-grid > .combo-note {
  grid-column: 1 / -1;
}
.combo-grid > button, .combo-grid > .combo-head { display: grid; grid-template-columns: subgrid; }
.combo-grid > button > span + span { color: var(--ink-muted); }
.combo-grid > .combo-head { padding: 0.3rem 0.6rem; font-size: var(--fs-xs); font-weight: 600; color: var(--ink-muted); }
.promote-list .combo-section {
  padding: 0.5rem 0.6rem 0.2rem; margin-top: 0.3rem; border-top: 1px solid var(--border);
  font-size: var(--fs-xs); font-weight: 600; color: var(--ink-muted);
}
.promote-list button.combo-more { color: var(--accent); font-style: italic; }
.promote-list button.active { background: var(--bg); color: var(--accent); }

.env-grid {
  display: flex;
  align-items: flex-end;
  gap: 0.6rem;
  flex-wrap: wrap;
}
.env-grid label {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  font-size: var(--fs-2xs);
  color: var(--ink-muted);
}
.env-grid select { font-size: var(--fs-sm); padding: 0.15rem 0.25rem; }
/* --- Manual transaction ---------------------------------------------------
   A document being keyed in, laid out as a document: one bar saying what is
   being sent, to whom and how, with both actions; the header fields grouped
   by what they are; the lines as a grid with their totals; the document's
   own totals where an invoice has them, at the bottom right. */

/* As wide as a document, not as wide as the screen: on a wide monitor every
   column, box and table stretched to fill it, and a 13-digit field became a
   box half a metre long. */
#p-manual { max-width: 86rem; }
/* The portal's form look for every box here - this tab is not a .detail-form,
   so without it the browser's bare, cramped defaults showed. */
#p-manual input:not([type="checkbox"]):not([type="radio"]), #p-manual select {
  font: inherit;
  font-size: var(--fs-sm);
  padding: 0.4rem 0.55rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
  color: var(--ink);
}
#p-manual input:focus, #p-manual select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-tint);
}
/* Worked out, and read-only: typing into one stopped it being worked out,
   and an open box was easy to type into by mistake. Light grey says "not
   yours to fill in". */
#p-manual input.p-computed[readonly] { background: #eef1f5; border-color: var(--border); color: var(--ink); cursor: default; }
#p-manual input.p-computed[readonly]:focus { box-shadow: none; border-color: var(--border); }
#p-manual input.p-mt-bad { border-color: var(--failed); background: #fffafa; }
/* A header box is as wide as an identifier or a date - a GLN is 13 digits. */
#p-manual .p-mt-groups .p-mt-input { max-width: 16rem; }
.p-mt-group h4::first-letter { text-transform: uppercase; }

/* The bar: what, to whom, how - and Send, never at the bottom of a long form. */
.p-mt-bar {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 0.6rem 1.2rem;
  align-items: center;
}
.p-mt-bar h2 { margin: 0 0 0.55rem; font-size: var(--fs-lg); }
.p-mt-picks { display: flex; flex-wrap: wrap; gap: 0.9rem; align-items: flex-end; }
.p-mt-pick { display: flex; flex-direction: column; gap: 0.2rem; }
.p-mt-pick > span {
  font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink-muted);
}
.p-mt-pick select { min-width: 18rem; max-width: 26rem; }
.p-mt-route { margin-top: 0.6rem; font-size: var(--fs-sm); color: var(--ink-muted); }
.p-mt-route code { font-size: var(--fs-xs); background: var(--bg); padding: 0.05rem 0.35rem; border-radius: 4px; color: var(--ink); }
.p-mt-env-dev { color: var(--delivered); }
.p-mt-env-test { color: var(--skipped); }
.p-mt-env-prod { color: var(--failed); font-weight: 600; }
.p-mt-actions { display: flex; gap: 0.5rem; align-self: start; }
.p-mt-wide { grid-column: 1 / -1; margin: 0; }
.p-mt-sent { font-size: var(--fs-sm); color: var(--delivered); }

/* Sections: a small heading line, then the content. */
.p-mt-section { padding: 0; }
.p-mt-section-head {
  margin: 0; padding: 0.6rem 1rem; font-size: var(--fs-xs); font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--ink-muted); border-bottom: 1px solid var(--border);
  display: flex; justify-content: space-between; align-items: center; gap: 1rem;
}
.p-mt-section-body { padding: 0.9rem 1rem 1rem; }
.p-mt-hint { font-size: var(--fs-xs); color: var(--ink-muted); text-transform: none; letter-spacing: 0; font-weight: 400; }

/* Header fields: label left, box right, in a column per group. */
.p-mt-groups { display: grid; gap: 0 1.6rem; }
.p-mt-group h4 { margin: 0 0 0.6rem; font-size: var(--fs-sm); font-weight: 650; }
.p-mt-field { display: grid; grid-template-columns: 9.5rem minmax(0, 1fr); column-gap: 0.6rem; align-items: center; margin-bottom: 0.5rem; }
.p-mt-field label { font-size: var(--fs-sm); color: var(--ink-muted); line-height: 1.2; }
.p-mt-input { position: relative; }
.p-mt-input input, .p-mt-input select { width: 100%; }
.p-mt-input input.p-num { text-align: right; font-variant-numeric: tabular-nums; }
.p-mt-count {
  position: absolute; right: 0.5rem; top: 50%; transform: translateY(-50%);
  font-size: 0.64rem; color: #9aa5b6; pointer-events: none;
}
.p-mt-msg { grid-column: 2; font-size: var(--fs-2xs); color: var(--failed); margin-top: 0.15rem; }
.p-mt-field.p-mt-bad input, input.p-mt-bad { border-color: var(--failed); background: #fffafa; }
.p-req { color: var(--failed); text-decoration: none; font-weight: 700; margin-left: 0.1rem; }

/* The lines. Each column is as wide as what it holds - a line number is six
   characters, not the fifty every box used to be - so an ordinary invoice line
   fits across the page with the sidebar collapsed. Wider specs still scroll
   sideways rather than squeezing every column to uselessness. */
.p-lines-wrap { overflow-x: auto; }
/* Fixed layout, so the column widths set per field are read as proportions of
   the page rather than as demands that add up past it. min-width stops the
   boxes shrinking to unusable on a narrow window - only then does the wrapper
   scroll. */
table.p-lines {
  width: 100%;
  table-layout: fixed;
  /* min-width comes from the fields themselves - see linesMinWidth. */
}
table.p-lines th {
  /* The guideline's own words, not upper-cased like a data grid's headings. */
  font-size: var(--fs-xs);
  font-weight: 600;
  text-transform: none;
  letter-spacing: normal;
  color: var(--ink-muted);
  background: none;
  /* Wraps at spaces only; a column too narrow for its longest word is made
     wider instead (see fieldWidth), and if the page cannot give it the room
     the table scrolls. */
  white-space: normal;
  overflow-wrap: normal;
  word-break: normal;
  line-height: 1.2;
  /* Bottom, so headings of one, two and three lines all end level above their boxes. */
  vertical-align: bottom;
  padding: 0.2rem 0.35rem 0.4rem;
  border-bottom: 1px solid var(--border);
}
table.p-lines th.p-num { text-align: right; }
table.p-lines thead th.p-line-no { text-align: right; }
table.p-lines td { padding: 0.2rem 0.35rem; }
table.p-lines input, table.p-lines select {
  width: 100%;
  min-width: 0;
  box-sizing: border-box;
  padding: 0.25rem 0.4rem;
  font-size: var(--fs-sm);
}
table.p-lines input.p-num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.p-line-no {
  width: 2.2rem;
  text-align: right;
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}
.p-line-x { width: 2.6rem; text-align: center; }
table.p-lines tfoot td {
  padding-top: 0.55rem; font-weight: 600; font-size: var(--fs-sm); text-align: right;
  font-variant-numeric: tabular-nums; border-top: 1px solid var(--border);
}
table.p-lines tfoot td.p-lines-count { text-align: left; font-weight: 400; color: var(--ink-muted); }
.p-mt-linebar { display: flex; justify-content: space-between; align-items: center; margin-top: 0.6rem; }

/* A field the arithmetic fills in: still an input, because a guideline can
   always disagree with the sum - but tinted, and tagged, until somebody types
   over it. */
.p-calc {
  font-size: 0.6rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--accent);
  background: var(--accent-tint);
  font-weight: 600;
  padding: 0.05rem 0.3rem;
  border-radius: 3px;
}
input.p-computed {
  background: #eef1f5;
  font-variant-numeric: tabular-nums;
}

/* Bottom: a note on what Send does, and the totals where an invoice has them. */
.p-mt-bottom { display: grid; grid-template-columns: minmax(0, 1fr) 30rem; gap: 1rem; align-items: start; }
.p-mt-bottom-single { grid-template-columns: 1fr; }
.p-mt-note { font-size: var(--fs-sm); color: var(--ink-muted); line-height: 1.55; }
.p-mt-note ul { margin: 0.4rem 0 0 1.1rem; padding: 0; }
.p-mt-totals .p-mt-total-row { margin-bottom: 0.5rem; }
.p-mt-totals .p-mt-total-row:first-child { display: grid; grid-template-columns: minmax(0, 1fr) 9rem; align-items: center; }
.p-mt-totals .p-mt-total-row:first-child label { font-size: var(--fs-sm); color: var(--ink-muted); }
.p-mt-totals .p-mt-field { grid-template-columns: minmax(0, 1fr) 9rem; margin-bottom: 0; }
.p-mt-totals .p-mt-total-row:last-child { border-top: 1px solid var(--border); padding-top: 0.6rem; font-weight: 650; }
.p-mt-totals .p-mt-total-row:last-child label { color: var(--ink); font-weight: 650; }
.p-mt-rate { display: inline-flex; align-items: center; gap: 0.35rem; justify-content: flex-end; font-size: var(--fs-sm); color: var(--ink-muted); }
.p-mt-rate input { width: 5rem; text-align: right; }

.p-mt-history .p-mt-hint { margin-top: 0.1rem; }
.p-mt-open { margin-left: 0.4rem; font-size: var(--fs-xs); color: var(--accent); }

.p-manual-empty { padding: 1.2rem 0; max-width: 46rem; }
.p-manual-empty h3 { margin: 0 0 0.4rem; font-size: var(--fs-base); }
.p-manual-empty p { margin: 0; font-size: var(--fs-sm); color: var(--ink-muted); text-transform: none; }

/* Show the document: a panel at the side, over the form, with the output as
   it will go out and the document read as a document. */
dialog.p-doc-dialog {
  margin: 4vh 2vw 4vh auto; width: min(44rem, 94vw); max-height: 92vh; padding: 0;
  border: 1px solid var(--border); border-radius: 12px; overflow: auto; color: var(--ink);
}
dialog.p-doc-dialog::backdrop { background: rgba(23, 33, 31, 0.3); }
.p-doc-head { display: flex; justify-content: space-between; align-items: center; padding: 0.8rem 1rem; border-bottom: 1px solid var(--border); position: sticky; top: 0; background: var(--surface); }
.p-doc-close { border: 0; background: none; font-size: var(--fs-md); color: var(--ink-muted); cursor: pointer; }
.p-doc-tabs { padding: 0.6rem 1rem 0; }
.p-doc-body { padding: 0.8rem 1rem 1rem; font-size: var(--fs-sm); }
.p-doc-body h4 { margin: 0.9rem 0 0.4rem; font-size: var(--fs-sm); }
.p-doc-body h4:first-child { margin-top: 0; }
.p-doc-pre {
  margin: 0; padding: 0.8rem; background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
  font-size: var(--fs-xs); line-height: 1.5; white-space: pre-wrap; word-break: break-all;
}
.p-doc-facts { display: grid; grid-template-columns: 11rem 1fr; gap: 0.25rem 0.8rem; margin: 0; }
.p-doc-facts dt { color: var(--ink-muted); }
.p-doc-facts dd { margin: 0; }
.p-doc-lines { width: 100%; font-size: var(--fs-sm); }
.p-doc-lines th, .p-doc-lines td { padding: 0.25rem 0.4rem; }
.p-doc-lines .p-num { text-align: right; font-variant-numeric: tabular-nums; }

@media (max-width: 70rem) {
  .p-mt-bar { grid-template-columns: 1fr; }
  .p-mt-bottom { grid-template-columns: 1fr; }
  .p-mt-groups { grid-template-columns: 1fr !important; }
}

/* A file-name pattern and the picker that adds a value to it, side by side:
   the pattern is the field, the picker is a way into it. */
.b-filename-row {
  display: flex;
  gap: 0.4rem;
  align-items: center;
}
.b-filename-row input { flex: 1; min-width: 0; }
.b-filename-row select { flex: 0 0 auto; max-width: 11rem; }

.env-cell {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
.env-grid .env-default {
  flex-direction: row;
  align-items: center;
  gap: 0.3rem;
  font-size: var(--fs-2xs);
}
.env-grid .env-default input { margin: 0; }
.env-grid .env-default:has(input:disabled) { opacity: 0.45; }

.sidebar-nav {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: 0.5rem;
  flex: 1;
  overflow-y: auto;
}
.sidebar-item {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.55rem 0.65rem;
  border: none;
  background: none;
  border-radius: 6px;
  color: var(--ink-muted);
  font: inherit;
  font-size: var(--fs-base);
  cursor: pointer;
  text-align: left;
  white-space: nowrap;
  width: 100%;
}
.sidebar-item svg { width: 18px; height: 18px; flex: 0 0 auto; }
.sidebar-item:hover { background: var(--bg); color: var(--ink); }
.sidebar-item.active { background: var(--accent-tint); color: var(--accent); font-weight: 600; box-shadow: inset 3px 0 0 var(--accent); }
.sidebar-item.soon { color: var(--ink-muted); opacity: 0.75; }
.sidebar-item-label { flex: 1; overflow: hidden; text-overflow: ellipsis; }
.soon-badge {
  font-size: 0.65rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  background: var(--skipped-bg);
  color: var(--skipped);
  padding: 0.1rem 0.4rem;
  border-radius: 999px;
  flex: 0 0 auto;
}
.sidebar.collapsed .sidebar-item-label,
.sidebar.collapsed .soon-badge { display: none; }
.sidebar.collapsed .sidebar-item { justify-content: center; box-shadow: none; }

/* A group of menu entries that opens and closes (MENU in portal.js). */
.sidebar-group-head {
  display: flex;
  align-items: center;
  width: 100%;
  margin-top: 0.3rem;
  padding: 0.5rem 0.65rem;
  border: none;
  background: none;
  border-radius: 6px;
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-base);
  font-weight: 600;
  cursor: pointer;
  text-align: left;
  white-space: nowrap;
}
.sidebar-group-head:hover { background: var(--bg); }
.sidebar-group-head span { flex: 1; }
.sidebar-group-head svg { width: 14px; height: 14px; flex: 0 0 auto; color: var(--ink-muted); transition: transform 0.15s ease; }
.sidebar-group.open > .sidebar-group-head svg { transform: rotate(90deg); }
.sidebar-group-items {
  display: none;
  flex-direction: column;
  gap: 0.15rem;
  margin-left: 0.9rem;
  padding-left: 0.4rem;
  border-left: 1px solid var(--border);
}
.sidebar-group.open > .sidebar-group-items { display: flex; }
/* Collapsed to icons there is no room for a heading and nothing to fold: every
   screen stays one click away, with a line where each group begins. */
.sidebar.collapsed .sidebar-group-head { display: none; }
.sidebar.collapsed .sidebar-group-items {
  display: flex;
  margin: 0.3rem 0 0;
  padding: 0.3rem 0 0;
  border-left: none;
  border-top: 1px solid var(--border);
}

.sidebar-footer {
  border-top: 1px solid var(--border);
  padding: 0.75rem 0.65rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.sidebar-user {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
  font-size: var(--fs-sm);
}
.sidebar-username {
  color: var(--ink);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sidebar-logout {
  color: var(--ink-muted);
  text-decoration: none;
  font-size: var(--fs-xs);
}
.sidebar-logout:hover { color: var(--accent); }
.sidebar-2fa-link {
  color: var(--ink-muted);
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  font-size: var(--fs-xs);
  text-align: left;
  cursor: pointer;
}
.sidebar-2fa-link:hover { color: var(--accent); }
.sidebar.collapsed .sidebar-user { display: none; }

/* Two-factor recovery codes (views/twoFactorView.js) */
.tfa-codes {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.4rem;
  font-family: var(--font-mono);
  font-size: var(--fs-base);
  background: var(--panel-2, #f6f6f6);
  border-radius: 6px;
  padding: 0.8rem 1rem;
  margin: 0.75rem 0;
}

.sidebar-collapse-btn {
  border: 1px solid var(--border);
  background: var(--surface);
  border-radius: 6px;
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-muted);
  cursor: pointer;
  flex: 0 0 auto;
}
.sidebar-collapse-btn:hover { color: var(--accent); border-color: var(--accent); }
.sidebar-collapse-btn svg { width: 16px; height: 16px; }
.sidebar.collapsed .sidebar-collapse-btn svg { transform: rotate(180deg); }

.workspace {
  position: relative;
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  height: 100vh;
}

/* The tabs scroll; "Close all" does not. Two elements rather than one, because
   a button inside a horizontally scrolling strip goes wherever the scroll takes
   it - which, with enough tabs open, is off the end. */
.tab-bar {
  display: flex;
  align-items: stretch;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  flex: 0 0 auto;
  min-width: 0;
}
.tab-strip {
  display: flex;
  align-items: flex-end;
  gap: 0.2rem;
  padding: 0.5rem 0.75rem 0;
  /* Both axes stated. CSS says that when one axis is `auto` and the other is
     `visible`, the visible one computes to `auto` as well - so `overflow-x`
     alone was turning on vertical scrolling too, and `.tab.active`'s `top: 1px`
     was enough overflow to draw a scrollbar for. Hiding it clips exactly that
     1px, which is the bit meant to disappear under the content edge anyway. */
  overflow-x: auto;
  overflow-y: hidden;
  flex: 1 1 auto;
  min-width: 0;
  scrollbar-width: thin;
}
/* One tab open (just the section) - nothing to switch between, so hide the
   bar and let the view's own sub-tabs have the top of the workspace. */
.tab-strip--solo {
  display: none;
}
.tab {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.85rem;
  border: 1px solid var(--border);
  border-bottom: none;
  border-radius: 8px 8px 0 0;
  background: var(--surface);
  color: var(--ink-muted);
  font-size: var(--fs-sm);
  cursor: pointer;
  white-space: nowrap;
}
.tab.active {
  background: var(--bg);
  color: var(--ink);
  font-weight: 600;
  position: relative;
  top: 1px;
  box-shadow: inset 0 2px 0 var(--accent);
}
.tab-close {
  border: none;
  background: none;
  color: inherit;
  cursor: pointer;
  font-size: var(--fs-xs);
  line-height: 1;
  padding: 0.1rem;
  border-radius: 4px;
}
.tab-close:hover { background: var(--failed-bg); color: var(--failed); }

/* Outside the scrolling strip, at the far end, so it never looks like one more
   tab to switch to and never scrolls away from the tabs it closes. */
.tab-closeall {
  flex: 0 0 auto;
  align-self: center;
  margin: 0 0.75rem 0 0.5rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
  color: var(--ink-muted);
  font-size: var(--fs-xs);
  padding: 0.25rem 0.6rem;
  cursor: pointer;
  white-space: nowrap;
}
.tab-closeall:hover { color: var(--ink); border-color: var(--ink-muted); }

/* The logo mark turning about its upright axis while a page loads - so an
   empty screen says "working", not "broken". Fades in after a moment, so a
   view that loads at once never shows it. */
.portal-loader {
  position: absolute; top: 30vh; left: 0; right: 0;
  display: flex; justify-content: center;
  perspective: 400px; pointer-events: none;
  opacity: 0; animation: portal-loader-in 0.2s ease 0.15s forwards;
}
.portal-loader img { width: 3.5rem; height: auto; animation: portal-spin-y 1.6s cubic-bezier(0.45, 0, 0.55, 1) infinite; }
/* The first load covers the empty shell at once, in the page colour. */
.portal-loader-boot { position: fixed; inset: 0; align-items: center; background: var(--bg); z-index: 1000; opacity: 1; animation: none; }
@keyframes portal-loader-in { to { opacity: 1; } }
@keyframes portal-spin-y { from { transform: rotateY(0deg); } to { transform: rotateY(360deg); } }
@keyframes portal-pulse { 50% { opacity: 0.4; } }
@media (prefers-reduced-motion: reduce) {
  .portal-loader img { animation: portal-pulse 1.6s ease-in-out infinite; }
}
.tab-panel { position: relative; }

.tab-content {
  flex: 1;
  overflow: auto;
  min-height: 0;
  /* Breathing room below the tab bar (or the window top, when it's hidden),
     and a left gutter matching the tab bar's so each view's sub-tabs line up
     with the tabs above them. */
  padding: 0.6rem 0.75rem 0 0.75rem;
}

/* View chrome shared by list/detail views rendered into a tab */
.panel-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.85rem;
}
.panel-toolbar h2 { margin: 0; }
/* Its buttons side by side, whatever the text beside them takes. */
.panel-toolbar-buttons { display: flex; flex: none; gap: 0.6rem; margin-left: 1rem; }

/* The shape every button on the platform shares. .btn-danger is in here, and
   not in its own rule further down, for two reasons: it only overrides COLOUR,
   so on its own it used to leave a button wearing the browser's native chrome -
   square and mis-sized beside its properly shaped neighbours - and the shape has
   to be declared BEFORE .btn-small so that the small variant can still win on
   padding. A colour variant should not have to remember to bring the shape. */
.btn-primary, .btn-secondary, .btn-danger {
  font: inherit;
  font-size: var(--fs-sm);
  padding: 0.45rem 0.9rem;
  border: 1px solid var(--border);
  background: var(--surface);
  border-radius: 8px;
  cursor: pointer;
}
.btn-primary {
  border: 1px solid var(--accent);
  background: var(--accent);
  color: #fff;
  font-weight: 600;
}
.btn-primary:hover { opacity: 0.9; }
.btn-secondary {
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--ink);
}
.btn-secondary:hover { border-color: var(--accent); color: var(--accent); }
.btn-secondary:disabled, .btn-primary:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}
.btn-small {
  padding: 0.25rem 0.6rem;
  font-size: var(--fs-xs);
}

.subtabs {
  display: flex;
  gap: 0.3rem;
}
/* Words with a line under the open one, not a row of buttons: beside real
   buttons in a toolbar, button-shaped subtabs read as more things to press. */
.subtab {
  font: inherit;
  font-size: var(--fs-base);
  padding: 0.4rem 0.7rem;
  border: none;
  border-bottom: 2px solid transparent;
  background: none;
  color: var(--ink-muted);
  cursor: pointer;
}
.subtab:hover { color: var(--ink); }
.subtab.active {
  border-bottom-color: var(--accent);
  color: var(--accent);
  font-weight: 600;
}

/* Test projects (views/testingView.js): the Gantt chart, pies and roadblocks.
   Panels, tables, pills, the step bar and the boxes are the shared ones. */
.tp-halves { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
@media (max-width: 1100px) { .tp-halves { grid-template-columns: 1fr; } }
.tp-gantt { table-layout: fixed; }
.tp-gantt th:nth-child(1) { width: 17rem; }
.tp-gantt th:nth-child(2) { width: 7rem; }
.tp-gantt th:nth-child(3) { width: 10rem; }
.tp-gantt-cell { position: relative; }
.tp-weeks { display: flex; }
.tp-weeks span { flex: 1; font-weight: 400; border-left: 1px dashed var(--border); padding-left: 0.25rem; white-space: nowrap; overflow: hidden; }
.tp-track { position: relative; height: 1.6rem; }
.tp-seg { position: absolute; top: 0.3rem; height: 1rem; border-radius: 4px; font-size: var(--fs-2xs); line-height: 1rem; color: #fff; padding: 0 0.3rem; overflow: hidden; white-space: nowrap; box-sizing: border-box; }
.tp-seg.done { background: var(--delivered); }
.tp-seg.now { background: var(--accent); }
.tp-seg.plan { background: var(--border); color: var(--ink-muted); }
.tp-seg.late { background: var(--failed); }
.tp-finish { position: absolute; top: 0; bottom: 0; border-left: 2px solid var(--ink); }
.tp-today { position: absolute; top: -0.4rem; bottom: -0.4rem; border-left: 2px solid var(--filtered); }
.tp-legend { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem 0.9rem; margin: 0.6rem 0 0; }
.tp-key { display: inline-block; width: 0.8rem; height: 0.6rem; border-radius: 2px; margin-right: -0.5rem; }
.tp-key.done { background: var(--delivered); } .tp-key.now { background: var(--accent); }
.tp-key.plan { background: var(--border); } .tp-key.late { background: var(--failed); }
.tp-key.fin { width: 0; border-left: 2px solid var(--ink); border-radius: 0; }
.tp-key.today { width: 0; border-left: 2px solid var(--filtered); border-radius: 0; }
.tp-over { color: var(--failed); font-weight: 600; }
.tp-pie-row { display: flex; gap: 1.2rem; align-items: center; }
.tp-pie { position: relative; display: inline-block; width: 9rem; height: 9rem; border-radius: 50%; flex: none; }
.tp-pie::after { content: ""; position: absolute; inset: 2rem; border-radius: 50%; background: var(--surface); }
.tp-pie b { position: absolute; inset: 0; z-index: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: var(--fs-2xl); }
.tp-pie b small { font-size: var(--fs-2xs); font-weight: 400; color: var(--ink-muted); }
.tp-pie.small { width: 1.4rem; height: 1.4rem; vertical-align: middle; }
.tp-pie.small::after { inset: 0.4rem; }
.tp-keys { flex: 1; }
.tp-keys > div { display: flex; align-items: center; gap: 0.5rem; padding: 0.15rem 0; }
.tp-keys i { width: 0.7rem; height: 0.7rem; border-radius: 3px; display: inline-block; }
.tp-keys b { margin-left: auto; }
.tp-keys .tp-minis { flex-wrap: wrap; gap: 0.3rem 0.9rem; border-top: 1px solid var(--border); margin-top: 0.4rem; padding-top: 0.5rem; }
.tp-block { display: flex; gap: 0.6rem; align-items: flex-start; padding: 0.5rem 0; border-top: 1px solid var(--border); }
.tp-block:first-of-type { border-top: 0; }
.tp-block > div { flex: 1; }
.tp-block span:not(.tp-ic) { display: block; color: var(--ink-muted); font-size: var(--fs-sm); }
.tp-ic { width: 1.25rem; height: 1.25rem; border-radius: 50%; flex: none; display: inline-flex; align-items: center; justify-content: center; font-size: var(--fs-2xs); font-weight: 700; }
.tp-ic.bad { background: var(--failed-bg); color: var(--failed); }
.tp-ic.warn { background: var(--filtered-bg); color: var(--filtered); }
.tp-ic.now { background: var(--accent-tint); color: var(--accent); }
.tp-ic.ok { background: var(--delivered-bg); color: var(--delivered); }
.panel-toolbar h2 .tp-ic { margin-right: 0.35rem; vertical-align: 0.1rem; }
.tp-file { display: flex; align-items: center; gap: 0.7rem; padding: 0.5rem 0; border-top: 1px solid var(--border); }
.tp-file:first-of-type { border-top: 0; }
.tp-file > div { flex: 1; min-width: 0; }
.tp-file > div span { display: block; color: var(--ink-muted); font-size: var(--fs-sm); }
.tp-fi { width: 2.1rem; height: 2.4rem; flex: none; border: 1px solid var(--border); border-radius: 4px; background: var(--bg); color: var(--ink-muted); font-size: 0.62rem; font-weight: 700; display: inline-flex; align-items: center; justify-content: center; }
.tp-check { display: flex; align-items: center; gap: 0.5rem; text-transform: none; }
.tp-two-lists { display: grid; grid-template-columns: 1fr 2.6rem 1fr; gap: 1rem; align-items: start; }
.tp-arrows { display: flex; flex-direction: column; gap: 0.5rem; padding-top: 6rem; align-items: center; }
.tp-arrow { width: 2.3rem; padding: 0.35rem 0; font-size: var(--fs-md); }
.tp-on { background: var(--accent-tint); border-color: var(--accent); color: var(--accent); }
.tp-inline-select { font: inherit; font-size: var(--fs-sm); margin-left: 0.4rem; }
.tp-mini-steps { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
.tp-mini-steps span { flex: 1; padding: 0.5rem 0.8rem; border: 1px solid var(--border); border-radius: 8px; background: var(--surface); color: var(--ink-muted); }
.tp-mini-steps span.done { color: var(--delivered); }
.tp-mini-steps span.now { border-color: var(--accent); color: var(--accent); background: var(--accent-tint); font-weight: 600; }
.tp-opt { align-items: flex-start; padding: 0.3rem 0; }
.tp-opt input { margin-top: 0.2rem; }
.tp-msg { margin: 0 0 0.6rem; background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 0.6rem; font: 0.78rem/1.5 Consolas, "Cascadia Mono", monospace; white-space: pre-wrap; word-break: break-all; max-height: 26rem; overflow: auto; }
.tp-comment { display: flex; gap: 0.5rem; margin-bottom: 0.6rem; }
.tp-grow { flex: 1; }
.tp-ref { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.tp-ref select, .tp-ref input { width: auto; min-width: 12rem; }
.tp-comments td:first-child { width: 4.5rem; }
.tp-check input { width: auto; margin: 0; }
.status-pill.status-NOW { background: var(--accent-tint); color: var(--accent); }
tr.tp-selected td { background: var(--accent-tint); }
tr.tp-selected td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
tr.tp-selected .status-pill.status-NOW { background: var(--surface); }
td.tp-actions { white-space: nowrap; text-align: right; }
.tp-bar { display: flex; height: 0.5rem; width: 8rem; border-radius: 4px; overflow: hidden; background: var(--bg); margin-bottom: 0.2rem; }
.tp-bar .p { background: var(--delivered); } .tp-bar .f { background: var(--failed); } .tp-bar .i { background: var(--accent); }

/* Dashboard (views/dashboardView.js). Panels, tables, pills, subtabs and
   buttons are the shared ones; only what is particular to it is here. */
.dash-head { align-items: flex-end; }
.dash-head-actions { display: flex; align-items: center; gap: 1rem; }
.dash-boxes { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 1rem; }
.dash-box {
  --tone: var(--accent);
  --tone-bg: var(--accent-tint);
  display: flex;
  align-items: center;
  gap: 0.85rem;
  padding: 1rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface);
  box-shadow: var(--card-shadow);
  font: inherit;
  color: var(--ink);
  text-align: left;
  cursor: pointer;
  min-width: 0;
}
.dash-box:hover { border-color: var(--tone); }
.dash-box:disabled { cursor: default; }
.dash-box.ok { --tone: var(--delivered); --tone-bg: var(--delivered-bg); }
.dash-box.bad { --tone: var(--failed); --tone-bg: var(--failed-bg); }
.dash-box.warn { --tone: var(--filtered); --tone-bg: var(--filtered-bg); }
.dash-ring {
  width: 46px;
  height: 46px;
  border-radius: 50%;
  background: var(--tone-bg);
  color: var(--tone);
  display: grid;
  place-items: center;
  flex: 0 0 auto;
}
.dash-ring svg { width: 22px; height: 22px; }
.dash-box-text { display: flex; flex-direction: column; min-width: 0; }
.dash-box-label { color: var(--ink-muted); font-size: var(--fs-sm); }
.dash-box-value { font-size: var(--fs-2xl); font-weight: 700; letter-spacing: -0.02em; line-height: 1.2; }
.dash-box-value.word { font-size: var(--fs-xl); color: var(--tone); }
.dash-box-foot { color: var(--ink-muted); font-size: var(--fs-xs); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dash-box-foot .up { color: var(--delivered); }
.dash-box-foot .down { color: var(--failed); }
/* The Dashboard's widgets, in each person's order and widths (DashboardLayouts) - a quarter, half or all. */
.dash-board { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1rem; grid-auto-flow: dense; align-items: start; margin-bottom: 1rem; }
.dash-board > .dash-w { min-width: 0; margin: 0; }
.dash-board > .dash-w > .dash-w-body > .dash-boxes { margin-bottom: 0; }
@media (max-width: 1100px) { .dash-board { grid-template-columns: minmax(0, 1fr); } .dash-board > .dash-w { grid-column: auto !important; } }
.dash-arrange-bar { display: flex; align-items: center; gap: 1rem; background: var(--accent-tint); border: 1px solid var(--accent); border-radius: 10px; padding: 0.6rem 0.9rem; margin-bottom: 1rem; }
.dash-arrange-bar[hidden] { display: none; }
.dash-arrange-bar > span:first-child { flex: 1; }
.dash-arranging .dash-w { border: 1px dashed var(--accent); resize: vertical; overflow: hidden; min-height: 120px; }
/* A widget given a height: its content scrolls inside it. */
.dash-board > .dash-w.dash-fixed { display: flex; flex-direction: column; }
.dash-board > .dash-w.dash-fixed > .dash-w-body { flex: 1; min-height: 0; overflow: auto; }
.dash-w.dash-fixed .dash-rows { max-height: none; }
.dash-arranging .dash-w-body { pointer-events: none; opacity: 0.85; }
.dash-arranging .dash-w.dash-hidden { opacity: 0.45; }
.dash-arranging .dash-w.dash-dragging { opacity: 0.35; }
.dash-arranging .dash-w.dash-drop { box-shadow: -4px 0 0 var(--accent); }
.dash-grip { cursor: grab; color: var(--ink-muted); letter-spacing: -2px; margin-right: 0.4rem; user-select: none; }
.dash-arrange-tools { display: inline-flex; gap: 0.3rem; }
/* The Business processes widget: a row per process and partner, a tile per step joined by arrows, each tinted by how
   its documents are doing and saying what is wrong in words. */
.dash-lane { display: grid; grid-template-columns: 11rem minmax(0, 1fr); gap: 0.8rem; align-items: center; padding: 0.55rem 0; border-bottom: 1px solid var(--border); }
.dash-lane:last-of-type { border-bottom: none; }
.dash-lane-name span { display: block; font-size: var(--fs-sm); color: var(--ink-muted); }
.dash-lane-name b { display: block; }
.dash-w[data-size="1"] .dash-lane { grid-template-columns: minmax(0, 1fr); gap: 0.35rem; }
.dash-flow { display: flex; align-items: center; min-width: 0; }
.dash-tile { flex: 0 1 8.6rem; min-width: 0; padding: 0.45rem 0.65rem; border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface); box-shadow: 0 1px 2px rgba(16, 26, 46, 0.05); color: var(--ink); font: inherit; text-align: left; cursor: pointer; }
.dash-tile:hover { border-color: var(--accent); }
.dash-tile.bad { background: #fffafa; border-color: #e7b9bd; }
.dash-tile.warn { background: #fffcf6; border-color: #ecd3ad; }
.dash-tile-top { display: flex; align-items: center; gap: 0.4rem; min-width: 0; }
.dash-tile-top b { flex: 1; min-width: 0; font-size: var(--fs-sm); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dash-mark { flex: none; width: 1rem; height: 1rem; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center;
  font-size: 0.62rem; font-weight: 800; font-style: normal; color: #fff; background: var(--delivered); }
.dash-tile.first .dash-mark { background: var(--accent); }
/* The start's triangle drawn, not a character: the ▶ glyph sits off the circle's centre in most fonts. A triangle
   looks centred when its middle, not its box, is - hence the nudge to the right. */
.dash-tile.first .dash-mark::before { content: ""; border-style: solid; border-width: 0.22rem 0 0.22rem 0.36rem;
  border-color: transparent transparent transparent #fff; margin-left: 0.1rem; }
.dash-tile.bad .dash-mark { background: var(--failed); }
.dash-tile.warn .dash-mark { background: var(--filtered); }
.dash-tile-n { display: block; font-size: var(--fs-lg); font-weight: 700; line-height: 1.25; white-space: nowrap; }
.dash-tile-n small { font-size: var(--fs-2xs); font-weight: 400; color: var(--ink-muted); }
.dash-tile-says { display: block; font-size: var(--fs-xs); color: var(--ink-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dash-tile.bad .dash-tile-says { color: var(--failed); font-weight: 600; }
.dash-tile.warn .dash-tile-says { color: var(--filtered); font-weight: 600; }
.dash-tile .dash-watched { font-size: var(--fs-2xs); font-weight: 700; color: var(--accent); }
/* The line stretches with the room; the head is its own fixed shape, so a long arrow never distorts. */
.dash-arrow { flex: 1 1 1.5rem; min-width: 1.25rem; height: 10px; position: relative; margin: 0 0.15rem; }
.dash-arrow::before { content: ""; position: absolute; left: 0; right: 7px; top: 4px; border-top: 2px solid #cfd8e6; }
.dash-arrow::after { content: ""; position: absolute; right: 0; top: 0; border-style: solid; border-width: 5px 0 5px 8px;
  border-color: transparent transparent transparent #cfd8e6; }
.dash-lane-key { display: flex; gap: 1rem; flex-wrap: wrap; font-size: var(--fs-sm); color: var(--ink-muted); margin-top: 0.6rem; align-items: center; }
/* A business process step's link (process-link-mockup.html): its pill and line in the steps table, and the Link pop-up. */
.bp-time { display: flex; align-items: center; gap: 0.3rem; }
.bp-time + .bp-time { margin-top: 0.3rem; }
.bp-time input { width: 4.5rem; }
/* A step's link in the table: one underlined word, the whole link on hover. */
.bp-link-text { background: none; border: none; padding: 0; font: inherit; color: var(--accent); text-decoration: underline; cursor: pointer; }
.bp-link-text.bp-unlinked { color: var(--filtered); }
.bp-link-text:disabled { color: var(--ink-muted); cursor: default; }
.portal-dialog.bp-link-dialog { max-width: 860px; padding: 0; }
.bp-link-body.detail-form { max-width: none; padding: 0.9rem 1.2rem 1rem; max-height: 65vh; overflow: auto; }
.bp-link-foot { margin: 0; padding: 0.75rem 1.2rem; border-top: 1px solid var(--border); align-items: center; }
.bp-link-foot .readonly-note { margin-right: auto; text-transform: none; }
.bp-sect { margin: 1rem 0 0.2rem; font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: 0.04em; color: var(--ink-muted); }
.bp-pair { display: grid; grid-template-columns: 1fr 2rem 1fr; gap: 0.5rem; align-items: start; margin-top: 0.8rem; }
.bp-eq { text-align: center; padding-top: 2rem; color: var(--ink-muted); font-size: var(--fs-lg); }
.bp-side { border: 1px solid var(--border); border-radius: 8px; padding: 0 0.75rem 0.6rem; display: flex; flex-direction: column; gap: 0.2rem; }
.bp-suggest { display: flex; align-items: center; gap: 0.6rem; margin-top: 0.3rem; }
.bp-two { display: grid; grid-template-columns: 1fr 1fr; gap: 0.6rem; }
.bp-two > div { display: flex; flex-direction: column; }
.bp-trace { background: var(--bg); border-radius: 8px; padding: 0.55rem 0.75rem; margin-top: 0.6rem; font-size: var(--fs-sm); line-height: 1.8; }
.bp-match { color: var(--delivered); font-weight: 600; font-size: var(--fs-xs); }
/* Process watch: its counts above the list. */
.pw-stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1rem; margin-bottom: 1rem; }
@media (max-width: 900px) { .pw-stats { grid-template-columns: minmax(0, 1fr); } }
.pw-stat { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 0.8rem 1rem; }
.pw-stat b { display: block; font-size: var(--fs-2xl); }
.pw-stat span { color: var(--ink-muted); font-size: var(--fs-sm); }
.pw-stat.bad b, .pw-over { color: var(--failed); font-weight: 600; }
.pw-stat.warn b { color: var(--filtered); }
.dash-grid3 { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr) minmax(0, 1.1fr); gap: 1rem; margin-bottom: 1rem; align-items: start; }
@media (max-width: 1250px) { .dash-grid3 { grid-template-columns: minmax(0, 1fr); } }
/* The middle row: equal shares, however many of its panels this account has. */
.dash-even { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1rem; margin-bottom: 1rem; align-items: start; }
.dash-even > .panel, .dash-grid3 > .panel { min-width: 0; }
.dash-keys { font-size: var(--fs-xs); color: var(--ink-muted); }
.dash-keys i, .dash-legend i, .dash-channels i { display: inline-block; width: 9px; height: 9px; border-radius: 50%; margin: 0 0.35rem 0 0.8rem; }
.dash-chart { width: 100%; height: 215px; display: block; }
.dash-grid { stroke: var(--border); stroke-width: 1; }
.dash-axis { fill: var(--ink-muted); font-size: 11px; }
.dash-line { fill: none; stroke: var(--accent); stroke-width: 2.4; stroke-linejoin: round; vector-effect: non-scaling-stroke; }
.dash-line.failed { stroke: var(--failed); stroke-width: 2; stroke-dasharray: 5 4; }
.dash-slot { fill: transparent; cursor: pointer; }
.dash-slot:hover { fill: var(--accent); fill-opacity: 0.07; }
.dash-ring-row { display: flex; align-items: center; gap: 1.1rem; }
.dash-donut { width: 132px; height: 132px; flex: 0 0 auto; }
.dash-donut-total { font-size: 24px; font-weight: 700; fill: var(--ink); }
.dash-legend { flex: 1; display: grid; gap: 0.55rem; font-size: var(--fs-sm); color: var(--ink-muted); }
.dash-legend div { display: flex; align-items: center; }
.dash-legend i { margin-left: 0; }
.dash-legend b { margin-left: auto; color: var(--ink); }
.dash-channels { display: flex; flex-wrap: wrap; gap: 0.3rem 0.4rem; border-top: 1px solid var(--border); margin-top: 0.9rem; padding-top: 0.7rem; font-size: var(--fs-sm); cursor: pointer; }
.dash-channels i { margin-left: 0; }
.dash-attn { border-color: var(--failed-bg); }
/* Every list on the dashboard stops at a height and scrolls from there: on a
   bad day, or for a customer with fifty workflows, one list would otherwise
   push everything under it off the screen. Three rows for the two lists of
   things to do and for the connection tiles, about ten for the table. */
.dash-rows { max-height: 11.3rem; overflow-y: auto; padding-right: 0.3rem; }
.dash-conns { max-height: 11.8rem; overflow-y: auto; padding-right: 0.3rem; }
.dash-table-scroll { max-height: 27rem; }
.dash-rows { display: grid; }
.dash-row {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  padding: 0.55rem 0;
  border: none;
  border-bottom: 1px solid var(--border);
  background: none;
  font: inherit;
  color: var(--ink);
  text-align: left;
  cursor: pointer;
  min-width: 0;
}
.dash-row:last-child { border-bottom: none; }
.dash-row:hover b { color: var(--accent); }
.dash-row-text { flex: 1; min-width: 0; display: flex; flex-direction: column; font-size: var(--fs-base); }
.dash-row-text span { color: var(--ink-muted); font-size: var(--fs-xs); }
.dash-kind { width: 28px; height: 28px; border-radius: 8px; background: var(--accent-tint); color: var(--accent); display: grid; place-items: center; font-weight: 700; font-size: var(--fs-sm); flex: 0 0 auto; }
.dash-conns { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 0.5rem; }
.dash-conn {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: var(--surface);
  font: inherit;
  color: var(--ink);
  text-align: left;
  cursor: pointer;
  min-width: 0;
}
.dash-conn:hover { border-color: var(--accent); }
.dash-conn b { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dash-dot { width: 9px; height: 9px; border-radius: 50%; margin-top: 0.35rem; flex: 0 0 auto; }
.dash-dot.OK { background: var(--delivered); }
.dash-dot.FAILED { background: var(--failed); }
.dash-dot.LATE { background: var(--filtered); }
.dash-dot.UNUSED { border: 1.5px solid var(--ink-muted); }
/* The Workflows list's last column: the Dashboard's dot, in a table cell. A
   workflow that is not running blinks, so it is found at a glance. */
.wf-run { width: 1%; text-align: center; white-space: nowrap; }
.wf-run .dash-dot { display: inline-block; margin: 0; vertical-align: middle; }
.dash-dot.wf-stopped { animation: wf-stopped-blink 1.2s ease-in-out infinite; }
@keyframes wf-stopped-blink { 50% { opacity: 0.2; } }
@media (prefers-reduced-motion: reduce) { .dash-dot.wf-stopped { animation: none; } }
.dash-sev-CRITICAL { background: var(--failed-bg); color: var(--failed); }
.dash-sev-WARNING { background: var(--filtered-bg); color: var(--filtered); }
.dash-sev-HEALTHY { background: var(--delivered-bg); color: var(--delivered); }
.dash-sev-QUIET { background: var(--skipped-bg); color: var(--skipped); }
.dash-num { text-align: right; font-variant-numeric: tabular-nums; }
.dash-table thead th.dash-num { text-align: right; }
.dash-spark { width: 70px; height: 22px; display: block; }
.dash-spark polyline { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.dash-gauges { display: grid; grid-template-columns: repeat(auto-fit, minmax(70px, 1fr)); gap: 0.4rem; text-align: center; font-size: var(--fs-xs); color: var(--ink-muted); }
.dash-gauge svg { width: 58px; height: 58px; display: block; margin: 0 auto 0.2rem; }
.dash-gauge circle { fill: none; stroke-width: 6; }
.dash-gauge .track { stroke: var(--border); }
.dash-gauge .fill { stroke: var(--accent); stroke-linecap: round; transform: rotate(-90deg); transform-origin: 30px 30px; }
.dash-gauge .fill.hot { stroke: var(--filtered); }
.dash-gauge text { text-anchor: middle; font-size: 13px; font-weight: 700; fill: var(--ink); }

/* Integrations cockpit */
.env-badge {
  display: inline-block;
  padding: 0.1rem 0.45rem;
  border-radius: 999px;
  font-size: var(--fs-2xs);
  border: 1px solid var(--border, #cbd5e1);
  color: var(--muted, #64748b);
  background: #fff;
  white-space: nowrap;
}
.env-badge.live.env-dev  { background: #e6f5ec; border-color: #1c7c4d; color: #14603b; }
.env-badge.live.env-test { background: #e9edf0; border-color: #55605f; color: #3d4746; }
.env-badge.live.env-prod { background: #fde9e9; border-color: #d64545; color: #a83232; }
.int-steps { margin: 0.3rem 0 0; padding-left: 1.4rem; }
.int-steps li { margin: 0.2rem 0; }

/* Cockpit tab bodies: each topic is its own section.panel (see .int-tab
   above), so headings pick up the same section.panel h2 style every other
   panel in the app uses - deliberately not a one-off style for this screen,
   so it reads as consistent with the rest of the app rather than another
   variant. Only the content rhythm inside each card is specific here. */
.int-tab p { margin: 0.35rem 0; font-size: var(--fs-sm); }
.int-tab ul, .int-tab ol { margin: 0.35rem 0; }
.int-tab li { margin: 0.25rem 0; font-size: var(--fs-sm); }
.int-tab table { width: 100%; border-collapse: collapse; margin: 0.4rem 0 0; font-size: var(--fs-sm); }.int-tab th {
  text-align: left;
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-muted);
  font-weight: 600;
  padding: 0.3rem 0.6rem 0.3rem 0;
  border-bottom: 1px solid var(--border);
}
.int-tab td { padding: 0.35rem 0.6rem 0.35rem 0; border-bottom: 1px solid var(--border); vertical-align: top; }
.int-tab tr:last-child td { border-bottom: none; }
.int-tab .readonly-note { font-style: normal; }
/* Small neutral inline chip - replaces ad-hoc coloured spans mid-sentence */
.int-chip {
  display: inline-block;
  padding: 0.05rem 0.4rem;
  border-radius: 4px;
  font-size: var(--fs-xs);
  border: 1px solid var(--border);
  color: var(--ink-muted);
  background: var(--bg);
  white-space: nowrap;
}
.int-chip.warn { border-color: var(--failed); color: var(--failed); background: var(--failed-bg); }
/* Inline error text inside a cockpit table cell - red, but no box */
.int-err { color: var(--failed); font-size: var(--fs-sm); }

.row-actions {
  display: flex;
  gap: 0.4rem;
  justify-content: flex-end;
}

.detail-form {
  display: flex;
  flex-direction: column;
  max-width: 420px;
}
.detail-form[hidden] {
  display: none;
}
.detail-form label {
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-muted);
  margin-top: 0.75rem;
}
.detail-form input, .detail-form select, .detail-form textarea,
#ai-policy-rows select, #tpd-set-cols input, #tpd-set-cols select, #tpd-set-rows input,
#rl-rules select, #rl-amb select, #rl-hand input, #li-sends select, #li-sends input {
  font: inherit;
  font-size: var(--fs-base);
  padding: 0.5rem 0.65rem;
  margin-top: 0.3rem;
  border: 1px solid var(--border);
  border-radius: 8px;
}
.detail-form input:focus, .detail-form select:focus, .detail-form textarea:focus,
#ai-policy-rows select:focus, #tpd-set-cols input:focus, #tpd-set-cols select:focus, #tpd-set-rows input:focus,
#rl-rules select:focus, #rl-amb select:focus, #rl-hand input:focus, #li-sends select:focus, #li-sends input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-tint);
}
.detail-form textarea {
  resize: vertical;
}
/* The AI levels are dropdowns in a table, not in a form; same look, no gap above. */
#ai-policy-rows select { margin-top: 0; background-color: var(--surface); color: var(--ink); }
/* A test data set's columns and rows are edited in their tables; the same look. */
#tpd-set-cols input, #tpd-set-cols select, #tpd-set-rows input { margin-top: 0; background-color: var(--surface); color: var(--ink); }
/* Record links' rules and review are picked in their tables; the same look. */
#rl-rules select, #rl-amb select, #rl-hand input { margin-top: 0; background-color: var(--surface); color: var(--ink); }
/* A live integration's What it sends rows are picked in their table; the same look. */
#li-sends select, #li-sends input { margin-top: 0; background-color: var(--surface); color: var(--ink); }
.detail-form input[readonly], .detail-form select:disabled,
.detail-form textarea[readonly], .detail-form input:disabled, #ai-policy-rows select:disabled,
#li-sends select:disabled, #li-sends input:disabled {
  background: var(--bg);
  color: var(--ink-muted);
}
.readonly-note {
  margin: 0.75rem 0 0;
  font-size: var(--fs-sm);
  color: var(--ink-muted);
  font-style: italic;
}
/* An integration's readiness: the task catalogue's checks against one
   workflow. A list, not a grid - each row is a sentence (what it is, what was
   found, whose information it needs) and a table would cut the one part that
   carries the meaning down to a column width. */
.plan-heading {
  margin: 1.25rem 0 0.4rem;
  font-size: var(--fs-base);
}
.plan-heading .readonly-note {
  margin-left: 0.4rem;
  font-style: normal;
}
.plan-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.plan-row {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  padding: 0.4rem 0;
  border-top: 1px solid var(--line, rgba(127, 127, 127, 0.18));
}
.plan-row:first-child {
  border-top: none;
}
/* The catalogue id, shown on purpose: it is the id the documentation uses, so
   "D5 is wrong" is a sentence that means something to both ends. */
.plan-id {
  flex: 0 0 2.2rem;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--ink-muted);
}
.plan-body {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}
.plan-label {
  font-size: var(--fs-base);
}
/* What was actually found, which is the part worth reading - never a
   restatement of the label. */
.plan-detail {
  font-size: var(--fs-sm);
  color: var(--ink-muted);
}
.plan-owner {
  flex: 0 0 auto;
  font-size: var(--fs-xs);
  color: var(--ink-muted);
  white-space: nowrap;
}
/* Required and missing: the rows the screen exists to surface, and they have
   to be findable in a list of twenty-seven. Red on the mark, the label AND the
   detail, because a single red glyph at the left edge is easy to scan past.
   Only REQUIRED - an advisory item is not a fault and must not read like one,
   which is the whole reason the row carries its importance as a class. */
.plan-row.plan-missing.plan-required .plan-id,
.plan-row.plan-missing.plan-required .plan-label,
.plan-row.plan-missing.plan-required .plan-detail {
  color: var(--failed);
}
.plan-row.plan-missing.plan-required .plan-id,
.plan-row.plan-missing.plan-required .plan-label {
  font-weight: 700;
}
/* A left edge, so a block of stages can be scanned without reading a word. */
.plan-row.plan-missing.plan-required {
  border-left: 3px solid var(--failed);
  padding-left: 0.5rem;
  margin-left: -0.5rem;
}
/* Missing but advisory: worth noticing, not worth alarm. */
.plan-row.plan-missing.plan-advisory .plan-label {
  font-weight: 600;
}
.plan-row.plan-ok .plan-id {
  color: var(--delivered);
}
.plan-row.plan-too_early .plan-label,
.plan-row.plan-not_applicable .plan-label,
.plan-row.plan-not_applicable .plan-detail {
  color: var(--ink-muted);
}
/* The stage heading's own count, so a collapsed glance down the page still
   says which stage the trouble is in. */
.plan-heading .plan-missing-count {
  color: var(--failed);
  font-weight: 600;
  font-style: normal;
  margin-left: 0.4rem;
  font-size: var(--fs-sm);
}
.plan-details {
  margin: 0.75rem 0 0;
}
.plan-details > summary {
  cursor: pointer;
  font-size: var(--fs-sm);
  color: var(--ink-muted);
  padding: 0.3rem 0;
}
@media (max-width: 700px) {
  .plan-row { flex-wrap: wrap; }
  .plan-owner { flex-basis: 100%; }
}

/* The union of a user's roles, on the Settings → Users detail. A list rather
   than a grid: it says what this account can do, not what it can't, and the
   sections it has nothing in are simply absent. */
.effective-access {
  margin: 0.25rem 0 0;
  padding: 0 0 0 1.1rem;
  font-size: var(--fs-sm);
}
.effective-access li {
  margin: 0.15rem 0;
}
.effective-access .readonly-note {
  margin: 0 0 0 0.4rem;
}

/* A name that would have been a link into another section, for an account whose
   role can't read that section. It keeps the name - which is information the
   screen it's on is entitled to show - and stops pretending to be clickable.
   The reason is on the element's title. */
.link-unavailable {
  color: var(--ink-muted);
  text-decoration: none;
  cursor: default;
}
.detail-error {
  background: var(--failed-bg);
  color: var(--failed);
  border: 1px solid var(--failed);
  border-radius: 6px;
  padding: 0.55rem 0.8rem;
  font-size: var(--fs-sm);
  margin-top: 1rem;
}
/* An identity and the scheme it belongs to are one fact, so they sit on one
   line: an interchange ID without its qualifier says nothing. */
.detail-form .pair-row {
  display: flex;
  gap: 0.5rem;
}
.detail-form .pair-row input:first-child { flex: 2 1 0; }
.detail-form .pair-row input:last-child { flex: 1 1 0; }
@media (max-width: 560px) {
  .detail-form .pair-row { flex-direction: column; }
}
/* The generated message, a segment to a line. Real EDI is one unbroken string
   and a wrapped blob of it cannot be read; every EDI tool shows it split, and
   the line break lives in the stylesheet rather than in the text so that what
   gets copied is still what a partner receives. */
.es-edi {
  margin: 0;
  padding: 0.7rem 0.9rem;
  background: var(--panel-alt, rgba(127, 127, 127, 0.08));
  border-radius: 4px;
  max-height: 26rem;
  overflow: auto;
  font-size: var(--fs-sm);
  line-height: 1.6;
}
.es-edi-line {
  display: block;
  white-space: pre-wrap;
  word-break: break-all;
}
.es-edi-line:nth-child(even) { background: rgba(127, 127, 127, 0.06); }

/* A row the partner's guideline says is not answered yet, and the note saying
   why. Marked on the row rather than listed beside the table: a list means
   reading "RFF+AAK sends nothing" and then hunting for RFF+AAK. */
tr.es-marked td { background: rgba(217, 139, 0, 0.07); }
.es-mark {
  display: block;
  margin-top: 0.3rem;
  font-size: var(--fs-2xs);
  line-height: 1.35;
  text-transform: none;
  letter-spacing: 0;
  border-left: 3px solid #d98b00;
  padding-left: 0.45rem;
  color: var(--ink-muted, #64748b);
}
.es-mark.stopping { border-left-color: var(--danger, #dc2626); }
.detail-form label.detail-check {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  margin-top: 0.6rem;
  font-size: var(--fs-sm);
  color: var(--ink);
  text-transform: none;
  letter-spacing: 0;
}
.detail-check input { margin-top: 0.15rem; width: auto; }

.form-actions {
  display: flex;
  gap: 0.6rem;
  margin-top: 1.25rem;
}

.reload-prompt {
  max-width: 420px;
}
.reload-prompt p {
  font-size: var(--fs-base);
  color: var(--ink);
  margin: 0;
}

/* App-styled confirm/alert (util.js portalConfirm/portalAlert), replacing
   window.confirm/window.alert everywhere - those render as a bare OS dialog
   that looks nothing like the rest of the portal. */
.portal-dialog-overlay {
  position: fixed;
  inset: 0;
  background: rgba(23, 33, 31, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  padding: 1rem;
}
.portal-dialog {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 12px 32px rgba(23, 33, 31, 0.22);
  max-width: 440px;
  width: 100%;
  padding: 1.25rem 1.4rem;
}
.portal-dialog p {
  margin: 0;
  color: var(--ink);
  font-size: var(--fs-base);
  line-height: 1.45;
  white-space: pre-line;
}
.portal-dialog-input {
  width: 100%;
  margin-top: 0.75rem;
  padding: 0.5rem 0.65rem;
  font: inherit;
  font-size: var(--fs-base);
  border: 1px solid var(--border);
  border-radius: 6px;
}
.portal-dialog-actions {
  margin-top: 1.1rem;
  justify-content: flex-end;
}

/* Help: a "?" beside what it explains (util.js helpTip), its text in a bubble pointing at that field, which is
   ringed while it is open. The rule for every screen, instead of small grey text under fields. */
.help-tip {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 17px; height: 17px; margin-left: 6px; padding: 0; vertical-align: middle;
  border-radius: 50%; border: 1px solid var(--accent); background: var(--surface); color: var(--accent);
  font-family: inherit; font-size: 11px; font-weight: 700; line-height: 1; text-transform: none; letter-spacing: 0; cursor: pointer;
}
.help-tip:hover, .help-tip.pinned { background: var(--accent); color: #fff; }
.help-tip.note-tip { border-radius: 4px; }
.help-pop {
  position: fixed; z-index: 1100; max-width: 330px; padding: 10px 13px;
  background: var(--surface); border: 1px solid #c9d1de; border-radius: 9px;
  box-shadow: 0 10px 28px rgba(16, 26, 46, 0.2); color: var(--ink); font-size: var(--fs-sm); line-height: 1.5;
}
.help-pop p { margin: 0; }
/* A "?" on its own line under a field (the Pipeline editor's explanations): small, at the right, taking little room. */
.help-line { display: flex; justify-content: flex-end; margin: 0.15rem 0 0.1rem; }
.help-pop p + p { margin-top: 7px; }
.help-pop::before {
  content: ""; position: absolute; top: -7px; left: var(--arrow-x, 16px); width: 12px; height: 12px;
  background: var(--surface); border-left: 1px solid #c9d1de; border-top: 1px solid #c9d1de; transform: rotate(45deg);
}
.help-pop.above::before { top: auto; bottom: -7px; transform: rotate(225deg); }
.help-target { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 6px; }

.coming-soon {
  padding: 3rem 2rem;
  text-align: center;
  color: var(--ink-muted);
}
.coming-soon h2 { margin: 0 0 0.5rem; color: var(--ink); }

.m-danger, .btn-danger, .wf-promote-cell > .promote-btn.btn-danger { color: var(--failed); border-color: var(--failed); }
.m-danger:hover, .btn-danger:hover, .wf-promote-cell > .promote-btn.btn-danger:hover { background: var(--failed-bg); border-color: var(--failed); color: var(--failed); }

/* Workflow pipeline editor: hosts the compiled views/workflowEditor.js bundle.
   The bundle's own .wfx shell is calc(100vh - 42px); bound it when embedded
   below the Channels panel so the detail page doesn't grow a huge scroll. */
.wf-embed {
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
}
.wf-embed .wfx { height: 68vh; min-height: 420px; }

/* Fallback while the compiled mapper bundle loads (its own CSS then takes over) */
.mapper-empty { padding: 2rem; text-align: center; color: var(--ink-muted); font-size: var(--fs-base); }
.mapper-empty code { font-size: 0.85em; }

/* Login page */
body.login-body {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
}
.login-wrap { width: min(360px, 90vw); }
.login-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 2rem 1.75rem;
}
.login-card h1 {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin: 0 0 0.25rem;
  font-size: var(--fs-xl);
  font-weight: 600;
  line-height: 26px;
  letter-spacing: -0.01em;
  color: var(--brand-ink);
}
.login-sub {
  margin: 0 0 1.25rem;
  color: var(--ink-muted);
  font-size: var(--fs-base);
}
.login-error {
  background: var(--failed-bg);
  color: var(--failed);
  border: 1px solid var(--failed);
  border-radius: 6px;
  padding: 0.55rem 0.8rem;
  font-size: var(--fs-sm);
  margin-bottom: 1rem;
}
.login-card form {
  display: flex;
  flex-direction: column;
}
.login-card label {
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-muted);
  margin-top: 0.6rem;
}
.login-card input {
  font: inherit;
  font-size: var(--fs-base);
  padding: 0.5rem 0.65rem;
  margin-top: 0.3rem;
  border: 1px solid var(--border);
  border-radius: 6px;
}
.login-card button {
  margin-top: 1.25rem;
  font: inherit;
  font-size: var(--fs-base);
  padding: 0.55rem 0.9rem;
  border: 1px solid var(--accent);
  border-radius: 6px;
  background: var(--accent);
  color: #fff;
  cursor: pointer;
}
.login-card button:hover { opacity: 0.9; }

/* API connections (views/apisView.js) */
.api-ops { display: flex; flex-direction: column; gap: 0.3rem; }
.api-op {
  display: flex; align-items: baseline; gap: 0.6rem; width: 100%; text-align: left;
  padding: 0.45rem 0.6rem; border: 1px solid var(--border); border-radius: 7px;
  background: var(--surface); cursor: pointer; font: inherit; color: inherit;
}
.api-op:hover { border-color: var(--accent); }
.api-op.active { border-color: var(--accent); background: var(--accent-tint); }
.api-method {
  font-size: var(--fs-2xs); font-weight: 700; letter-spacing: 0.03em;
  padding: 0.1rem 0.4rem; border-radius: 4px; background: var(--bg); color: var(--ink-muted);
  min-width: 3.4rem; text-align: center; flex: 0 0 auto;
}
.api-method.m-GET { background: var(--delivered-bg); color: var(--delivered); }
.api-method.m-POST { background: var(--accent-tint); color: var(--accent); }
.api-method.m-PUT, .api-method.m-PATCH { background: var(--filtered-bg); color: var(--filtered); }
.api-method.m-DELETE { background: var(--failed-bg); color: var(--failed); }
.api-path { font-family: var(--font-mono); font-size: var(--fs-sm); }
.api-op-summary { color: var(--ink-muted); font-size: var(--fs-sm); }
.api-target {
  font-family: var(--font-mono);
  font-size: var(--fs-sm); color: var(--ink-muted); word-break: break-all; margin: 0.25rem 0 0;
}
/* auth-type field groups: let their label/input children flow into the
   parent .detail-form flex column, and honour [hidden]. */
.auth-fields { display: contents; }
.auth-fields[hidden] { display: none; }
.api-connect-row { display: flex; align-items: center; gap: 0.6rem; margin-top: 0.7rem; }
.api-redirect { display: flex; align-items: center; gap: 0.5rem; margin-top: 0.3rem; }
.api-redirect input {
  flex: 1; min-width: 0; margin-top: 0 !important;
  font-family: var(--font-mono); font-size: var(--fs-sm);
}
.api-redirect .btn-small { flex: 0 0 auto; }

#api-invoke-fields { max-width: 640px; }
#api-form textarea, #api-invoke-fields textarea {
  font: inherit; font-size: var(--fs-sm); padding: 0.5rem 0.65rem; margin-top: 0.3rem;
  border: 1px solid var(--border); border-radius: 6px; resize: vertical;
  font-family: var(--font-mono);
}
.api-out { margin-top: 1rem; }
.api-pre {
  background: var(--bg); border: 1px solid var(--border); border-radius: 6px;
  padding: 0.7rem 0.85rem; font-size: var(--fs-sm); overflow: auto; max-height: 420px;
  white-space: pre-wrap; word-break: break-word;
}
.api-out details { margin: 0.4rem 0; }
.api-out summary { cursor: pointer; font-size: var(--fs-sm); color: var(--ink-muted); }

/* Databases: read-only query result grid */
#db-form textarea, #db-sql {
  font: inherit; font-size: var(--fs-sm); padding: 0.5rem 0.65rem; margin-top: 0.3rem;
  border: 1px solid var(--border); border-radius: 6px; resize: vertical;
  font-family: var(--font-mono);
}
.db-result-scroll { overflow: auto; max-height: 420px; border: 1px solid var(--border); border-radius: 6px; margin-top: 0.5rem; }
/* An ordinary table that scrolls in place once it is long, its headings
   staying put. The height is set by whoever fills it (so many rows, not so
   many pixels) - see maintenanceView.js. The table's own overflow has to be
   let go of, or the headings are clipped instead of held. */
.table-scroll { overflow-y: auto; }
.table-scroll table { overflow: visible; }
.table-scroll thead th { position: sticky; top: 0; z-index: 1; }
table.db-result { border-collapse: collapse; font-size: var(--fs-sm); width: 100%; }
table.db-result th, table.db-result td {
  border: 1px solid var(--border); padding: 0.35rem 0.55rem; text-align: left;
  white-space: nowrap; max-width: 320px; overflow: hidden; text-overflow: ellipsis;
}
table.db-result th { background: var(--bg); position: sticky; top: 0; }

/* Translations: entries table + inline add row */
table.tr-entries { border-collapse: collapse; width: 100%; font-size: var(--fs-sm); margin-top: 0.5rem; }
table.tr-entries th, table.tr-entries td {
  border: 1px solid var(--border); padding: 0.35rem 0.55rem; text-align: left;
}
table.tr-entries th { background: var(--bg); font-weight: 600; }
.tr-add { display: flex; gap: 0.5rem; align-items: center; margin-top: 0.75rem; flex-wrap: wrap; }
.tr-add select, .tr-add input {
  font: inherit; font-size: var(--fs-base); padding: 0.4rem 0.55rem;
  border: 1px solid var(--border); border-radius: 6px;
}
.tr-add input { flex: 1 1 8rem; min-width: 6rem; }
.tr-default-row { text-transform: none; letter-spacing: 0; }

/* Settings / Users */
.role-checks { display: flex; flex-wrap: wrap; gap: 0.25rem 1rem; margin-top: 0.3rem; }
.role-checks .detail-check { margin-top: 0; }
.role-checks .detail-check .readonly-note { display: block; text-transform: none; font-size: var(--fs-xs); }
.u-roles-cell { display: flex; flex-wrap: wrap; gap: 0.15rem 0.9rem; }
.u-roles-cell .detail-check { margin-top: 0; font-size: var(--fs-sm); }
.role-cards { display: flex; flex-wrap: wrap; gap: 0.8rem; margin-top: 0.6rem; }
.role-card {
  flex: 1 1 220px; border: 1px solid var(--border); border-radius: 8px; padding: 0.7rem 0.9rem;
  background: var(--surface);
}
.role-card h3 { margin: 0 0 0.3rem; font-size: var(--fs-base); }
.role-card p { margin: 0; font-size: var(--fs-sm); color: var(--ink-muted); }

/* Users tab: master-detail - a narrow selectable user list on the left,
   the selected (or new) user's full editable state on the right. */
.users-split { display: flex; gap: 1.2rem; align-items: flex-start; flex-wrap: wrap; }
.users-list-col { flex: 1 1 280px; min-width: 240px; }
.users-list-col table { width: 100%; }
.users-list-col tbody tr { cursor: pointer; }
.users-detail-col { flex: 2 1 420px; min-width: 320px; }
.users-detail-empty { color: var(--ink-muted); font-size: var(--fs-base); padding: 1rem 0; }

/* Settings -> Global settings: a column of buttons, one per section, beside
   the sections. The buttons stay in view while the sections scroll past. */
.settings-jump { display: flex; gap: 1.2rem; align-items: flex-start; }
.settings-jump-list {
  flex: 0 0 13rem; display: flex; flex-direction: column; gap: 0.4rem;
  position: sticky; top: 0.5rem;
}
.settings-jump-list button { text-align: left; }
/* The panels with the same space between them as the buttons beside them -
   without it their borders touched and the settings ran together. */
.settings-jump-sections { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 0.4rem; }
/* Too narrow for two columns: the buttons go across the top instead. */
@media (max-width: 900px) {
  .settings-jump { flex-direction: column; }
  .settings-jump-list { flex: none; flex-direction: row; flex-wrap: wrap; position: static; }
}

/* The Integration Wizard. Collapsed: just a small round launcher in the
   bottom-right corner, so it never covers a row of whatever's underneath it
   (a Version History table's top row, say). Open: a window floating over the
   bottom right, about 60% of the height - not a drawer across the whole
   bottom, which covered what was being worked on, and not docked beside the
   page, which squeezed it out of shape. It can be dragged by its top bar
   (aiPane.js). The layer it floats in covers the workspace and nothing else
   (#workspace is position:relative), so it never sits over the sidebar; the
   layer itself takes no clicks. */
.ai-pane {
  position: absolute;
  inset: 0;
  z-index: 40;
  pointer-events: none;
}
.ai-pane-launcher {
  pointer-events: auto;
  position: absolute;
  right: 1rem;
  bottom: 1rem;
  width: 48px;
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  cursor: pointer;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
}
.ai-pane-launcher svg { width: 20px; height: 20px; }
/* [hidden] is a user-agent style (display:none) - any author rule that sets
   `display` on the same element outranks it regardless of specificity, so it
   has to be reasserted explicitly here or toggling `hidden` in JS does
   nothing visible - see the same note on .ai-pane-body[hidden] below. */
.ai-pane-launcher[hidden] { display: none; }

.ai-pane-header {
  pointer-events: auto;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.55rem 1rem;
  background: var(--accent);
  color: #fff;
  font-size: var(--fs-sm);
  font-weight: 600;
  cursor: move;
  user-select: none;
}
.ai-pane-header svg { width: 16px; height: 16px; }
.ai-pane-header span { flex: 1; }
.ai-pane-close {
  border: none;
  background: none;
  color: inherit;
  cursor: pointer;
  padding: 0.2rem;
  border-radius: 4px;
  display: flex;
}
.ai-pane-close:hover { background: rgba(255, 255, 255, 0.15); }

.ai-pane-body {
  pointer-events: auto;
  position: absolute;
  right: 1rem;
  bottom: 1rem;
  width: min(440px, calc(100% - 2rem));
  height: 60%;
  min-height: min(380px, calc(100% - 2rem));
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: 0 18px 50px rgba(16, 26, 46, 0.28);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.ai-pane-body[hidden] { display: none; }

.ai-pane-transcript {
  flex: 1;
  overflow-y: auto;
  padding: 0.9rem;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
.ai-pane-hint { color: var(--ink-muted); font-size: var(--fs-sm); margin: 0; }

.ai-msg {
  max-width: 88%;
  padding: 0.5rem 0.7rem;
  border-radius: 10px;
  font-size: var(--fs-base);
  white-space: pre-wrap;
  word-break: break-word;
}
.ai-msg-user {
  align-self: flex-end;
  background: var(--accent);
  color: #fff;
  border-bottom-right-radius: 2px;
}
.ai-msg-assistant {
  align-self: flex-start;
  background: var(--bg);
  border: 1px solid var(--border);
  border-bottom-left-radius: 2px;
}

.ai-tool {
  align-self: flex-start;
  max-width: 95%;
  font-size: var(--fs-xs);
  color: var(--ink-muted);
}
.ai-tool summary {
  cursor: pointer;
  padding: 0.2rem 0.4rem;
  border-radius: 6px;
  background: var(--skipped-bg);
}
.ai-tool.ai-tool-error summary { background: var(--failed-bg); color: var(--failed); }
.ai-tool pre {
  margin: 0.3rem 0 0;
  padding: 0.5rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  max-height: 220px;
  overflow: auto;
  white-space: pre-wrap;
  word-break: break-word;
}

.ai-pane-error {
  margin: 0 0.9rem 0.5rem;
  padding: 0.5rem 0.7rem;
  background: var(--failed-bg);
  color: var(--failed);
  border: 1px solid var(--failed);
  border-radius: 6px;
  font-size: var(--fs-sm);
}

.ai-pane-form {
  display: flex;
  gap: 0.5rem;
  align-items: flex-end;
  padding: 0.7rem 0.9rem;
  border-top: 1px solid var(--border);
}
.ai-pane-form textarea {
  flex: 1;
  resize: none;
  font: inherit;
  font-size: var(--fs-sm);
  padding: 0.45rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  max-height: 140px;
  min-height: 36px;
}
.ai-pane-form-actions { display: flex; flex-direction: column; gap: 0.35rem; }

/* No room to float beside anything: take the bottom of the screen, as before. */
@media (max-width: 640px) {
  .ai-pane-body { left: 0 !important; right: 0 !important; top: auto !important; bottom: 0 !important; width: auto; border-radius: 14px 14px 0 0; }
}

/* Partners -> Workflows: the bound workflows on the left, the selected one's
   detail on the right. Every binding stays in the DOM (hidden) so a save still
   collects them all - only one is on screen at a time. */
.p-wf-split {
  display: grid;
  grid-template-columns: 220px 1fr;
  gap: 1rem;
  align-items: start;
}
.p-wf-list {
  border: 1px solid var(--border);
  border-radius: 6px;
  overflow: hidden;
}
.p-wf-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 0.5rem 0.6rem;
  border: 0;
  border-bottom: 1px solid var(--border);
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: var(--fs-base);
  cursor: pointer;
}
.p-wf-item:last-child { border-bottom: 0; }
.p-wf-item:hover { background: var(--bg); }
.p-wf-item.active {
  background: var(--bg);
  font-weight: 600;
  box-shadow: inset 3px 0 0 var(--accent);
}

@media (max-width: 860px) {
  .p-wf-split { grid-template-columns: 1fr; }
}

/* One binding's settings, grouped by subject: which workflow, how this
   partner's messages for it are recognised, where they arrive and leave.
   Before this it was a single column of twenty-odd fields in which the
   envelope boxes, the content rules and the channels all ran together with
   nothing to say where one subject ended and the next began. */
.b-section {
  border-top: 1px solid var(--border);
  margin-top: 0.9rem;
  padding-top: 0.7rem;
}
.b-section:first-child {
  border-top: 0;
  margin-top: 0;
  padding-top: 0;
}
.b-section > h4 {
  margin: 0;
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink);
}
/* Paired fields side by side rather than stacked: a sender and a receiver, an
   in channel and an out channel, read as a pair and take half the height for
   it. Falls back to one column in a narrow pane on its own. */
.b-grid {
  display: grid;
  /* Columns up to a field's width, not shares of the page: a sender GLN or a
     channel name in a box the width of a third of a wide screen reads as
     something enormous to fill in. */
  grid-template-columns: repeat(auto-fill, minmax(13rem, 19rem));
  gap: 0 1.2rem;
  align-items: start;
}
/* Each box as wide as what it holds. The form style makes every input the
   full width of its column; these cap it at what the field can contain. */
.binding-row .b-workflow { max-width: 28rem; }
.binding-row .b-sender, .binding-row .b-receiver, .binding-row .b-type { max-width: 16rem; }
.binding-row .b-in-channel, .binding-row .b-out-channel,
.binding-row .b-ack-channel, .binding-row .b-partner-key { max-width: 19rem; }
/* "*.edi, *.xml" - a few patterns, not a sentence. */
.binding-row .b-file-names { max-width: 10rem; }
.binding-row .b-ack-hours { max-width: 6rem; }
/* "Keep only the latest 100", the longer of its two choices. */
.binding-row .b-keep-latest { max-width: 15rem; }
/* A file-name pattern and its "Add a value" picker take two columns' room. */
.binding-row .b-field-wide { grid-column: span 2; }
.binding-row .b-filename-row { max-width: 32rem; }
.binding-row .b-ack-channel-field, .binding-row .b-ack-hours-field,
.binding-row .b-keep-latest-field { max-width: 40rem; }
/* display:grid would otherwise beat the browser's own [hidden] rule, which is
   how a block that is meant to be hidden stays on screen. */
.b-grid[hidden] { display: none; }
.b-field {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
/* One line, always: a label that wraps pushes its box below its neighbours'
   and the row stops lining up. A label too long for its column is cut off,
   and its title has it in full. */
.b-field > label {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.b-field .readonly-note {
  margin-top: 0.35rem;
  font-size: var(--fs-xs);
}

/* Workflows grouped by customer: a heading row inside the table, so the
   columns still line up underneath it. */
.wf-group-row td {
  background: var(--bg);
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: 600;
  padding: 0.35rem 0.6rem;
}
.wf-group-row td .readonly-note { margin-left: 0.5rem; font-size: var(--fs-2xs); }
.wf-group-row:hover td { background: var(--bg); }

.p-wf-group {
  padding: 0.3rem 0.6rem;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-muted);
}

/* A workflow tab whose body is several section.panel blocks - same treatment as
   the cockpit's .int-tab, so they read as separate blocks rather than one wall.
   The [hidden] reassertion is needed for the same reason it is there: display
   grid outranks the UA hidden rule, and without it every tab renders at once. */
.wf-tab-stack {
  display: grid;
  gap: 1.25rem;
}
.wf-tab-stack[hidden] {
  display: none;
}

/* Monitoring: which way the message was going. A pill rather than a word, so a
   column of them scans as a shape instead of as text to read. */
.mon-dir {
  display: inline-block;
  padding: 0 0.4rem;
  border-radius: 999px;
  font-size: var(--fs-2xs);
  font-weight: 600;
  border: 1px solid var(--border);
  color: var(--ink-muted);
}
.mon-dir.in { border-color: #2f6f4f; color: #2f6f4f; }
.mon-dir.out { border-color: #2a5d9f; color: #2a5d9f; }

/* A support ticket, collapsed. <details> rather than a scripted toggle: the
   reply box and buttons stay in the DOM while closed, so the handlers that look
   them up by data attribute keep working without knowing about any of this. */
.op-ticket > summary {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  cursor: pointer;
  list-style: none;
  padding: 0.1rem 0;
}
.op-ticket > summary::-webkit-details-marker { display: none; }
.op-ticket > summary::before {
  content: "\25B8";
  color: var(--ink-muted);
  font-size: var(--fs-sm);
}
.op-ticket[open] > summary::before { content: "\25BE"; }
.op-ticket > summary:hover .op-ticket-title { color: var(--accent); }
.op-ticket-title { font-weight: 600; font-size: var(--fs-md); }

/* A checkbox that reads as one line with its label, rather than a label above a
   box the width of the form. */
.p-inline {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  text-transform: none;
  font-weight: 600;
  margin-top: 0.6rem;
}
.p-inline input { width: auto; margin: 0; }

/* Monitoring: a message rendered as the business document it is - an invoice, an
   order. Laid out like the paper version rather than like a form, because the
   question it answers is "what does this say", and a reader who knows invoices
   should not have to learn this screen. One layout for every document type: the
   server decides what the fields are (BusinessDocumentView), so a new document type
   needs a reader and nothing here. */
.doc-sheet {
  padding: 1.2rem 1.4rem 1.5rem;
  font-size: var(--fs-base);
  color: var(--ink);
  background: var(--surface);
}
.doc-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 1rem;
  padding-bottom: 0.8rem;
  border-bottom: 2px solid var(--ink);
}
.doc-title { font-size: var(--fs-xl); font-weight: 700; letter-spacing: 0.01em; }
.doc-ref { font-size: var(--fs-md); color: var(--ink-muted); margin-top: 0.15rem; }
.doc-head-right { text-align: right; white-space: nowrap; }

/* Parties side by side, the way they sit on a printed invoice - from on the left,
   to on the right - and wrapping rather than shrinking on a narrow window. */
.doc-parties {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem;
  margin: 1rem 0 0;
}
.doc-party { min-width: 190px; flex: 1 1 190px; line-height: 1.45; }
.doc-party-role {
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-muted);
  margin-bottom: 0.15rem;
}
.doc-party-name { font-weight: 600; }

/* A named value: label above, value below, so a column of them reads as a list of
   facts rather than a sentence. */
.doc-field { display: flex; gap: 0.4rem; font-size: var(--fs-sm); margin-top: 0.12rem; }
.doc-field > span { color: var(--ink-muted); }
.doc-refs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.5rem;
  margin: 1rem 0 0;
  padding-top: 0.7rem;
  border-top: 1px solid var(--border);
}

.doc-lines { margin: 1rem 0 0; border: none; border-radius: 0; }
.doc-lines thead th {
  background: none;
  border-bottom: 1px solid var(--ink);
  padding: 0.35rem 0.5rem;
}
.doc-lines tbody td {
  padding: 0.45rem 0.5rem;
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-sm);
}
.doc-lines tbody tr { cursor: default; }
.doc-lines tbody tr:hover td { background: none; }

/* Totals right-aligned under the lines, which is where an eye goes looking. */
.doc-totals {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.1rem;
  margin: 0.8rem 0 0;
}
.doc-totals .doc-field { justify-content: flex-end; min-width: 220px; }
.doc-totals .doc-field:last-child b { font-size: var(--fs-md); }

.doc-notes {
  margin: 1.1rem 0 0;
  padding-top: 0.7rem;
  border-top: 1px solid var(--border);
  color: var(--ink-muted);
  font-size: var(--fs-sm);
  line-height: 1.5;
}

/* The rendered document needs room and its own scroll - the payload <pre> beside
   it has both already. */
#payload-dialog-doc { max-height: 70vh; overflow: auto; }

/* The step bar: one row of numbered steps wherever work moves through stages
   (a test plan, an EDI adapter). Each says how far it is; done ones are
   ticked, the one whose page is open is marked; a step is a button. */
.step-bar { display: flex; background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  overflow: hidden; margin: 0.25rem 0 0.9rem; box-shadow: var(--card-shadow); }
.step-bar .step-bar-item { flex: 1 1 0; min-width: 0; text-align: left; border: 0; border-right: 1px solid var(--border);
  background: none; padding: 0.6rem 0.75rem; font: inherit; color: var(--ink-muted); cursor: pointer; }
.step-bar .step-bar-item:last-child { border-right: 0; }
.step-bar .step-bar-item:hover { background: var(--bg); }
.step-bar .step-bar-n { display: inline-flex; width: 1.25rem; height: 1.25rem; border-radius: 50%; align-items: center;
  justify-content: center; font-size: var(--fs-2xs); font-weight: 700; background: var(--bg); color: var(--ink-muted);
  margin-bottom: 0.2rem; }
.step-bar .step-bar-label { display: block; font-size: var(--fs-sm); font-weight: 600; color: var(--ink-muted); }
.step-bar .step-bar-sub { display: block; font-size: var(--fs-xs); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.step-bar .step-bar-item.done .step-bar-n { background: var(--delivered); color: #fff; }
.step-bar .step-bar-item.done .step-bar-label { color: var(--ink); }
.step-bar .step-bar-item.current { background: var(--accent-tint); box-shadow: inset 0 -3px 0 var(--accent); }
.step-bar .step-bar-item.current .step-bar-n { background: var(--accent); color: #fff; }
.step-bar .step-bar-item.current .step-bar-label { color: var(--accent); }

/* The one next thing to do, under a step bar: what and why, and its button. */
.next-step { display: flex; align-items: center; gap: 0.9rem; background: var(--surface); border: 1px solid var(--accent);
  border-radius: 10px; padding: 0.7rem 1rem; margin-bottom: 0.9rem; }
.next-step .next-step-what { flex: 1 1 auto; min-width: 0; }
.next-step .next-step-what b { display: block; font-size: var(--fs-base); }
.next-step .next-step-what span { color: var(--ink-muted); font-size: var(--fs-sm); }

/* The Monitor's row number: small, grey, right-aligned, out of the way of what the row says. */
.mon-num { width: 1%; white-space: nowrap; text-align: right; color: var(--ink-muted); font-size: var(--fs-xs); font-variant-numeric: tabular-nums; }
