@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Manrope:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600;700&display=swap');/*
 * Settings screen (GIS-01…GIS-06). The tool-wide ignore list is small and rarely edited, so this
 * is a plain, legible panel: one column, one row per pattern, and the three feedback states
 * (refusal, conflict, advisory note) told apart by colour and wording rather than by layout.
 * Every value goes through a design-system token — see ../ds/README.md.
 */

/*
 * The shell's content canvas is full-bleed (there is no top bar to sit under any more), so the
 * screen carries the page padding it used to inherit from the canvas.
 */
.settings-screen {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  padding: var(--space-6) var(--space-8);
}

/*
 * The applies-to-everyone statement, at the top of the global row's detail (GIS-17). Deliberately
 * the loudest thing in the row: it is the one fact a Reviewer could act on destructively, and it
 * must not need a hover.
 */
.settings-global-notice {
  margin: 0;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--warning);
  border-radius: var(--radius-md);
  background: var(--warning-wash);
  color: var(--text-primary);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  line-height: 1.5;
}

/*
 * ADR 0017: how deep a pattern reaches. Permanent rather than conditional, because a Reviewer who
 * does not know this rule types a pattern that silently matches nothing — which is exactly how it
 * went wrong before the rule changed.
 */
.settings-pattern-rule {
  margin: 0;
  padding: var(--space-3) var(--space-4);
  color: var(--text-secondary);
  font-size: var(--fs-xs);
  line-height: 1.5;
}

/* GIS-02: the server refused the pattern. The typed text stays in the field beside this. */
.settings-error {
  margin: 0;
  padding: var(--space-3) var(--space-4);
  background: var(--danger-wash);
  color: var(--danger);
  font-size: var(--fs-sm);
}

/* GIS-05: never an auto-retry — the panel states what happened and offers Reload. */
.settings-conflict {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
  padding: var(--space-4);
  background: var(--danger-wash);
}

.settings-conflict-title {
  margin: 0;
  color: var(--danger);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
}

.settings-conflict-who,
.settings-conflict-detail {
  margin: 0;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
}

/*
 * Advisory notes on a save that SUCCEEDED (spec edge case: a duplicate pattern). Info blue, never
 * the danger wash — nothing failed here.
 */
.settings-notes {
  margin: 0;
  padding: var(--space-3) var(--space-4) var(--space-3) var(--space-8);
  background: var(--info-wash);
  color: var(--text-secondary);
  font-size: var(--fs-sm);
  list-style: disc;
}

.settings-note {
  margin: var(--space-1) 0;
}

/* GIS-15: an empty saved list is a state with something to say, not a blank panel. */
.settings-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-10) var(--space-4);
  text-align: center;
}

.settings-empty-title {
  margin: 0;
  color: var(--text-secondary);
}

.settings-empty-detail {
  margin: 0;
  color: var(--text-tertiary);
  font-size: var(--fs-sm);
}

.settings-pattern {
  overflow: hidden;
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.settings-message {
  margin: 0;
  color: var(--text-secondary);
  text-align: center;
}

.settings-message-action {
  display: flex;
  justify-content: center;
  padding-top: var(--space-3);
}

/* GIS-01/GIS-04: who last changed the shared list, or that nobody has. */
.settings-list-attribution {
  margin: 0;
  color: var(--text-tertiary);
  font-size: var(--fs-xs);
}

.settings-attribution {
  margin: 0;
  color: var(--text-tertiary);
  font-size: var(--fs-xs);
}

/*
 * TMF-09/TMF-10: the Team directory. A third card on a page that already has two, so it stays a
 * summary rather than a wall — three numbers on one line, one line about bots, and rows only for
 * the accounts somebody can actually do something about.
 */
.settings-directory {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.settings-directory-message {
  margin: 0;
  color: var(--text-secondary);
}

.settings-directory-retry {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

/* A refresh that never reached the callable. The counts stay on screen underneath it (TMF-08). */
.settings-directory-error {
  margin: 0 0 var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  background: var(--danger-wash);
  color: var(--danger);
  font-size: var(--fs-sm);
}

/*
 * TMF-08: a recorded refresh failure, sitting ABOVE counts that did not change. Danger-toned so it
 * is not mistaken for a note, but never in place of the numbers — the previous snapshot is still
 * in force and saying so is the whole point.
 */
.settings-directory-failure {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  background: var(--danger-wash);
}

.settings-directory-failure-title {
  margin: 0;
  color: var(--danger);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
}

.settings-directory-failure-detail,
.settings-directory-failure-when {
  margin: 0;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
}

/* TMF-11: never refreshed is a state with something to say, not a blank panel. */
.settings-directory-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-8) var(--space-4);
  text-align: center;
}

.settings-directory-empty-title {
  margin: 0;
  color: var(--text-secondary);
}

.settings-directory-empty-detail {
  margin: 0;
  max-width: 46ch;
  color: var(--text-tertiary);
  font-size: var(--fs-sm);
}

/* The three facts TMF-09 asks for, on one line rather than three rows. */
.settings-directory-stats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
  margin: 0;
}

.settings-directory-stat {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.settings-directory-stat-label {
  color: var(--text-tertiary);
  font-size: var(--fs-2xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

.settings-directory-stat-value {
  margin: 0;
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
}

/*
 * Bots. Visibly NOT one of the rows below: a quiet single line on the surface-2 wash, no badge, no
 * username on screen (they are in the title). A bot with no Team is permanent and normal; a person
 * with no Team is a gap somebody should close, and the two must never look like the same item.
 */
.settings-directory-bots {
  margin: 0;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--bg-surface-2);
  color: var(--text-tertiary);
  font-size: var(--fs-xs);
}

.settings-directory-resolved {
  margin: 0;
  color: var(--success);
  font-size: var(--fs-sm);
}

.settings-directory-unresolved {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.settings-directory-unresolved-title {
  margin: 0;
  color: var(--text-primary);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
}

.settings-directory-unresolved-detail {
  margin: 0;
  color: var(--text-tertiary);
  font-size: var(--fs-xs);
}

.settings-directory-list {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  list-style: none;
}

/* One unresolved person: the username, and the reason it could not be placed. */
.settings-directory-person {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border);
}

.settings-directory-person:last-child {
  border-bottom: none;
}

.settings-directory-username {
  overflow: hidden;
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.settings-directory-reason {
  display: inline-flex;
  flex: 0 0 auto;
}

/* Project overrides (PRO-07, PRO-08): one row per project that has any, expanding to its entries. */

.settings-overrides {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.settings-overrides-empty {
  margin: 0;
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: var(--text-secondary);
}

.settings-overrides-list,
.settings-override-entries {
  display: flex;
  flex-direction: column;
  gap: var(--space-1, 4px);
  margin: 0;
  padding: 0;
  list-style: none;
}

.settings-override-project-row {
  display: flex;
  gap: var(--space-3);
  align-items: baseline;
  justify-content: space-between;
  width: 100%;
  padding: var(--space-2);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm, 6px);
}

.settings-override-counts {
  font-size: var(--fs-xs, 0.75rem);
  color: var(--text-tertiary);
  white-space: nowrap;
}

.settings-override-detail {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-2) var(--space-2) var(--space-3);
  border-left: 2px solid var(--border);
}

.settings-override-entry {
  display: flex;
  gap: var(--space-2);
  align-items: baseline;
}

.settings-override-kind {
  font-size: var(--fs-xs, 0.75rem);
  color: var(--text-secondary);
}

.settings-override-actions {
  display: flex;
  gap: var(--space-2);
}

.settings-override-error {
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--danger, #c0392b);
}

/*
 * Team Home Groups (THG-01…THG-06, ADR 0026): one row per Team, every Team rendered — including a
 * zero-entry one, which is a normal row here rather than a missing one (unlike the Project
 * Overrides audit set above). Shares its row/detail anatomy with `.settings-override-*` but keeps
 * its own class names since the two lists are unrelated domains.
 */
.settings-homegroups {
  display: flex;
  flex-direction: column;
  gap: var(--space-1, 4px);
  margin: 0;
  padding: 0;
  list-style: none;
}

.settings-homegroups-message {
  margin: 0;
  color: var(--text-secondary);
}

.settings-homegroups-retry {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

.settings-homegroups-team-row {
  display: flex;
  gap: var(--space-3);
  align-items: baseline;
  justify-content: space-between;
  width: 100%;
  padding: var(--space-2);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm, 6px);
}

.settings-homegroups-label {
  overflow: hidden;
  color: var(--text-primary);
  font-size: var(--fs-sm);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.settings-homegroups-counts {
  font-size: var(--fs-xs, 0.75rem);
  color: var(--text-tertiary);
  white-space: nowrap;
}

.settings-homegroups-detail {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-2) var(--space-2) var(--space-3);
  border-left: 2px solid var(--border);
}

.settings-homegroups-entries {
  display: flex;
  flex-direction: column;
  gap: var(--space-1, 4px);
  margin: 0;
  padding: 0;
  list-style: none;
}

.settings-homegroups-entry {
  display: flex;
  gap: var(--space-2);
  align-items: baseline;
}

.settings-homegroups-path {
  overflow: hidden;
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* THG-01: a zero-entry Team is a normal row with something to say, not a blank detail panel. */
.settings-homegroups-empty {
  margin: 0;
  color: var(--text-tertiary);
  font-size: var(--fs-sm);
}

.settings-homegroups-actions {
  display: flex;
  gap: var(--space-2);
}

.settings-homegroups-add {
  display: flex;
  gap: var(--space-2);
  align-items: center;
}

/**
 * Slack nudges (WHK-14): one switch per Team, every Team rendered — with Nudges opt-in, every row
 * starts off, so an "only the enabled ones" list would start empty and stay that way. Plain rows
 * rather than the expandable anatomy above, since a Team here holds one boolean and nothing to
 * expand into.
 */
.settings-nudge-notice {
  margin: 0;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
}

.settings-nudge-message {
  margin: 0;
  color: var(--text-secondary);
}

.settings-nudge-retry {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

.settings-nudge-teams {
  display: flex;
  flex-direction: column;
  gap: var(--space-1, 4px);
  margin: 0;
  padding: 0;
  list-style: none;
}

.settings-nudge-team {
  display: flex;
  align-items: center;
  padding: var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm, 6px);
}

.settings-nudge-attribution {
  margin: 0;
  color: var(--text-tertiary);
  font-size: var(--fs-xs, 0.75rem);
}

.settings-nudge-conflict {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}
/*
 * What one Analysis has to say about itself (FND-13/14/15, AOD-13/14). The backdrop and card come
 * from ConfirmDialog.css — one modal shell for the whole app — so what is left here is what a
 * report of facts and two path lists needs.
 */

.analysis-report__title {
  margin: 0;
  font-size: var(--fs-md, 1rem);
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
}

.analysis-report__subtitle {
  margin: 0 0 var(--space-1, 4px);
  font-size: var(--fs-xs, 0.75rem);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}

/*
 * The caveats, above the facts and in the warning tone: they change how far the findings below can
 * be trusted, which is a different kind of statement from "it took 8.5s".
 */
.analysis-report__warning {
  margin: 0;
  padding: var(--space-2) var(--space-3);
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: var(--text-primary);
  background: var(--warning-wash, transparent);
  border-left: 2px solid var(--warning);
  border-radius: var(--radius-sm, 6px);
}

/*
 * A two-column definition list rather than prose: these are facts to be looked up, not read in
 * order, and the toolbar line they replaced was hard to scan precisely because it was a sentence.
 */
.analysis-report__facts {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-1, 4px) var(--space-3);
  margin: 0;
  font-size: var(--fs-sm);
}

.analysis-report__facts dt {
  color: var(--text-tertiary);
}

.analysis-report__facts dd {
  margin: 0;
  color: var(--text-primary);
}

.analysis-report__facts code {
  font-family: var(--font-mono);
  font-size: var(--fs-xs, 0.75rem);
}

.analysis-report__section {
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/*
 * Each list scrolls on its own: a generated-code merge request can skip hundreds of files, and a
 * modal taller than the viewport puts its own Close button out of reach.
 */
.analysis-report__list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 12rem;
  margin: 0;
  padding: var(--space-2);
  overflow-y: auto;
  list-style: none;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm, 6px);
}

/*
 * Paths are long and have no spaces to break at, so they wrap anywhere rather than forcing the
 * modal wider than the screen — the same rule the pattern editor's preview list follows.
 */
.analysis-report__list code {
  font-family: var(--font-mono);
  font-size: var(--fs-xs, 0.75rem);
  color: var(--text-primary);
  overflow-wrap: anywhere;
}

.analysis-report__actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
}

/*
 * A Reviewer's own words, quoted back (ARN-08). Italic and quoted so it reads as reported speech
 * rather than as another fact the tool is asserting.
 */
.analysis-report__hint {
  font-style: italic;
}

.analysis-report__hint::before {
  content: '“';
}

.analysis-report__hint::after {
  content: '”';
}
/**
 * The one modal shell in this app: the confirmation below and the pattern editor both use it, so
 * there is one thing to keep accessible and one thing to restyle.
 */

.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  background: rgb(0 0 0 / 45%);
}

.modal-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  width: min(34rem, 100%);
  max-height: min(90vh, 44rem);
  padding: var(--space-4);
  overflow-y: auto;
  font-family: var(--font-sans);
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md, 8px);
  box-shadow: 0 12px 32px rgb(0 0 0 / 35%);
}

.confirm-dialog {
  width: min(30rem, 100%);
}

.confirm-dialog__title {
  margin: 0;
  font-size: var(--fs-md, 1rem);
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
}

.confirm-dialog__question {
  margin: 0;
  font-size: var(--fs-sm);
  line-height: 1.55;
  color: var(--text-secondary);
}

.confirm-dialog__actions {
  display: flex;
  gap: var(--space-2);
  justify-content: flex-end;
}

/*
 * Whatever the decision needs beyond the question — today the Analyse hint (ARN-08). Between the
 * question and the actions, so it reads as part of the decision rather than a step before it.
 */
.confirm-dialog__extra {
  margin-top: var(--space-3);
}
/* The commit history, over the diff it belongs to (CMT-02). Wide enough for a real subject line
   without wrapping, and it scrolls inside the card so the diff behind it never moves. */

.commits-dialog {
  width: min(52rem, 100%);
  max-height: min(84vh, 48rem);
  padding: 0;
}

/*
 * A fixed header and foot: the list scrolls between them, so the way out stays reachable on a merge
 * request with a hundred commits — the same reason the changelog dialog pins its own.
 */
.commits-dialog__head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: 0 0 auto;
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--border);
}

.commits-dialog__title {
  margin: 0;
  font-size: var(--fs-h3, 1rem);
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
}

/* `6 commits on feature/daily-streak` — mono, because a count and a ref are written that way
   everywhere else in this app. */
.commits-dialog__subtitle {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-tertiary);
}

.commits-dialog__body {
  flex: 1 1 auto;
  overflow-y: auto;
}

.commits-dialog__empty {
  margin: 0;
  padding: var(--space-5);
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

.commits-dialog__list {
  margin: 0;
  padding: 0;
  list-style: none;
}

/*
 * One commit per row, hairline-separated rather than carded: this is a list to read down, and a
 * border per item is enough structure for the eye without turning six commits into six objects.
 */
.commits-dialog__row {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-5);
  border-bottom: 1px solid var(--border);
}

.commits-dialog__row:last-child {
  border-bottom: none;
}

/* The sha is the one interactive thing in a row, so it looks like the chip it is — and it is the
   column the eye runs down, hence the fixed width. */
.commits-dialog__sha {
  flex: 0 0 auto;
  min-width: 68px;
  padding: 2px 6px;
  border-radius: var(--radius-xs);
  background: var(--bg-surface-2);
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--accent);
  text-align: center;
  text-decoration: none;
}

.commits-dialog__sha:hover {
  background: var(--bg-hover);
  text-decoration: underline;
}

.commits-dialog__text {
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: 2px;
}

/*
 * Somebody's git subject, as they wrote it — `anywhere` so a 200-character subject with no space in
 * it wraps instead of pushing the row sideways.
 */
.commits-dialog__commit-title {
  font-size: 13.5px;
  color: var(--text-primary);
  overflow-wrap: anywhere;
}

.commits-dialog__meta {
  font-size: 12px;
  color: var(--text-tertiary);
}

.commits-dialog__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex: 0 0 auto;
  padding: var(--space-3) var(--space-5);
  border-top: 1px solid var(--border);
}

/* What the diff behind this dialog actually shows (CMT-03) — mono and quiet: a standing fact about
   the screen, not an action. */
.commits-dialog__scope {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-tertiary);
}
/*
 * File tree of the merge request diff screen (DWB-01/02/03/05).
 *
 * Everything the tree draws that is not shared with the rest of the screen lives here rather than
 * in a `style` prop: the folder headers, the nested Finding/discussion list and the row internals
 * all need real CSS states (`:hover`) and attribute selectors, which inline styles cannot express.
 * Sizes, weights, letter-spacing and colours are the approved screen's, token for token — no
 * literal colour appears below.
 *
 * The scroll behaviour of `.mrdiff-tree` (its `max-height`, and the narrow-viewport release of it)
 * belongs to the screen's own stylesheet, which owns the two-column shell (DVR-01/05). Only the
 * card's own paint is set here.
 */
.mrdiff-tree {
  padding: 0 6px 10px;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}

/*
 * The card's header strip (DWB-07/08/10, decision 2026-08-13): "Show hidden files" and Refresh,
 * which used to take a full-width toolbar row across the tree *and* the diff column.
 *
 * `position: sticky; top: 0` inside the card's own scroll (`.mrdiff-tree` is `overflow-y: auto`, set
 * by the screen's stylesheet) is the load-bearing part. The controls now live in the container that
 * scrolls when a merge request has more files than the viewport has room for, and a control that
 * scrolls out of reach is worse than one on a row of its own. The opaque `--bg-surface` — the card's
 * own paint — is what stops the rows reading through it while they pass underneath.
 *
 * `margin: 0 -6px` cancels the card's horizontal padding so the strip and its bottom rule reach the
 * card's edges; the card's `overflow` clips them back to its rounded corners. The card's top padding
 * was moved into this strip for the same reason: a sticky child of a padded scroll container pins to
 * the padding edge, so 4px of rows would otherwise slide through the gap above it.
 *
 * `flex-wrap` is the fallback, not the plan: at 272px the switch (32px track + label at `--fs-sm`)
 * and a 34px icon button fit one line with room to spare, but a wider font or a longer label takes a
 * second line inside the card rather than pushing a control out of it.
 */
.mrdiff-treehead {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin: 0 -6px 2px;
  padding: 6px 8px;
  background: var(--bg-surface);
  border-bottom: 1px solid var(--border);
}

/* Refresh sits at the strip's right edge whether or not the switch is there to precede it. */
/*
 * What the list holds, on the list's own heading — `12 files · 1 hidden` (moved from the toolbar,
 * 2026-08-20). Mono and tertiary, the way every other count in this screen is written.
 */
.mrdiff-treehead-summary {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--text-tertiary);
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The one control left on the strip takes its trailing edge. */
.mrdiff-treehead-rules {
  display: inline-flex;
  flex: 0 0 auto;
  margin-left: auto;
}

/*
 * Folder header (DWB-01): a directory is named once, above the files in it, instead of being
 * repeated at the head of every row.
 *
 * `direction: rtl` is what makes a long path stay useful when it does not fit: the ellipsis falls
 * on the **left**, so `src/analytics/instrumentation` keeps its tail — the part that says which
 * folder this is — rather than truncating to `src/analytics/in…`. `unicode-bidi: plaintext` keeps
 * the path's own characters in logical order inside that right-to-left box, and `text-align: left`
 * keeps the header aligned with the rows under it.
 */
.mrdiff-foldrow {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 7px 4px 2px;
  padding-top: 7px;
  border-top: 1px solid var(--border);
  color: var(--text-disabled);
}

/*
 * The first folder of a section sits directly under its heading, which is already a divider — a
 * second rule there would read as a separator between the heading and the files it heads.
 */
.mrdiff-treelabel + .mrdiff-foldrow {
  margin-top: 2px;
  padding-top: 0;
  border-top: 0;
}

.mrdiff-foldpath {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.01em;
  color: var(--text-tertiary);
  white-space: nowrap;
  text-overflow: ellipsis;
  direction: rtl;
  unicode-bidi: plaintext;
  text-align: left;
}

.mrdiff-foldcount {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 9.5px;
  color: var(--text-disabled);
}

/*
 * The two decorative glyphs of the tree, muted on purpose (decision 2026-08-13).
 *
 * They have to be coloured through a wrapper: `Icon` writes `color: currentColor` as an *inline*
 * style on its own `<svg>`, and an inline declaration outranks any rule that could name the svg.
 * Both are decoration — a folder is already named beside its glyph, and a file's extension is
 * already in its name — so neither is allowed to compete with the marks that do carry meaning: the
 * crucial `shield-alert` in `--accent`, the change-kind dot, and the viewed check in `--success`.
 */
.mrdiff-tfileicon {
  display: inline-flex;
  flex: 0 0 auto;
  color: var(--text-tertiary);
}

/* A crucial file's shield is the one glyph in the row that carries information, so it is the one
   that carries the colour (2026-08-17). */
.mrdiff-tfileicon.crucial {
  color: var(--accent);
}

/* The hidden section's heading carries its own switch on the same line (2026-08-17): the heading keeps
   its padding and tone from `.mrdiff-treelabel`, and this only turns it into a row and pushes the
   control to the far end. `align-items: center` is what makes the two match in height rather than the
   switch sitting a pixel proud of the caps. */
/* The heading line of the hidden section — and, since 2026-08-18, a rule above it, so the files the
   review rules put away sit visibly *below a line* rather than reading as one more heading in the
   same list. Only this heading carries one: "Crucial paths" and "Files" are two halves of what a
   Reviewer is here to read, and a rule between those would divide what belongs together. Drawn on
   the row itself rather than as an element of its own, so it spans the strip with nothing extra to
   lay out. */
.mrdiff-treelabelrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: 6px;
  padding-top: 8px;
  padding-right: 10px;
  border-top: 1px solid var(--border);
}

/* The heading keeps its own padding and tone from `.mrdiff-treelabel` — the row only lays the two out
   and pushes the control to the far end, so nothing about the heading changes by gaining a switch. */
.mrdiff-treelabelrow > .mrdiff-treelabel {
  flex: 1 1 auto;
}

/* Requested 2026-08-17: a crucial file's *name* carries weight too, so the row reads as crucial from
   the name alone — the glyph is 13px and easy to skim past in a long tree, and the two together are
   what make the row impossible to miss without a third mark. 600 rather than 700: the tree's names are
   mono at 11.5px, where 700 smears at the sizes this list is read at. */
[data-relevance='crucial'] .mrdiff-tname {
  font-weight: 600;
}

/* One step quieter than the file glyph — the folder header is context, not content. */
.mrdiff-tfoldericon {
  display: inline-flex;
  flex: 0 0 auto;
  color: var(--text-disabled);
}

/* File name of a tree row — the basename, now that the folder above it carries the rest. */
.mrdiff-tname {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  font-family: var(--font-mono);
  font-size: 11.5px;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/*
 * The full path, for assistive technology only. The folder grouping is a *visual* association: no
 * markup relates a row to the header above it, so a row announcing only `events.ts` would lose the
 * directory a sighted Reviewer reads off the header. This span restores it as the row's accessible
 * name, which is why the visible basename beside it is `aria-hidden`.
 *
 * `.mrdiff-sronly` is the same treatment for the header's icon-only Refresh (DWB-08), whose word is
 * its accessible name and nothing else. Clipped rather than `display: none` or `hidden`: both of
 * those take the text out of the accessibility tree, which would leave the button nameless.
 */
/*
 * Every element that *holds* visually hidden text is positioned, and that is load-bearing rather
 * than tidiness (fixed 2026-08-14, found in the released build).
 *
 * The hidden spans are `position: absolute`, and an absolutely positioned element with no
 * positioned ancestor is laid out against the **initial containing block** — so the tree's
 * `overflow: auto` could not clip them. A row scrolled out of sight still sat at its page
 * coordinate, stretched the document ~200px past the viewport, and the page scrolled: the fixed
 * 100vh shell was dragged up and the app background showed beneath it. The clipping only works if
 * the containing block is inside the thing that scrolls.
 */
.mrdiff-treerow,
.mrdiff-treehead-rules {
  position: relative;
}

.mrdiff-srpath,
.mrdiff-sronly {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Change-kind indicator: added=success, deleted=danger, renamed=info, modified=warning. */
.mrdiff-tdot {
  width: 7px;
  height: 7px;
  flex: 0 0 auto;
  border-radius: 999px;
}

.mrdiff-tdot[data-change-kind='added'] {
  background: var(--success);
}

.mrdiff-tdot[data-change-kind='deleted'] {
  background: var(--danger);
}

.mrdiff-tdot[data-change-kind='renamed'] {
  background: var(--info);
}

.mrdiff-tdot[data-change-kind='modified'] {
  background: var(--warning);
}

/* CFO-06: the file's patch has not arrived, so what happened to it is not yet known. A hollow ring
   rather than a fifth colour — the four filled dots are statements, and this is the absence of one.
   It fills in with the real dot the moment that file's page lands. */
.mrdiff-tdot[data-change-kind='pending'] {
  background: transparent;
  box-shadow: inset 0 0 0 1px var(--text-tertiary);
}

.mrdiff-tstat {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--text-tertiary);
}

/* A viewed file swaps its change-kind dot for a check (DIF-12). */
.mrdiff-tcheck {
  display: inline-flex;
  flex: 0 0 auto;
  color: var(--success);
}

/*
 * The Findings and unresolved discussions of one file, nested under its row (DWB-01). The 22px
 * indent plus a 9px-padded left border is what makes the list read as belonging to the row above
 * it rather than as more rows of the tree.
 */
.mrdiff-findlist {
  display: flex;
  flex-direction: column;
  gap: 1px;
  margin: 1px 0 4px 22px;
  padding-left: 9px;
  border-left: 1px solid var(--border);
}

/* One Finding or thread — a button, because selecting it navigates the diff (DWB-02). */
.mrdiff-findrow {
  display: flex;
  align-items: baseline;
  gap: 6px;
  width: 100%;
  padding: 3px 8px 3px 6px;
  border: 0;
  border-radius: 4px;
  background: transparent;
  font-family: var(--font-sans);
  text-align: left;
  cursor: pointer;
}

.mrdiff-findrow:hover {
  background: var(--bg-hover);
}

.mrdiff-fbar {
  width: 2px;
  flex: 0 0 auto;
  align-self: stretch;
  border-radius: 2px;
}

.mrdiff-fkind {
  flex: 0 0 auto;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.mrdiff-ftitle {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 11.5px;
  line-height: 1.35;
  color: var(--text-secondary);
  text-wrap: pretty;
}

.mrdiff-fline {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 9.5px;
  color: var(--text-disabled);
}

/*
 * Severity is the colour, on the bar and on the word alike. A thread takes neither: it is not a
 * Severity, and painting it like one would say the Analysis found it (diff-workbench edge case).
 */
.mrdiff-findrow[data-kind='conflict'] .mrdiff-fbar {
  background: var(--danger);
}

.mrdiff-findrow[data-kind='conflict'] .mrdiff-fkind {
  color: var(--danger);
}

.mrdiff-findrow[data-kind='issue'] .mrdiff-fbar {
  background: var(--warning);
}

.mrdiff-findrow[data-kind='issue'] .mrdiff-fkind {
  color: var(--warning);
}

/*
 * `--info`, matching the Improvement badge on the Finding's own card — and pointedly not
 * `--accent`. This project has one accent colour and the Review Manifest's crucial marker owns it
 * (AGENTS.md), so an Improvement wearing it put the loudest signal in the tree on the mildest news
 * while impersonating the one thing a Reviewer must not miss.
 */
.mrdiff-findrow[data-kind='improvement'] .mrdiff-fbar {
  background: var(--info);
}

.mrdiff-findrow[data-kind='improvement'] .mrdiff-fkind {
  color: var(--info);
}

.mrdiff-findrow[data-kind='thread'] .mrdiff-fbar {
  background: var(--border-strong);
}

.mrdiff-findrow[data-kind='thread'] .mrdiff-fkind {
  color: var(--text-tertiary);
}

/*
 * A draft's row is tinted, not just its bar — `--warning-wash`, the same background the draft's
 * own card wears (`.mrdiff-draft`, MergeRequestDiffScreen.css) and the same tone the "Unsent" badge
 * carries, so a Reviewer learns the colour once and reads it everywhere.
 */
.mrdiff-findrow[data-kind='draft'] {
  background: var(--warning-wash);
}

.mrdiff-findrow[data-kind='draft'] .mrdiff-fbar {
  background: var(--warning);
}

.mrdiff-findrow[data-kind='draft'] .mrdiff-fkind {
  color: var(--warning);
}

/*
 * The overflow row of a file with more Findings than the tree shows at once (spec edge case). It
 * selects the file, whose card renders every one of them inline — the tree stays a list, and
 * nothing behind this row is lost.
 */
.mrdiff-findmore .mrdiff-fbar {
  background: var(--border);
}

.mrdiff-findmore .mrdiff-ftitle {
  color: var(--text-tertiary);
}
/**
 * The pattern editor (PRO-01, PRO-12). A modal over a diff a Reviewer is part-way through, so it
 * stays small, keeps the preview above the fold, and never scrolls the page behind it.
 */

/* The backdrop and card come from ConfirmDialog.css — one modal shell for the whole app. What is
   left here is only what a pattern editor needs beyond it. */

.override-dialog__title {
  margin: 0;
  font-size: var(--fs-md, 1rem);
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
}

.override-dialog__subtitle {
  margin: 0;
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: var(--text-secondary);
}

.override-dialog__suggestions {
  display: flex;
  flex-direction: column;
  gap: var(--space-1, 4px);
  margin: 0;
  padding: 0;
  list-style: none;
}

.override-dialog__suggestion {
  display: flex;
  gap: var(--space-2);
  align-items: baseline;
  justify-content: space-between;
  width: 100%;
  padding: var(--space-2);
  font-family: inherit;
  font-size: var(--fs-sm);
  color: var(--text-primary);
  text-align: left;
  cursor: pointer;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm, 6px);
}

.override-dialog__suggestion:hover {
  border-color: var(--accent);
}

.override-dialog__suggestion--picked {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}

/* Both halves of a suggestion row carry paths, which are long and arbitrary — a localisation file
   nested six directories deep is a normal thing to right-click. `min-width: 0` is what lets a flex
   child shrink below its content's intrinsic width at all; without it the row overflows the card
   rather than wrapping, and the pattern (the half a Reviewer is choosing between) is the part that
   gets clipped. */
.override-dialog__suggestion-label {
  min-width: 0;
  overflow-wrap: anywhere;
  color: var(--text-secondary);
}

.override-dialog__suggestion-pattern {
  min-width: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-xs, 0.75rem);
  color: var(--text-primary);
  /* Right-aligned so a wrapped pattern stays visually anchored to the edge it started at, and
     `anywhere` rather than `break-word` because a path has no spaces to break at — the browser
     would otherwise keep it on one overflowing line. */
  text-align: right;
  overflow-wrap: anywhere;
}

.override-dialog__field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1, 4px);
}

.override-dialog__field-label {
  font-size: var(--fs-xs, 0.75rem);
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.override-dialog__input {
  padding: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--text-primary);
  background: var(--bg-base, transparent);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm, 6px);
}

.override-dialog__input[aria-invalid='true'] {
  border-color: var(--danger, #c0392b);
}

.override-dialog__preview {
  display: flex;
  flex-direction: column;
  gap: var(--space-1, 4px);
  min-height: 3.5rem;
  padding: var(--space-2);
  background: var(--bg-subtle, transparent);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm, 6px);
}

.override-dialog__preview-note {
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

.override-dialog__preview-note--danger {
  color: var(--danger, #c0392b);
}

.override-dialog__preview-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 9rem;
  margin: 0;
  padding: 0;
  overflow-y: auto;
  list-style: none;
}

/* The same reason as the suggestion rows: these are matched file paths, so they are long, they have
   no spaces to break at, and the list already scrolls vertically — it must never scroll sideways
   or push the card wider. */
.override-dialog__preview-list code {
  font-family: var(--font-mono);
  font-size: var(--fs-xs, 0.75rem);
  color: var(--text-primary);
  overflow-wrap: anywhere;
}

.override-dialog__preview-more {
  font-size: var(--fs-xs, 0.75rem);
  color: var(--text-tertiary);
}

.override-dialog__error {
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--danger, #c0392b);
}

.override-dialog__actions {
  display: flex;
  gap: var(--space-2);
  justify-content: flex-end;
}

.override-dialog__cancel,
.override-dialog__save {
  padding: var(--space-2) var(--space-3);
  font-family: inherit;
  font-size: var(--fs-sm);
  cursor: pointer;
  border-radius: var(--radius-sm, 6px);
}

.override-dialog__cancel {
  color: var(--text-secondary);
  background: transparent;
  border: 1px solid var(--border);
}

.override-dialog__save {
  color: var(--on-accent, #fff);
  background: var(--accent);
  border: 1px solid var(--accent);
}

.override-dialog__save:disabled {
  cursor: not-allowed;
  opacity: 0.5;
}
/*
 * Full screen (FFO-01) — the diff this screen already renders, given the whole window.
 *
 * Deliberately a **mode**, expressed as a class on the screen's own root, rather than an overlay
 * that renders the diff a second time. Nothing is re-parented, so no card unmounts: expanded
 * context, open composers and the column's scroll position all survive going in and coming out,
 * and scrolling stays continuous across every file exactly as it is outside this mode.
 */
.mrdiff-fullscreen {
  position: fixed;
  inset: 0;
  z-index: 40;
  padding: 0;
  background: var(--bg-app);
  gap: 0;
}

/*
 * Everything that is not the diff steps aside: the merge request header, the toolbar row, the merge
 * bar, the discussion panels. They are hidden rather than unmounted — a Reviewer leaving full
 * screen finds them exactly as they left them, and nothing they hold has to be re-fetched.
 */
.mrdiff-fullscreen > *:not(.mrdiff-layout):not(.mrdiff-fsbar) {
  display: none;
}

/*
 * The file tree goes with them: the whole point of this mode is the diff at full width, and the
 * tree is the surface a Reviewer uses to *choose* a file rather than to read one.
 */
.mrdiff-fullscreen .mrdiff-tree {
  display: none;
}

.mrdiff-fullscreen .mrdiff-layout {
  grid-template-columns: minmax(0, 1fr);
  flex: 1 1 auto;
  min-height: 0;
  padding: 0 16px 16px;
}

.mrdiff-fsbar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 0 0 auto;
  padding: 8px 16px;
  background: var(--bg-surface-2);
  border-bottom: 1px solid var(--border);
}

.mrdiff-fslabel {
  font-family: var(--font-mono);
  font-size: 12.5px;
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/*
 * The keyboard way out, said once and quietly — the button beside it is the same door.
 */
.mrdiff-fshint {
  flex: 0 0 auto;
  padding: 1px 6px;
  border: 1px solid var(--border);
  border-radius: 4px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--text-tertiary);
}

/*
 * A run in flight, in the one row that is always on screen (AOD-09, DWB-09). The analysing card at
 * the top of the diff column says the same thing at length; here it has to fit beside the file's
 * name, so it is a glyph, a clock and a bar.
 */
.mrdiff-fsanalysing {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  flex: 0 0 auto;
  padding: 2px 9px;
  border: 1px solid var(--accent-border, var(--border));
  border-radius: var(--radius-pill);
  background: var(--accent-wash, var(--bg-inset));
  font-size: 11.5px;
  color: var(--text-secondary);
  white-space: nowrap;
}

/*
 * Indeterminate, drawn here rather than by the DS ProgressBar, which takes a value and has no
 * indeterminate mode — the same reason the analysing card draws its own.
 */
.mrdiff-fsanalysing-bar {
  width: 46px;
  height: 3px;
  border-radius: 2px;
  background: linear-gradient(
    90deg,
    transparent 0%,
    var(--accent) 35%,
    var(--accent) 65%,
    transparent 100%
  );
  background-size: 200% 100%;
  animation: mrdiff-fsanalysing-slide 1.1s linear infinite;
}

@keyframes mrdiff-fsanalysing-slide {
  from {
    background-position: 200% 0;
  }
  to {
    background-position: -200% 0;
  }
}
/* Opening a merge request (NMR-01). A two-page wizard in one card: a fixed head and foot with the
   step's own body scrolling between them, so the way out and the primary action stay reachable in a
   namespace of hundreds of projects or a repository of a hundred branches. */

/* Wide enough for the form and the commit sidebar side by side on step 2; step 1 simply gets the
   room, which its full project paths use. */
.nmr-card {
  width: min(62rem, 100%);
  max-height: min(88vh, 46rem);
  padding: 0;
  display: flex;
  flex-direction: column;
}

.nmr-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: 0 0 auto;
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--border);
  background: var(--bg-surface-2);
}

.nmr-title {
  margin: 0;
  font-size: var(--fs-h3, 1rem);
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
}

/* `Step 1 of 2` — mono, like every other count and identifier in this app. */
.nmr-step {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-tertiary);
}

.nmr-body {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: var(--space-4) var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  min-height: 0;
}

.nmr-foot {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: 0 0 auto;
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--border);
  background: var(--bg-surface-2);
}

.nmr-spacer {
  flex: 1 1 auto;
}

.nmr-section,
.nmr-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

.nmr-section-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding-bottom: var(--space-1);
}

/* Small eyebrow labels are the one place this design system uses uppercase. */
.nmr-label {
  font-size: 11px;
  font-weight: var(--fw-semibold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}

.nmr-crumbs {
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--accent);
}

.nmr-rule {
  flex: 1 1 auto;
  height: 1px;
  background: var(--border);
}

.nmr-hint,
.nmr-notice {
  font-size: 11.5px;
  color: var(--text-tertiary);
  /* Reserves the line even when the hint is a single space, so a row of fields does not jump as
     hints appear and disappear. */
  min-height: 1em;
}

.nmr-notice {
  margin: 0;
  padding: var(--space-4) var(--space-3);
  text-align: center;
}

.nmr-list {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

/* The branch list is the one that can run to a hundred rows, so it scrolls inside itself rather
   than pushing the title and description off the card. */
.nmr-list--scroll {
  max-height: 13rem;
  overflow-y: auto;
}

/*
 * A fixed viewport of about six rows for the lists that change under the Reviewer — the group
 * navigator and the search results.
 *
 * `height`, not `max-height`, and that is the entire point: with `max-height` the card resized on
 * every step into a group, so the dialog jumped and whatever the Reviewer was aiming at moved. A
 * constant height means walking three levels down and back up never moves the modal by a pixel — a
 * group with two children and one with forty look the same from the outside, and the difference is
 * inside the scroll instead.
 *
 * It holds the loading and empty states at the same height too, for the same reason: those are the
 * states that alternate fastest.
 */
.nmr-list--viewport {
  height: 13rem;
  overflow-y: auto;
}

/*
 * Search results, grouped by top-level GitLab group (NMR-09) — the same shape the merge request list
 * groups its own rows in. The viewport scrolls the whole set of groups rather than each group having
 * its own scroll, so the dialog stays one fixed height however many groups a term matches.
 */
.nmr-result-groups {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  /* The group headings are the structure; a border around the lot would compete with them. */
  border: 0;
}

.nmr-result-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

/* The group's own name, mono like every other identifier — and never prefixed with the namespace,
   which is the same for every row on this screen and so tells a reader nothing. */
.nmr-group-name {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: var(--fw-semibold);
  color: var(--text-secondary);
}

/*
 * People grouped by Team in the reviewer picker (NMR-21). The heading sticks while its own group
 * scrolls, so it is always clear whose Team the row under the cursor belongs to in a long list.
 */
.nmr-member-group {
  display: flex;
  flex-direction: column;
}

.nmr-member-group-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  position: sticky;
  top: 0;
  z-index: 1;
  padding: var(--space-1) var(--space-3);
  background: var(--bg-surface-2);
  border-bottom: 1px solid var(--border);
}

/* A person's display name in the reviewer picker — sans, unlike the mono identifiers around it,
   because a name is a name rather than a handle. Their username sits beside it in mono. */
.nmr-member-name {
  font-size: 12.5px;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

/* Every row is a real button: keyboard-operable, and it reads as the control it is. */
.nmr-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border: 0;
  border-bottom: 1px solid var(--border);
  background: var(--bg-surface);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.nmr-row:last-child {
  border-bottom: 0;
}

.nmr-row:hover {
  background: var(--bg-hover);
}

.nmr-row[data-chosen='true'] {
  background: var(--accent-wash);
  box-shadow: inset 3px 0 0 var(--accent);
}

/*
 * What clicking a project row does, shown where the click happens.
 *
 * Hidden until the row is hovered or keyboard-focused, so a list of forty projects is forty paths
 * rather than forty buttons — and it reserves no width when hidden, because a column of invisible
 * buttons would push every path's ellipsis leftwards for nothing.
 */
.nmr-row-action {
  display: none;
  align-items: center;
  gap: var(--space-1);
  flex: 0 0 auto;
  padding: 1px var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--accent);
  color: var(--accent-contrast);
  font-size: 11px;
  font-weight: var(--fw-semibold);
  white-space: nowrap;
}

.nmr-row:hover .nmr-row-action,
.nmr-row:focus-visible .nmr-row-action {
  display: inline-flex;
}

/* Full paths and branch names are identifiers: mono, and they ellipsize rather than wrap, because a
   wrapped path is harder to compare against the fork beside it. */
.nmr-path {
  font-family: var(--font-mono);
  font-size: 12.5px;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.nmr-dim {
  color: var(--text-tertiary);
}

.nmr-meta {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-tertiary);
  white-space: nowrap;
}

/*
 * Step 2: the form, and what it is describing (NMR-17).
 *
 * The sidebar is fixed-width and the form takes the rest, so a long branch path narrows the field it
 * sits in rather than squeezing the commit list into unreadability. `min-height: 0` on both is what
 * lets each scroll inside itself instead of stretching the card.
 */
.nmr-body--split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 19rem;
  gap: var(--space-5);
  align-items: stretch;
}

.nmr-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  min-width: 0;
}

/*
 * The sidebar column, and the one rule that matters here: **the form decides the height**.
 *
 * The commit list is taken out of flow (`absolute` inside this `relative` column) so its eight long
 * commit messages cannot stretch the grid row — which is exactly what they were doing, growing the
 * dialog past the form and leaving the Create button stranded halfway up. Out of flow, the row is as
 * tall as the form, the list fills precisely that, and the difference goes into its own scroll.
 */
.nmr-commits-column {
  position: relative;
  min-width: 0;
}

/*
 * The commit list. A reading surface: no row is a control, and the sha is the way out to GitLab for
 * the message and the diff this list deliberately does not carry.
 */
.nmr-commits {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
  min-height: 0;
}

/* Takes the height the sidebar has left, so the list ends where the dialog ends rather than at an
   arbitrary row count. `min-height: 0` is what allows it to shrink inside the grid and scroll. */
.nmr-commits-body {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-surface);
}

.nmr-commit {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border);
}

.nmr-commit:last-child {
  border-bottom: 0;
}

/* Mono with tabular figures, like every other identifier in this app. */
.nmr-commit-sha {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--accent);
}

.nmr-commit-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

/* Somebody's git subject: wrapped rather than ellipsized, because a truncated commit message is
   worth less than the two lines it would have taken. */
.nmr-commit-title {
  font-size: 12px;
  color: var(--text-primary);
  overflow-wrap: anywhere;
}

/*
 * The anchor a branch dropdown hangs from. Only the field itself — not its label or hint — so the
 * list opens flush under the control rather than under the whole block.
 */
.nmr-picker {
  position: relative;
}

/*
 * The open branch list, floating over the page.
 *
 * Absolute rather than in-flow, and that is the point: the title and description below must not move
 * when a hundred branches appear. The dialog is exactly the same size open or closed, so picking a
 * branch never shifts what the Reviewer was about to click.
 */
.nmr-dropdown,
/*
 * The reviewer list floats the same way, out of the same anchor, when the create wizard is the one
 * showing it. Scoped to `.nmr-picker` on purpose: the diff screen shows the very same list inside a
 * panel that already floats, and there the list has to stay in flow.
 */
.nmr-picker > .nmr-dropdown-list {
  position: absolute;
  top: calc(100% + 2px);
  left: 0;
  right: 0;
  z-index: 2;
  max-height: 13rem;
  overflow-y: auto;
  background: var(--bg-elevated);
  border-color: var(--border-strong);
  box-shadow: var(--shadow-lg);
}

/*
 * The collapsed branch picker: what was chosen, and a way back to the list.
 *
 * Styled as the inset field it replaces rather than as a button, so collapsing does not make the row
 * jump — it occupies exactly the height the filter input did.
 */
.nmr-combo {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  height: 2rem;
  padding: 0 var(--space-3);
  background: var(--bg-inset);
  color: var(--text-primary);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.nmr-combo:hover {
  border-color: var(--accent);
}

/* The project chosen on page 1, named on page 2 — its title, not its path: the path was how it was
   found, and this page is about the change. */
.nmr-project {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  font-size: var(--fs-h3, 1rem);
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
}

/*
 * The description, rendered rather than raw while it is not being edited (NMR-19).
 *
 * Shaped as the textarea it replaces so the form does not jump between the two, and it is a real
 * button: clicking anywhere in the rendered text is how you get back to editing it.
 */
.nmr-preview {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  text-align: left;
  cursor: text;
  overflow-y: auto;
  max-height: 12rem;
}

.nmr-preview-block[data-kind='heading'] {
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
}

.nmr-preview-block[data-kind='item'] {
  padding-left: var(--space-3);
}

.nmr-preview-block[data-kind='code'] {
  font-family: var(--font-mono);
  font-size: 12px;
  white-space: pre-wrap;
  padding: var(--space-2);
  background: var(--bg-surface-2);
  border-radius: var(--radius-sm);
}

.nmr-preview-block[data-kind='quote'] {
  padding-left: var(--space-3);
  border-left: 2px solid var(--border-strong);
  color: var(--text-secondary);
}

/* The project chosen on page 1, restated on page 2 — the only context that page needs. */
.nmr-chosen {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

.nmr-branches {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-4);
}

.nmr-textarea {
  width: 100%;
  box-sizing: border-box;
  min-height: 7rem;
  resize: vertical;
  padding: var(--space-2) var(--space-3);
  background: var(--bg-inset);
  color: var(--text-primary);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  font-family: var(--font-sans);
  font-size: 13px;
  line-height: 1.6;
}

.nmr-textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: var(--shadow-focus-amber);
}

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

/* A refusal states itself where the action was taken, and the wizard keeps everything typed
   (NMR-12). It is not a toast: a toast would take the sentence away while the Reviewer is still
   deciding what to do about it. */
.nmr-refusal-bar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: 0 0 auto;
  padding: var(--space-2) var(--space-4);
  border-top: 1px solid var(--border);
  background: var(--warning-wash);
  font-size: 12.5px;
  color: var(--text-primary);
}

.nmr-refusal {
  font-size: 11.5px;
  color: var(--danger);
}
/*
 * The reviewers control on the diff toolbar (NMR-23): the chip states who reviews this, and the list
 * that changes it opens directly underneath.
 *
 * The chip itself is styled beside its twin in `MergeRequestDiffScreen.css` — it has to match the
 * commit-count chip exactly, so the two live together. Only the dropdown is here.
 */

/* The anchor. `inline-flex` so it occupies exactly the chip's own width in the toolbar row, and
   nothing about opening the list changes that. */
.mrdiff-reviewers {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  flex: 0 0 auto;
  position: relative;
}

/* Open, the chip reads as pressed rather than merely hovered — a toggle should look like one. */
.mrdiff-reviewers-chip[data-open='true'] {
  background: var(--bg-active);
  color: var(--text-primary);
}

/*
 * A failed change, stated beside the chip. Never a toast: the fact it corrects — who reviews this —
 * is right there, so its correction belongs there too.
 */
.mrdiff-reviewers-failed {
  flex: 0 1 auto;
  font-size: var(--fs-xs);
  color: var(--danger);
}

/*
 * The floating list.
 *
 * `z-index: 4` clears everything the diff column pins: the card headers sit at 2 and the file
 * composer at 3 (see `MergeRequestDiffScreen.css`), and a reviewer list disappearing behind a pinned
 * filename would be a control that only works at the top of the page.
 *
 * A fixed width rather than the chip's, because the chip is as wide as whatever usernames happen to
 * be assigned — "No reviewers" would give a list 6rem to render people in. Left-aligned to the chip,
 * which is far enough from the right edge of the toolbar for the panel to fit.
 */
.mrdiff-reviewers-dropdown {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 4;
  width: 22rem;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-2);
  background: var(--bg-elevated);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  /* Against the flat-surface house rule, deliberately, and for the same reason the composer overlay
     breaks it: this covers code, so it has to read as being *over* the code. */
  box-shadow: var(--shadow-lg);
  text-align: left;
}

/*
 * Inside this panel the list stays **in flow** — the panel is already the thing that floats. (In the
 * create wizard the same list floats on its own, out of a field; that rule is scoped to `.nmr-picker`
 * so the two cannot collide.)
 */
.mrdiff-reviewers-dropdown .nmr-dropdown-list {
  max-height: 15rem;
  overflow-y: auto;
}
/* This project's Review Filter rules, edited from the diff (PRO-08). Wider than a confirmation
   because it lists patterns, which must not wrap mid-glob. */

.overrides-dialog {
  width: min(40rem, 100%);
}

.overrides-dialog__title {
  margin: 0;
  font-size: var(--fs-md, 1rem);
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
  overflow-wrap: anywhere;
}

.overrides-dialog__subtitle,
.overrides-dialog__empty {
  margin: 0;
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: var(--text-secondary);
}

.overrides-dialog__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-1, 4px);
  margin: 0;
  padding: 0;
  list-style: none;
}

.overrides-dialog__entry {
  display: flex;
  gap: var(--space-2);
  align-items: baseline;
  padding: var(--space-1, 4px) var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm, 6px);
}

.overrides-dialog__pattern {
  flex: 1 1 auto;
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--text-primary);
  overflow-wrap: anywhere;
}

.overrides-dialog__kind {
  flex: 0 0 auto;
  font-size: var(--fs-xs, 0.75rem);
  color: var(--text-secondary);
}

.overrides-dialog__kind[data-kind='crucial'] {
  color: var(--accent);
}

.overrides-dialog__attribution {
  flex: 0 0 auto;
  font-size: var(--fs-xs, 0.75rem);
  color: var(--text-tertiary);
}

.overrides-dialog__error {
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--danger, #c0392b);
}

.overrides-dialog__actions {
  display: flex;
  gap: var(--space-2);
  align-items: center;
}

.overrides-dialog__spacer {
  flex: 1 1 auto;
}
/* The changelog, over whatever screen the Reviewer was on. Taller and narrower than a confirmation
   — it is read, not decided — and it scrolls inside the card so the page behind it never moves. */

.changelog-dialog {
  width: min(44rem, 100%);
  max-height: min(84vh, 52rem);
  padding: 0;
}

/*
 * The header does not scroll: the list grows with every release, and a dismiss that has to be
 * scrolled back up to is one a Reviewer will not use.
 */
.changelog-dialog__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex: 0 0 auto;
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--border);
}

.changelog-dialog__title {
  margin: 0;
  font-size: var(--fs-h3, 1rem);
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
}

.changelog-dialog__body {
  display: flex;
  flex-direction: column;
  padding: var(--space-4) var(--space-5) var(--space-5);
  overflow-y: auto;
}

/*
 * `v0.7.0 · 2026-08-15`, in mono — a version and its date are written that way everywhere else in
 * this app, and a monospaced heading makes the releases line up down the list so the eye can drop
 * between them.
 */
.changelog-dialog__release {
  margin: var(--space-5) 0 var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  letter-spacing: -0.01em;
  color: var(--text-primary);
}

.changelog-dialog__release:first-child {
  margin-top: 0;
}

/*
 * A real bullet, hanging in the margin so wrapped lines align under the first word rather than
 * under the dot.
 */
.changelog-dialog__item {
  position: relative;
  margin: 0 0 var(--space-2);
  padding-left: var(--space-4);
  font-size: var(--fs-sm);
  line-height: 1.6;
  color: var(--text-secondary);
}

.changelog-dialog__item::before {
  content: '•';
  position: absolute;
  left: var(--space-1, 4px);
  color: var(--text-tertiary);
}

/*
 * Added / Changed / Fixed / Removed, over the bullets they group. The accent's one job here: a
 * Reviewer scanning a release finds the fixes among the additions without reading either. A heading
 * rather than a word on every line — the tag is the same for all of them, so it is printed once.
 */
.changelog-dialog__tag {
  margin: var(--space-3) 0 var(--space-2);
  color: var(--accent);
  font-size: var(--fs-xs, 0.75rem);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.changelog-dialog__release + .changelog-dialog__tag {
  margin-top: var(--space-1, 4px);
}

.changelog-dialog__body strong {
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
}

.changelog-dialog__body code {
  font-family: var(--font-mono);
  font-size: var(--fs-xs, 0.75rem);
  color: var(--text-primary);
}
/* The merge request's description, over the diff it belongs to (MRS-01/02). Prose width rather than
   the commits dialog's table width — this is read as text, and a 900px line of prose is not. */

.summary-dialog {
  width: min(46rem, 100%);
  max-height: min(84vh, 50rem);
  padding: 0;
}

.summary-dialog__head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: 0 0 auto;
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--border);
}

.summary-dialog__title {
  margin: 0;
  overflow: hidden;
  font-size: var(--fs-h3, 1rem);
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* `!482` — mono, the way this tool names a merge request everywhere else. */
.summary-dialog__iid {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-tertiary);
}

.summary-dialog__body {
  flex: 1 1 auto;
  padding: var(--space-4) var(--space-5) var(--space-5);
  overflow-y: auto;
}

.summary-dialog__empty {
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

/*
 * The author's own section headings. Uppercase and small at every level: a description's headings
 * organise it, while the dialog's title names it, and a description that opens with `#` must not
 * out-shout the merge request it belongs to. The three levels differ in tone rather than in size,
 * so nesting still reads without a heading becoming a banner.
 */
.summary-dialog__heading {
  margin: var(--space-4) 0 var(--space-2);
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: var(--fw-semibold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}

.summary-dialog__heading[data-level='1'] {
  color: var(--text-secondary);
}

.summary-dialog__heading:first-child {
  margin-top: 0;
}

.summary-dialog__paragraph {
  margin: 0 0 var(--space-3);
  font-size: 13.5px;
  line-height: 1.65;
  color: var(--text-primary);
  overflow-wrap: anywhere;
}

/*
 * Items are paragraphs with a mark rather than a real `<ul>`: the reader emits one block per line
 * (it does not track nesting), so a list built from `<li>` would claim a structure the parse does
 * not actually know.
 */
.summary-dialog__item {
  display: flex;
  gap: var(--space-2);
  margin: 0 0 var(--space-2);
  padding-left: var(--space-3);
  font-size: 13.5px;
  line-height: 1.65;
  color: var(--text-primary);
  overflow-wrap: anywhere;
}

.summary-dialog__bullet {
  flex: 0 0 auto;
  width: 4px;
  height: 4px;
  margin-top: 0.62em;
  border-radius: 50%;
  background: var(--text-tertiary);
}

/* A numbered item keeps its own number in the text, so its mark is only an indent, not a second
   bullet in front of the digit the author wrote. */
.summary-dialog__item[data-ordered='true'] .summary-dialog__bullet {
  background: transparent;
}

.summary-dialog__quote {
  margin: 0 0 var(--space-3);
  padding-left: var(--space-3);
  border-left: 2px solid var(--border-strong);
  font-size: 13.5px;
  line-height: 1.65;
  color: var(--text-secondary);
}

.summary-dialog__code {
  margin: 0 0 var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--bg-surface-2);
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.55;
  color: var(--text-primary);
  /* A fenced block scrolls rather than widening the dialog — code inside prose is the one thing
     here that legitimately runs long. */
  overflow-x: auto;
  white-space: pre;
}

.summary-dialog__rule {
  height: 0;
  margin: var(--space-4) 0;
  border: none;
  border-top: 1px solid var(--border);
}

/* `**bold**` and `` `code` `` from the author's text, rendered by `inlineMarkup`. */
.summary-dialog__body strong {
  font-weight: var(--fw-semibold);
}

.summary-dialog__body code {
  padding: 1px 4px;
  border-radius: var(--radius-xs);
  background: var(--bg-surface-2);
  font-family: var(--font-mono);
  font-size: 0.92em;
}

.summary-dialog__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex: 0 0 auto;
  padding: var(--space-3) var(--space-5);
  border-top: 1px solid var(--border);
}

/* Who opened it and when it last changed — mono and quiet, a fact rather than an action. */
.summary-dialog__provenance {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-tertiary);
}

.summary-dialog__actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
/*
 * Transient statements about what just happened (2026-08-18) — what the Analyse and Send Review
 * controls used to say inline, between their own buttons.
 *
 * Fixed to the window rather than placed in the layout, for two reasons: the action group keeps its
 * shape when an action settles, and the statements are still on screen in full screen, where the
 * whole header they used to live in has stepped aside.
 */
.mrdiff-toasts {
  position: fixed;
  right: 20px;
  bottom: 20px;
  z-index: 60;
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-width: min(460px, calc(100vw - 40px));
}

.mrdiff-toast {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 10px 9px 14px;
  border: 1px solid var(--border);
  border-left-width: 3px;
  border-radius: 8px;
  background: var(--bg-surface);
  box-shadow: var(--shadow-md, 0 6px 20px rgb(0 0 0 / 25%));
  font-size: 12.5px;
  color: var(--text-secondary);
}

.mrdiff-toast-text {
  flex: 1 1 auto;
  min-width: 0;
  line-height: 1.5;
}

/*
 * The left edge carries the tone, and nothing else does: a whole panel in `danger` red for a
 * spent budget would read as a fault, which is exactly what these sentences are not.
 */
.mrdiff-toast[data-toast='success'] {
  border-left-color: var(--success);
}

.mrdiff-toast[data-toast='danger'] {
  border-left-color: var(--danger);
}

.mrdiff-toast[data-toast='info'] {
  border-left-color: var(--accent);
}
:root{--diff-background-color:initial;--diff-text-color:initial;--diff-font-family:Consolas,Courier,monospace;--diff-selection-background-color:#b3d7ff;--diff-selection-text-color:var(--diff-text-color);--diff-gutter-insert-background-color:#d6fedb;--diff-gutter-insert-text-color:var(--diff-text-color);--diff-gutter-delete-background-color:#fadde0;--diff-gutter-delete-text-color:var(--diff-text-color);--diff-gutter-selected-background-color:#fffce0;--diff-gutter-selected-text-color:var(--diff-text-color);--diff-code-insert-background-color:#eaffee;--diff-code-insert-text-color:var(--diff-text-color);--diff-code-delete-background-color:#fdeff0;--diff-code-delete-text-color:var(--diff-text-color);--diff-code-insert-edit-background-color:#c0dc91;--diff-code-insert-edit-text-color:var(--diff-text-color);--diff-code-delete-edit-background-color:#f39ea2;--diff-code-delete-edit-text-color:var(--diff-text-color);--diff-code-selected-background-color:#fffce0;--diff-code-selected-text-color:var(--diff-text-color);--diff-omit-gutter-line-color:#cb2a1d;}.diff{background-color:var(--diff-background-color);border-collapse:collapse;color:var(--diff-text-color);table-layout:fixed;width:100%;}.diff::-moz-selection{background-color:#b3d7ff;background-color:var(--diff-selection-background-color);color:var(--diff-text-color);color:var(--diff-selection-text-color);}.diff::selection{background-color:#b3d7ff;background-color:var(--diff-selection-background-color);color:var(--diff-text-color);color:var(--diff-selection-text-color);}.diff td{padding-bottom:0;padding-top:0;vertical-align:top;}.diff-line{font-family:Consolas,Courier,monospace;font-family:var(--diff-font-family);line-height:1.5;}.diff-gutter>a{color:inherit;display:block;}.diff-gutter{cursor:pointer;padding:0 1ch;text-align:right;-webkit-user-select:none;-moz-user-select:none;user-select:none;}.diff-gutter-insert{background-color:#d6fedb;background-color:var(--diff-gutter-insert-background-color);color:var(--diff-text-color);color:var(--diff-gutter-insert-text-color);}.diff-gutter-delete{background-color:#fadde0;background-color:var(--diff-gutter-delete-background-color);color:var(--diff-text-color);color:var(--diff-gutter-delete-text-color);}.diff-gutter-omit{cursor:default;}.diff-gutter-selected{background-color:#fffce0;background-color:var(--diff-gutter-selected-background-color);color:var(--diff-text-color);color:var(--diff-gutter-selected-text-color);}.diff-code{word-wrap:break-word;padding:0 0 0 .5em;white-space:pre-wrap;word-break:break-all;}.diff-code-edit{color:inherit;}.diff-code-insert{background-color:#eaffee;background-color:var(--diff-code-insert-background-color);color:var(--diff-text-color);color:var(--diff-code-insert-text-color);}.diff-code-insert .diff-code-edit{background-color:#c0dc91;background-color:var(--diff-code-insert-edit-background-color);color:var(--diff-text-color);color:var(--diff-code-insert-edit-text-color);}.diff-code-delete{background-color:#fdeff0;background-color:var(--diff-code-delete-background-color);color:var(--diff-text-color);color:var(--diff-code-delete-text-color);}.diff-code-delete .diff-code-edit{background-color:#f39ea2;background-color:var(--diff-code-delete-edit-background-color);color:var(--diff-text-color);color:var(--diff-code-delete-edit-text-color);}.diff-code-selected{background-color:#fffce0;background-color:var(--diff-code-selected-background-color);color:var(--diff-text-color);color:var(--diff-code-selected-text-color);}.diff-widget-content{vertical-align:top;}.diff-gutter-col{width:7ch;}.diff-gutter-omit{height:0;}.diff-gutter-omit:before{background-color:#cb2a1d;background-color:var(--diff-omit-gutter-line-color);content:" ";display:block;height:100%;margin-left:4.6ch;overflow:hidden;white-space:pre;width:2px;}.diff-decoration{line-height:1.5;-webkit-user-select:none;-moz-user-select:none;user-select:none;}.diff-decoration-content{font-family:Consolas,Courier,monospace;font-family:var(--diff-font-family);padding:0;}/*
 * Diff screen overrides (DIF-03/04/11): maps the vendored DS tokens onto react-diff-view's CSS
 * variables and generated classes, per the approved visual reference
 * (diff-screen-visual-reference.md). The two rgba() word-mark values are the design's own
 * literals — everything else goes through var(--…) tokens.
 */

.mrdiff {
  --diff-background-color: var(--bg-surface);
  --diff-text-color: var(--text-primary);
  --diff-font-family: var(--font-mono);
  --diff-gutter-insert-background-color: var(--success-wash);
  --diff-gutter-delete-background-color: var(--danger-wash);
  --diff-code-insert-background-color: var(--success-wash);
  --diff-code-delete-background-color: var(--danger-wash);
  --diff-code-insert-edit-background-color: rgba(34, 197, 94, 0.28);
  --diff-code-delete-edit-background-color: rgba(239, 68, 68, 0.25);
}

/*
 * One changed file's card (DIF-02). `overflow: clip` rather than `hidden`: both keep the rounded
 * corners, but `hidden` would make the card its own scroll container, and the header inside it
 * would then stick to a box that never scrolls instead of to the diff column (DVR-13).
 */
.mrdiff-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  overflow: clip;
}

/*
 * While a floating panel is out — the actions menu (DVR-06) or the whole-file composer (MLC-06) —
 * the card must neither clip it nor let its neighbours paint over it.
 *
 * Clipping first: `overflow: clip` is what rounds the card's corners, and a collapsed or stub-only
 * card is barely taller than its header, so a panel hanging below that header is cut off a few
 * pixels down (seen on both panels during UAT, 2026-08-20).
 */
.mrdiff-card.overlay-open {
  overflow: visible;
}

/*
 * DVR-13: the file header pins to the top of the diff column while its file is being read, and
 * releases when the card ends — `sticky` inside the card, so the containing block is the card
 * itself and no two headers can ever be pinned at once.
 */
.mrdiff-cardhead {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 14px;
  background: var(--bg-surface-2);
}

/*
 * The layering half of `.overlay-open` above, kept after the rule it overrides — and it is only
 * about the **menu**: the menu is rendered inside the header, so it sits in the stacking context
 * that `z-index: 2` creates, and no z-index of its own can lift it past a *sibling* card's header —
 * equal rank, and DOM order hands it to whichever card comes later, which is why later headers used
 * to draw across an open list. Raising the one card's header settles it. The composer overlay never
 * had this problem: it is a sibling of the header rather than a child, so its own `z-index: 3`
 * already outranks the neighbours' 2 — and it stays below the 4 here deliberately, sliding under
 * its own header as the file scrolls.
 */
.mrdiff-card.overlay-open .mrdiff-cardhead {
  z-index: 4;
}

/*
 * DVR-12: a collapsed file has been dealt with, so its header recedes — the app background instead
 * of the raised surface, tertiary type instead of primary. Only the type and the wash change:
 * the checkbox, the actions menu and any pending line keep their own colours, because a finished
 * file still has to be re-openable and its remaining work still has to be readable.
 */
.mrdiff-cardhead.collapsed {
  background: var(--bg-app);
}

.mrdiff-cardhead.collapsed .mrdiff-cardpath {
  color: var(--text-tertiary);
  font-weight: var(--fw-medium);
}

.mrdiff-cardpath {
  min-width: 0;
  overflow: hidden;
  font-family: var(--font-mono);
  font-size: 12.5px;
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.mrdiff-cardstats {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-tertiary);
}

/*
 * The `+`/`−` counts, wherever they appear — the toolbar's totals and each card's own (2026-08-20).
 *
 * Their own greens and reds rather than the design system's `--success`/`--danger`: those two are
 * signal colours for states a Reviewer must act on (a failed pipeline, a destructive control), and a
 * diff's line counts are neither good news nor bad. These read at full strength against the light
 * canvas without borrowing that meaning — strengthened from a first, too-quiet pass on the same day.
 * Literal values for the same reason the diff washes above are literals: the vendored token set
 * carries one green and one red, and this screen's variants of them belong here rather than in the
 * design system.
 */
.mrdiff-additions {
  /* Tokens, not literals: these two were the only colours on this screen that could not follow the
     theme, and on a dark surface they failed contrast outright. */
  color: var(--success);
}

.mrdiff-deletions {
  color: var(--danger);
}

/*
 * The one line a viewed-collapsed card may still show (DVR-03): what is still open on a file the
 * Reviewer has finished with. It is deliberately not muted like the header above it — it is the
 * reason the file is still worth a glance.
 */
.mrdiff-collapsed-pending {
  padding: 6px 14px;
  border-top: 1px solid var(--border);
  background: var(--bg-app);
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  color: var(--accent);
}

/* Diff body: mono line grid, horizontal scroll stays inside the card. */
.mrdiff-diffbody {
  border-top: 1px solid var(--border);
  overflow-x: auto;
}

.mrdiff .diff {
  font-size: 13px;
  line-height: 1.75;
}

/* Old/new gutter columns: narrow, right-aligned, tertiary on the surface-2 wash. */
.mrdiff .diff-gutter {
  min-width: 42px;
  padding: 0 8px;
  text-align: right;
  font-size: 10.5px;
  color: var(--text-tertiary);
}

.mrdiff .diff-gutter-normal,
.mrdiff .diff-gutter-omit {
  background: var(--bg-surface-2);
  border-right: 1px solid var(--border);
}

.mrdiff .diff-code {
  padding: 0 14px;
  white-space: pre;
}

/* Side-by-side wraps; unified scrolls.
 *
 * In split view the two columns share the card's width, so a line of any real length runs under
 * its neighbour and then off the card — the right-hand column is unreadable exactly when the
 * left-hand one is interesting. `table-layout: fixed` is what makes the halves stay halves (without it
 * a single long line widens its own column and squeezes the other), and the gutters need explicit
 * widths under it because `min-width` no longer decides anything.
 *
 * Unified view keeps `white-space: pre` and the card's horizontal scroll: there is one code column
 * with the full width to itself, and wrapping there would break the alignment that makes a long
 * line's shape readable at all. */
.mrdiff .diff-split {
  table-layout: fixed;
  width: 100%;
}

.mrdiff .diff-split .diff-gutter {
  width: 42px;
  min-width: 42px;
}

.mrdiff .diff-split .diff-code {
  white-space: pre-wrap;
  /* `anywhere` rather than `break-word`: code has few break opportunities, and a 200-character
   * string literal with no space in it must still wrap rather than overflow. */
  overflow-wrap: anywhere;
}

.mrdiff .diff-code-normal {
  color: var(--text-secondary);
}

.mrdiff .diff-code-insert,
.mrdiff .diff-code-delete {
  color: var(--text-primary);
}

/* Hunk header rows (the @@ decoration). */
.mrdiff .diff-decoration-gutter,
.mrdiff .diff-decoration-content {
  background: var(--info-wash);
  color: var(--info);
  font-size: 11px;
}

.mrdiff .diff-decoration-content {
  padding: 3px 14px;
  font-family: var(--font-mono);
}

/* Hunk header row content: expand controls before the @@ text (DIF-10). */
.mrdiff-hunkhead {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.mrdiff-hunkhead-text {
  font-family: var(--font-mono);
}

.mrdiff-expand {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.mrdiff-expandbtn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 0 6px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--bg-surface);
  font-family: var(--font-sans);
  font-size: 11px;
  line-height: 18px;
  color: var(--text-secondary);
  cursor: pointer;
}

.mrdiff-expandbtn:hover {
  background: var(--bg-hover);
  color: var(--text-primary);
}

.mrdiff-expand-status {
  font-family: var(--font-sans);
  color: var(--text-tertiary);
}

.mrdiff-expand-error {
  font-family: var(--font-sans);
  color: var(--danger);
}

/* Word-level marks (markEdits, DIF-11). */
.mrdiff .diff-code-edit {
  border-radius: 2px;
}

/*
 * The screen is a fixed-height shell, not a tall page (DVR-13/14). Everything above the diff — the
 * MR header, the notices, the toolbar — is a non-shrinking row of this flex column, and the only
 * thing that scrolls is the tree and the diff column, each inside its own overflow. That is what
 * keeps the title, "Open in GitLab", the layout toggle and the viewed progress reachable at any
 * scroll depth without a single `top` offset computed from the height of anything above.
 *
 * The height comes from the shell: `DashboardLayout` gives `<main>` a definite height and wraps its
 * children in one unclassed centring div, so that div is told to fill it — scoped by `:has()` to
 * the diff screen, since the list screen wants a normally scrolling page. Where `:has()` is not
 * supported the percentage resolves to `auto` and the whole screen simply scrolls as a page, which
 * is the pre-DVR-14 behaviour rather than a broken one.
 */
main:has(> div > .mrdiff) > div {
  height: 100%;
  min-height: 0;
}

/*
 * The screen's own canvas padding and row rhythm, per the design (`padding: 16px 20px 18px`,
 * `gap: 12px`). It lives here rather than on the shell because `<main>` no longer supplies any:
 * every screen now pads itself, and this one would otherwise render edge to edge.
 *
 * The design draws this as `grid-template-rows: auto auto minmax(0, 1fr) auto`. It is built as a
 * flex column with `flex: 0 0 auto` rows and one `flex: 1 1 auto` body instead, which resolves
 * identically *and* survives the rows the design does not draw: the stale-head banner, the
 * truncation banner, the discussion notice and the Findings notice are all conditional, so a fixed
 * four-row template would put the diff in the wrong row the moment one of them appeared. The merge
 * bar is the trailing `auto` row either way — a non-shrinking last child below the scrolling diff
 * column (DWB-16).
 */
.mrdiff {
  box-sizing: border-box;
  height: 100%;
  min-height: 0;
  padding: 16px 20px 18px;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.mrdiff > * {
  flex: 0 0 auto;
}

/*
 * The MR header (DWB-07/10/11), per the design's one-row header: `minmax(0, 1fr) auto` with
 * `align-items: start` and a 16px gap. Built as a flex row rather than that grid for one reason —
 * `flex-wrap` — so a long title takes the width it needs and pushes the action group onto its own
 * line instead of squeezing it to nothing. `align-items: flex-start` keeps the group level with the
 * breadcrumb, which is where the design puts it: the first thing on the row, not centred against a
 * three-line column.
 */
.mrdiff-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
}

/* The design's left column: breadcrumb → title → the source→target line, 6px apart. */
.mrdiff-headermain {
  display: flex;
  flex: 1 1 320px;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}

.mrdiff-headertitle {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

/*
 * `min-width: 0` plus `overflow-wrap` is what makes a long title behave (DWB-07): the title keeps
 * its natural width while there is room, and a title too long for the row wraps its own text rather
 * than pushing the row wider than the screen. Because the h1's natural width is its full text, a
 * long title leaves no room beside it and the badge group — a single flex item — moves to its own
 * line underneath, whole. That is the trade this makes: the badges stay on the title line whenever
 * they fit, and when they do not they stay *together* one line down, rather than one badge being
 * orphaned across the break or the title being ellipsised to make room. A truncated title would be
 * the worse loss: the title is what the Reviewer navigated here by.
 */
.mrdiff-headertitle h1 {
  margin: 0;
  min-width: 0;
  font-size: 20px;
  font-weight: var(--fw-bold);
  color: var(--text-primary);
  overflow-wrap: break-word;
}

/*
 * DWB-07: the crucial-paths and per-Severity badges, as one flex item so they wrap as a group. They
 * wrap internally only when the group alone is wider than the row, which takes a narrow viewport
 * and all four badges at once.
 */
.mrdiff-headerbadges {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  min-width: 0;
}

.mrdiff-headerbadge {
  display: inline-flex;
}

.mrdiff-headermeta {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

/*
 * Every action this screen offers that is not the merge itself (DWB-07/10): the Analyse control with
 * its hint, then Open in GitLab, right-aligned and top-aligned with the breadcrumb. The design draws
 * the group `flex-wrap: nowrap`, which holds for its three short buttons; here the group also
 * carries the Analyse hint and, on a press that would spend, the confirmation question — so it wraps
 * rather than pushing its own buttons off the row.
 */
.mrdiff-headeractions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  align-self: flex-start;
  gap: var(--space-2);
  flex: 0 1 auto;
  flex-wrap: wrap;
}

/* The breadcrumb back to the list: mono, tertiary, the design's own row. */
.mrdiff-crumb {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--text-tertiary);
}

.mrdiff-crumb a {
  color: var(--text-tertiary);
}

.mrdiff-crumb-current {
  color: var(--text-secondary);
}

/*
 * Diff screen body: file tree beside the diff column (DVR-01/05), filling what the header and the
 * toolbar left. `min-height: 0` is load-bearing — without it a flex item refuses to shrink below
 * its content and the two columns would push the shell open again instead of scrolling inside it.
 * The grid lives here rather than in an inline style so the narrow-viewport rule below can win.
 */
.mrdiff > .mrdiff-layout {
  flex: 1 1 auto;
  min-height: 0;
}

.mrdiff-layout {
  display: grid;
  grid-template-columns: 272px minmax(0, 1fr);
  gap: var(--space-4);
  align-items: start;
}

/*
 * DVR-01: the tree stays on screen however far into the diff a Reviewer has scrolled — not by
 * sticking to a scrolling page, but by being a sibling of the diff column that scrolls inside
 * itself once the file list outgrows the room it has. `max-height` rather than `height` so a
 * three-file merge request gets a three-row card instead of an empty column; either way nothing
 * about it is measured against the height of the header above it.
 */
.mrdiff-tree {
  max-height: 100%;
  overflow-y: auto;
}

/*
 * The one scrolling column (DVR-14), and therefore the scroll container every card header sticks
 * within (DVR-13). It is the one grid item that must fill its area — a column only as tall as its
 * content has nothing to scroll. `scroll-to-file` follows it for free: `scrollIntoView` scrolls
 * whichever ancestor scrolls.
 */
.mrdiff-diffcol {
  align-self: stretch;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
}

/*
 * DVR-05: below the width where 272px + a diff column still reads, the two stack — and the fixed
 * shell is given up with them. One column of the viewport's width cannot hold a header, a tree and
 * a diff at once, so the page scrolls normally instead: the tree releases its own scrolling, the
 * diff column releases its overflow, and nothing is clipped. Card headers keep sticking, now
 * against the page's own scroll container.
 */
@media (max-width: 900px) {
  main:has(> div > .mrdiff) > div {
    height: auto;
  }

  .mrdiff {
    height: auto;
  }

  .mrdiff > .mrdiff-layout {
    flex: 0 0 auto;
  }

  .mrdiff-layout {
    grid-template-columns: minmax(0, 1fr);
  }

  .mrdiff-tree {
    position: static;
    max-height: none;
    overflow-y: visible;
  }

  /*
   * The card's header sticks to the card's own scroll, and here the card has none — leaving it
   * sticky would pin it to the *page* instead, so the overview's controls would follow the Reviewer
   * down through the diff. The whole tree scrolls away with the page on a narrow viewport (DVR-05);
   * its header goes with it.
   */
  .mrdiff-treehead {
    position: static;
  }

  .mrdiff-diffcol {
    overflow-y: visible;
  }
}

/* File tree rows (hover/selected need real CSS states, unlike the inline-styled DS ports). */
.mrdiff-treerow {
  display: flex;
  align-items: center;
  gap: 7px;
  width: 100%;
  padding: 4px 10px;
  border: none;
  border-radius: 5px;
  background: transparent;
  font-family: var(--font-sans);
  font-size: 12.5px;
  color: var(--text-secondary);
  text-align: left;
  cursor: pointer;
}

.mrdiff-treerow:hover {
  background: var(--bg-hover);
}

.mrdiff-treerow.on {
  background: var(--bg-active);
  color: var(--text-primary);
  font-weight: 600;
}

/* Viewed files dim their tree row; the check glyph replaces the change-kind dot (DIF-12). */
.mrdiff-treerow.viewed {
  color: var(--text-tertiary);
  opacity: 0.75;
}

/* Section headings of the file tree: uppercase tertiary labels (the design's `.treelabel`). */
.mrdiff-treelabel {
  padding: 8px 10px 4px;
  font-size: 10px;
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-tertiary);
}

/*
 * Stand-in line of the tree's "Hidden by manifest" section while the files stay hidden (RVM-02):
 * muted and non-interactive — it names the count, the toolbar switch reveals the rows.
 */
.mrdiff-treehidden {
  display: flex;
  align-items: center;
  gap: 7px;
  width: 100%;
  padding: 4px 10px;
  border-radius: 5px;
  font-family: var(--font-sans);
  font-size: 11.5px;
  color: var(--text-tertiary);
  text-align: left;
}

/*
 * The row between the header and the two columns (DWB-07/10): the layout tabs, the file/± summary,
 * which policy classified the diff, how much conversation is open, what the Analysis was — and,
 * pushed right by the spacer, how many files have been viewed.
 *
 * It is one row shorter than it was. "Show hidden files" and Refresh ended it until 2026-08-13, and
 * with everything else already on the line they were what tipped it into wrapping — a second
 * full-width row, spanning the tree *and* the diff column, for two controls a Reviewer touches once
 * a session. They now sit in the file overview's own header (`.mrdiff-treehead`), and this row is
 * back to one line: reading matter plus the one control that changes how the diff is drawn.
 */
.mrdiff-toolbar {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}

/* Toolbar policy indicator: which manifest classified this diff (RVM-06/12). */
.mrdiff-manifest {
  flex: 0 1 auto;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--text-tertiary);
}

/* A rejected manifest never passes silently: defaults applied, reason shown (RVM-05). */
.mrdiff-manifest.warn {
  color: var(--warning);
}

/* Toolbar viewed progress: "N of M files viewed" + a 120px ProgressBar (DIF-12). */
.mrdiff-progress {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  flex: 0 0 auto;
}

.mrdiff-progress-text {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--text-tertiary);
  white-space: nowrap;
}

/*
 * Per-file actions menu on a card header (DVR-06): a disclosure anchored to the header's right
 * edge. The host card switches its own `overflow` to visible while this is open, so a short card
 * cannot clip the list.
 */
.mrdiff-filemenu {
  position: relative;
  display: inline-flex;
  flex: 0 0 auto;
}

.mrdiff-filemenu-list {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 200px;
  padding: var(--space-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-surface);
  box-shadow: var(--shadow-md);
}

.mrdiff-filemenu-item {
  display: flex;
  align-items: center;
  width: 100%;
  padding: 6px 10px;
  border: none;
  border-radius: var(--radius-xs);
  background: transparent;
  font-family: var(--font-sans);
  font-size: 12.5px;
  color: var(--text-secondary);
  text-align: left;
  cursor: pointer;
}

/* The glyph and the label as one row, so labels line up whether or not an entry has an icon. */
.mrdiff-filemenu-item {
  gap: 8px;
}

.mrdiff-filemenu-label {
  flex: 1 1 auto;
}

/* A hairline between the rule edits and the action that spends money. */
.mrdiff-filemenu-separator {
  height: 1px;
  margin: 4px 6px;
  background: var(--border-subtle, var(--border));
}

.mrdiff-filemenu-item:hover:not(:disabled) {
  background: var(--bg-hover);
  color: var(--text-primary);
}

/* An entry waiting on a later task reads as unavailable rather than as broken (DVR-06). */
.mrdiff-filemenu-item:disabled {
  color: var(--text-tertiary);
  opacity: 0.6;
  cursor: not-allowed;
}

/*
 * What a manifest edit started from the file menu did (DVR-09/10/11): one line under the card's
 * header, toned by outcome — committed, already-present, refused — and dismissible. Deliberately
 * inside the card the action came from, so the answer is where the Reviewer is looking.
 */
.mrdiff-manifest-note {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding: var(--space-2) var(--space-4);
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-sm);
}

.mrdiff-manifest-note-text {
  flex: 1 1 auto;
  min-width: 0;
}

.mrdiff-manifest-note[data-tone='success'] {
  background: var(--success-wash);
  color: var(--success);
}

.mrdiff-manifest-note[data-tone='info'] {
  background: var(--info-wash);
  color: var(--info);
}

.mrdiff-manifest-note[data-tone='danger'] {
  background: var(--danger-wash);
  color: var(--danger);
}

/* Inline hint after a rejected setFileViewed — the card stays usable (DIF-12). */
.mrdiff-viewed-error {
  flex: 0 0 auto;
  font-family: var(--font-sans);
  font-size: 11px;
  color: var(--danger);
}

/*
 * Inline discussion threads (ICM-01/02/08/10/11). Everything below lives inside react-diff-view's
 * `.diff-widget-content` cell, so it opts back out of the diff table's mono/pre typography.
 */

/* One widget cell's content: every thread anchored to that line plus the open composer. */
.mrdiff-threads {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  background: var(--bg-surface-2);
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  font-family: var(--font-sans);
  white-space: normal;
}

.mrdiff-thread {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-surface);
}

/* A resolved thread reads as settled rather than shouting for attention (ICM-11). */
.mrdiff-thread.resolved {
  border-color: var(--success);
  opacity: 0.85;
}

.mrdiff-thread-resolved {
  font-size: 12px;
  font-weight: var(--fw-semibold);
  color: var(--success);
}

.mrdiff-notes {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.mrdiff-note {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.mrdiff-note-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  font-size: 12.5px;
}

.mrdiff-note-author {
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
}

.mrdiff-note-time {
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--text-tertiary);
}

/*
 * Note bodies are plain text on purpose (design: "Note bodies: plain text in v1") — `pre-wrap`
 * keeps the author's line breaks and indentation without any Markdown or HTML being parsed.
 */
.mrdiff-note-body {
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--text-secondary);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.mrdiff-note-more {
  display: flex;
}

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

.mrdiff-thread-error {
  font-size: 12px;
  color: var(--danger);
}

/* Thread-level control (Reply, Resolve, show more) — a bare button, like .mrdiff-expandbtn. */
.mrdiff-threadbtn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 0 8px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--bg-surface-2);
  font-family: var(--font-sans);
  font-size: 11px;
  line-height: 20px;
  color: var(--text-secondary);
  cursor: pointer;
}

.mrdiff-threadbtn:hover:not(:disabled) {
  background: var(--bg-hover);
  color: var(--text-primary);
}

.mrdiff-threadbtn:disabled {
  opacity: 0.6;
  cursor: default;
}

/* Composer (ICM-08/10/12): textarea plus actions, used for new comments and replies alike. */
.mrdiff-composer {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-surface);
}

/* MSG-02: the composer's place, holding the reason nothing may be written here instead of an input.
   Row rather than column, because it is one sentence and one way out — not a form. */
.mrdiff-composer--blocked {
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

.mrdiff-composer-blocked {
  margin: 0;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
}

.mrdiff-composer-input {
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--bg-inset);
  font-family: var(--font-sans);
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-primary);
  resize: vertical;
}

.mrdiff-composer-input:focus {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
}

.mrdiff-composer-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
}

/* A failed submit never loses the text — it says so, in place (ICM-12). */
.mrdiff-composer-error {
  font-size: 11px;
  color: var(--danger);
}

/* Hover affordance in the gutter: the line number gives way to a comment button (ICM-08). */
.mrdiff-gutterbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  padding: 0;
  border: none;
  border-radius: 3px;
  background: transparent;
  color: var(--accent);
  cursor: pointer;
}

.mrdiff-gutterbtn:hover {
  background: var(--bg-hover);
}

/* Per-file discussion count in a tree row: unresolved/total (ICM-15). */
.mrdiff-treecount {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--text-tertiary);
}

/* Open conversation is the thing to notice — resolved-only counts stay muted. */
.mrdiff-treecount.open {
  color: var(--accent);
}

/*
 * ARV-01: the auto-mark-viewed switch, immediately left of the count it moves.
 *
 * Toned down to match the row it joins — this row is mostly things a Reviewer *reads*, and a switch
 * shouting for attention beside them would misrepresent how often anybody touches it. The label's
 * size and colour come from the `style` prop at the call site, because the design system sets both
 * inline and inline wins over this file.
 */
.mrdiff-autoread {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
  font-size: var(--fs-xs);
  color: var(--text-tertiary);
}

/*
 * The track, smaller and quieter than the design system's smallest.
 *
 * Scaled rather than resized: its width, height and knob offsets are inline styles the component
 * computes, so a CSS `width` here would lose to them and only a forced override would win — and a
 * geometry fought over in two places is worse than one honest transform. `transform-origin` keeps it
 * from drifting away from its label as it shrinks.
 *
 * Full opacity, deliberately (requested 2026-08-21): the label carries the toning, and fading the
 * track as well made the one thing on this row that can be pressed look disabled.
 */
.mrdiff-autoread label > span:first-of-type {
  transform: scale(0.82);
  transform-origin: left center;
}

/* Toolbar: how many threads are still open across the merge request (ICM-16). */
.mrdiff-unresolved {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--text-tertiary);
  white-space: nowrap;
}

/*
 * Refresh, beside the layout tabs (2026-08-20). `position: relative` is load-bearing, not cosmetic:
 * the button's only accessible name is an absolutely-positioned `.mrdiff-sronly` span, and without a
 * positioned ancestor that span is laid out against the initial containing block — which stretched
 * the document past the viewport and dragged the fixed shell up the last time it happened here.
 */
.mrdiff-toolbar-refresh {
  display: inline-flex;
  flex: 0 0 auto;
  position: relative;
}

/*
 * The commit-count chip (CMT-01), beside the files/± summary it belongs with — same mono, same
 * tertiary tone, so the row still reads as one line of facts about the diff rather than as a
 * control bar. It is a button, so it earns a hover: the fact turns out to be a door, and nothing
 * else on this row is one.
 */
.mrdiff-commits-chip,
/* NMR-23: the reviewers chip is the commits chip's twin — same affordance, different fact — but it
   keeps its own class, because a selector named for commits is not a selector for reviewers, and the
   screen's tests read these classes to mean what they say. */
.mrdiff-reviewers-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  flex: 0 0 auto;
  padding: 2px 6px;
  border: none;
  border-radius: var(--radius-xs);
  background: transparent;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  /* Bold, and a step up in tone from the summary beside it: it is the one thing on this row that
     opens something, and the count is what a Reviewer looks for when sizing up a review. */
  font-weight: var(--fw-semibold);
  color: var(--text-secondary);
  white-space: nowrap;
  cursor: pointer;
}

.mrdiff-commits-chip:hover,
.mrdiff-reviewers-chip:hover {
  background: var(--bg-hover);
  color: var(--text-primary);
}

/* Conversation hiding behind a collapsed diff — one "Show diff" away, never silent (ICM-05). */
.mrdiff-stub-discussions {
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  color: var(--accent);
}

/*
 * Outdated / general discussion panels below the diff column (ICM-03/04/05): collapsed by default,
 * so secondary conversation is reachable without competing with the code.
 */
.mrdiff-panels {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.mrdiff-panel {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  overflow: hidden;
}

.mrdiff-panelhead {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  padding: 9px 14px;
  border: none;
  background: var(--bg-surface-2);
  font-family: var(--font-sans);
  font-size: 12.5px;
  font-weight: var(--fw-semibold);
  color: var(--text-secondary);
  text-align: left;
  cursor: pointer;
}

.mrdiff-panelhead:hover {
  background: var(--bg-hover);
  color: var(--text-primary);
}

.mrdiff-panelbody {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--border);
}

/* One line saying what these threads are — read before the threads themselves. */
.mrdiff-panelnote {
  font-size: 11.5px;
  color: var(--text-tertiary);
}

.mrdiff-panelentry {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* Where an outdated thread was originally anchored: that code is no longer on screen (ICM-03). */
.mrdiff-panelorigin {
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--text-tertiary);
}

/*
 * The two counts of conversation no panel can show (ICM-05). They are deliberately worded apart —
 * hidden by the manifest is a policy the Reviewer can switch off, not-loaded-yet is just paging.
 */
.mrdiff-discussion-count {
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-surface-2);
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

/* Discussions failed to load: a notice beside the toolbar, never the screen error state (ICM-07). */
.mrdiff-discussion-notice {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--warning-wash);
  color: var(--warning);
  font-size: var(--fs-sm);
}

/*
 * Findings (FND-01/02/03/04/06/08/13/14/15). A Finding is the tool's own advisory remark, so it
 * reads as a distinct object next to human conversation: a Severity-coloured bar down its left
 * edge, the same badge tone on its label, and the surface-2 wash of the widget behind it.
 */

/* Findings failed to load: their own notice, worded and labelled apart from the discussion one. */
.mrdiff-finding-notice {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--info-wash);
  color: var(--info);
  font-size: var(--fs-sm);
}

/* Toolbar provenance: when the analysis ran, against which revision, and what it covered. */
.mrdiff-analysis {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  flex: 0 1 auto;
  flex-wrap: wrap;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--text-tertiary);
}

/* A partial analysis is a caveat on every absent Finding — it is not muted like the rest. */
.mrdiff-analysis[data-analysis-coverage='partial'] .mrdiff-analysis-coverage {
  color: var(--warning);
}

/*
 * FND-14: the coverage statement is a button when there is a list behind it. It has to read as part
 * of the provenance line rather than as a control bolted onto it — so it inherits the line's type
 * and colour and carries only an underline to say it opens something, in the same spirit as the
 * breadcrumb link above it.
 */
/*
 * FND-15: findings produced against a revision that is no longer on screen. `--warning`, the same
 * tone partial coverage wears, because both say the same kind of thing — trust what follows a
 * little less.
 */
.mrdiff-analysis-stale {
  color: var(--warning);
}

.mrdiff-analysis-coverage--open {
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  background: none;
  border: 0;
  text-decoration: underline;
  text-decoration-style: dotted;
  text-underline-offset: 2px;
}

.mrdiff-analysis-coverage--open:hover {
  text-decoration-style: solid;
}

/* AOD-13: an analysis that had nothing recorded to check against is a caveat on every Finding it
   did and did not report, so the statement is legible rather than muted into the line. */
.mrdiff-analysis[data-analysis-decisions='0'] .mrdiff-analysis-decisions {
  color: var(--text-secondary);
}

/* FND-15: findings about an earlier revision must not read as findings about this one. */
.mrdiff-analysis-stale {
  color: var(--warning);
  font-weight: var(--fw-semibold);
}

/* AOD-01: the Analyse control and whatever the last press had to say, kept as one unit so the
   sentence can never drift away from the button it is about. */
.mrdiff-analyse {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  flex: 0 1 auto;
  flex-wrap: wrap;
  min-width: 0;
}

/* A refusal and a failure are ordinary text, not an error box (AOD-09): the diff, the discussions
   and any Findings already on screen are all untouched by either one. */
.mrdiff-analyse-note {
  font-size: var(--fs-xs);
  color: var(--text-tertiary);
  max-width: 46ch;
}

/* AOD-01: the confirmation takes the button's own place, so the question about spending money
   appears where the Reviewer is already looking rather than over the diff they are reading. */
.mrdiff-analyse-confirm {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

/* The question is a question, not a warning: nothing has gone wrong, and nothing has been spent. */
.mrdiff-analyse[data-analyse='confirming'] .mrdiff-analyse-note {
  color: var(--text-secondary);
}

/* A cache hit is the one outcome that must not read like a fresh run — it cost nothing, and the
   Reviewer has to be able to see that at a glance (AOD-06). */
.mrdiff-analyse[data-analyse='cached'] .mrdiff-analyse-note {
  color: var(--text-secondary);
}

/* Loud enough to notice, quiet enough not to look broken: being at the daily cap is the meter
   working, not a fault. */
.mrdiff-analyse[data-analyse='refused'] .mrdiff-analyse-note,
.mrdiff-analyse[data-analyse='failed'] .mrdiff-analyse-note {
  color: var(--warning);
}

/*
 * The card above the diff while an analysis runs (DWB-09, DWB-11), on the design's metrics: a file
 * card's surface, border and radius, `padding: 10px 14px`, a 12px gap and a wrapping row of glyph →
 * heading → detail → bar. It renders only while a run is in flight, and it is a row of the diff
 * column rather than anything the diff has to make room for.
 */
.mrdiff-analysing {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
}

.mrdiff-analysing-title {
  font-size: 12.5px;
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
}

/*
 * The one moving number this screen honestly has (DWB-09): seconds since the call was made. Mono and
 * non-shrinking, so the digits do not shuffle the row every time they tick.
 */
.mrdiff-analysing-elapsed {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-secondary);
  white-space: nowrap;
}

/*
 * The design's flexible mono slot holds the file being scanned. There is no such file here — one
 * callable reads the whole diff — so the slot says exactly that, and takes the same room.
 */
.mrdiff-analysing-note {
  flex: 1 1 160px;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-tertiary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/*
 * An indeterminate progress bar, on the design's 140px track. The vendored DS `ProgressBar` renders
 * a fraction of a known total and has no indeterminate mode; giving it one would mean keyframes, and
 * every DS component here is inline-styled with no stylesheet to put them in. So the track is drawn
 * here instead, with the DS bar's own metrics — `--bg-inset` track, `--accent` fill, pill radius,
 * 6px tall — and a sweeping fill that claims no percentage, because there is none to claim.
 */
.mrdiff-analysing-bar {
  display: block;
  flex: 0 0 140px;
  height: 6px;
  border-radius: var(--radius-pill);
  background: var(--bg-inset);
  overflow: hidden;
}

.mrdiff-analysing-bar::after {
  content: '';
  display: block;
  width: 33%;
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--accent);
  animation: mrdiff-analysing-sweep 1400ms var(--ease-standard) infinite;
}

@keyframes mrdiff-analysing-sweep {
  from {
    transform: translateX(-100%);
  }

  to {
    transform: translateX(300%);
  }
}

/* A Reviewer who has asked for less motion still gets the state, just without the sweep. */
@media (prefers-reduced-motion: reduce) {
  .mrdiff-analysing-bar::after {
    width: 100%;
    animation: none;
    opacity: 0.6;
  }
}

/* One Finding inside a widget row: severity bar, then the finding's own column. */
.mrdiff-finding {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-surface);
}

/* The severity bar — the at-a-glance difference from a human thread (FND-02). */
.mrdiff-finding-bar {
  flex: 0 0 auto;
  width: 3px;
  border-radius: 2px;
  background: var(--border-strong);
}

.mrdiff-finding[data-severity='conflict'] .mrdiff-finding-bar {
  background: var(--danger);
}

.mrdiff-finding[data-severity='issue'] .mrdiff-finding-bar {
  background: var(--warning);
}

.mrdiff-finding[data-severity='improvement'] .mrdiff-finding-bar {
  background: var(--info);
}

.mrdiff-finding-main {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
  flex: 1 1 auto;
}

.mrdiff-finding-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.mrdiff-finding-title {
  font-size: 12.5px;
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
}

/* Positioned ancestor for the sr-only "Copied"/"Copy failed" status span (`.mrdiff-sronly`,
   DiffFileTree.css) — the icon itself never grows the row. */
.mrdiff-finding-copy {
  position: relative;
  display: inline-flex;
}

/*
 * Finding bodies are plain text for exactly the reason note bodies are — `pre-wrap` keeps the
 * analysis's own line breaks without any Markdown or HTML being parsed.
 */
.mrdiff-finding-body {
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--text-secondary);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* A promoted Finding says so: its words are a real GitLab discussion now (FND-10). */
.mrdiff-finding-promoted {
  font-size: 11px;
  font-weight: var(--fw-semibold);
  color: var(--success);
}

.mrdiff-finding-error {
  font-size: 11px;
  color: var(--danger);
}

.mrdiff-finding-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

/* FND-08: a dismissed Finding collapses out of the way — one line, attributed, undoable. */
.mrdiff-finding.dismissed {
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  border-style: dashed;
  background: var(--bg-surface-2);
}

.mrdiff-finding-dismissal {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 11.5px;
  color: var(--text-tertiary);
}

/* Findings hiding behind a collapsed diff — one "Show diff" away, never silent (FND-04). */
.mrdiff-stub-findings {
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  color: var(--accent);
}

/*
 * The foot (DWB-16, RVS-06) — the verdict on the review, then the action on the branch, stacked in
 * that reading order. Stacking is half of what keeps Approve and Merge from being confusable: they
 * are never two buttons in one group, and each row carries only its own facts.
 */
.mrdiff-foot {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/*
 * The Approve row (RVS-06 … RVS-10). Same bordered surface as the merge bar below it, so the two
 * read as one foot; the button leads from the left while the merge bar's ends at the right, which
 * is the other half of keeping the two verbs apart.
 */
.mrdiff-approvebar {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
}

/* The design system's small uppercase eyebrow: this row is about the review, not about merging. */
.mrdiff-approve-eyebrow {
  flex: 0 0 auto;
  font-size: 10px;
  font-weight: var(--fw-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}

/* RVS-10: an approval that stands is a stated fact, not an inferred one. */
.mrdiff-approve-approved {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 12.5px;
  font-weight: var(--fw-semibold);
  color: var(--success);
}

.mrdiff-approve-spacer {
  flex: 1 1 auto;
}

/*
 * RVS-06: every outstanding item, named on screen rather than only in the button's tooltip. Sits
 * beside the button it explains — a Reviewer reading "Still outstanding: …" is reading why the
 * control next to it is off.
 */
.mrdiff-approve-blockers {
  flex: 1 1 240px;
  font-size: 12px;
  color: var(--text-secondary);
}

/*
 * The line about the press itself, or about approvals not being available here (RVS-09). Ordinary
 * text rather than an error box, as everywhere else on this screen: a GitLab without the paid-tier
 * approvals API has not broken anything.
 */
.mrdiff-approve-note {
  flex: 1 1 100%;
  font-size: 12px;
  color: var(--text-tertiary);
}

/*
 * The merge bar (DWB-16) — the foot's trailing row, below the scrolling diff column, styled as
 * the design's `.filecard`: a bordered surface rather than a bare strip, so the one irreversible
 * control on the page reads as its own thing and not as more toolbar.
 */
.mrdiff-mergebar {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
}

/* The design's `StatusDot`, which the vendored DS does not ship: a dot and its label. */
.mrdiff-merge-pipeline {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  flex: 0 0 auto;
  font-size: 12.5px;
  color: var(--text-secondary);
  white-space: nowrap;
}

/*
 * PIP-01: the same fact, now a door when GitLab gave us somewhere to go. It keeps the tone of the
 * statement it replaces rather than taking the accent colour — the row is a line of facts about the
 * merge, and one of them turning blue would read as the important one. The underline appears on
 * hover, which is where a link on a row of facts should announce itself.
 */
a.mrdiff-merge-pipeline {
  text-decoration: none;
  cursor: pointer;
}

a.mrdiff-merge-pipeline:hover {
  color: var(--text-primary);
  text-decoration: underline;
}

/*
 * PIP-02: re-read the status, beside the pipeline that has not settled yet.
 *
 * Sized to the text it sits in rather than to the design system's `IconButton` (28px at its
 * smallest, which would have been twice the height of this row's tallest word). `position:
 * relative` is load-bearing for the same reason it is on `.mrdiff-toolbar-refresh`: the accessible
 * name is an absolutely-positioned `.mrdiff-sronly` span, and without a positioned ancestor it is
 * laid out against the initial containing block and stretches the document.
 */
.mrdiff-merge-recheck {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  position: relative;
  width: 18px;
  height: 18px;
  margin-left: -3px;
  padding: 0;
  border: none;
  border-radius: var(--radius-xs);
  background: transparent;
  cursor: pointer;
}

.mrdiff-merge-recheck:hover:not(:disabled) {
  background: var(--bg-hover);
}

/* Turning while it reads, and not pressable twice: the answer is one request away, and a second
   press would only race the first. */
.mrdiff-merge-recheck:disabled {
  cursor: default;
}

.mrdiff-merge-recheck[data-refreshing='true'] svg {
  animation: mrdiff-merge-recheck-spin 900ms linear infinite;
}

@keyframes mrdiff-merge-recheck-spin {
  to {
    transform: rotate(360deg);
  }
}

.mrdiff-merge-dot {
  width: 8px;
  height: 8px;
  border-radius: var(--radius-pill);
  flex: 0 0 auto;
}

.mrdiff-merge-fact {
  flex: 0 0 auto;
  font-size: 12.5px;
  color: var(--text-secondary);
  white-space: nowrap;
}

/*
 * DWB-16: outstanding Conflicts get the warning wash and nothing more. It is a *statement*, never a
 * gate — the button beside it stays exactly as enabled as GitLab says it should be.
 */
.mrdiff-merge-advisory {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 220px;
  min-width: 200px;
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  background: var(--warning-wash);
  font-size: 12px;
  color: var(--text-secondary);
}

.mrdiff-merge-spacer {
  flex: 1 1 auto;
}

/*
 * NMR-24: who the merge request is assigned to, immediately left of how it will be merged.
 *
 * Toned as one of this row's facts rather than as a control — mono for the username, one step
 * quieter than the merge method, and no hover, because there is nothing here to press. It sits after
 * the spacer, so it stays beside the merge controls rather than drifting left among the pipeline and
 * approval statements.
 */
.mrdiff-assignees {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--text-tertiary);
  white-space: nowrap;
}

.mrdiff-merge-method {
  flex: 0 0 160px;
}

/* The inline two-step confirmation's own styles are gone with it (DWB-18, revised 2026-08-21): the
   question is a `ConfirmDialog` now, which brings its own shell. */

/*
 * What that confirmation warns about (DWB-18): the review's outstanding items, in the warning wash
 * the bar uses for the same class of fact.
 *
 * A wash rather than a red border, and no effect on the confirm button: this advises. It is the last
 * place anybody sees "you have three unsent comments" before merging makes them moot, and that is
 * worth colour — but the decision stays the Reviewer's (DWB-16).
 */
.mrdiff-merge-warning {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin: 0;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  background: var(--warning-wash);
  font-size: 12.5px;
  color: var(--text-secondary);
}

/*
 * The two branch names in that confirmation's heading (DWB-18).
 *
 * Same chip the summary dialog gives inline code, and for the same reason: a ref is an identifier,
 * not a word, and on a grey ground the pair reads as the two things being merged rather than as a
 * sentence to be parsed. `0.92em` because it sits inside an `h2` and should not set its line height.
 */
.mrdiff-merge-ref {
  padding: 1px 6px;
  border-radius: var(--radius-xs);
  background: var(--bg-surface-2);
  font-family: var(--font-mono);
  font-size: 0.92em;
  font-weight: var(--fw-regular, 400);
}

/* DWB-20: what a merged merge request shows instead of controls. */
.mrdiff-merge-done {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 12.5px;
  font-weight: var(--fw-semibold);
  color: var(--success);
}

.mrdiff-merge-sha {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--text-tertiary);
}

/*
 * The reason line: why the button is off (DWB-18), or what a refusal said (DWB-19). Full-width so
 * it never competes with the facts above it, and ordinary text rather than an error box — GitLab
 * declining to merge did not break anything the Reviewer was doing.
 */
.mrdiff-merge-note {
  flex: 1 1 100%;
  font-size: 12px;
  color: var(--text-tertiary);
}

/*
 * Tree navigation (DWB-02/03).
 *
 * The row a Reviewer navigated to wears `.mrdiff-navtarget` for as long as the screen holds it as
 * the navigation target — a couple of seconds. The class is generated by the render (react-diff-
 * view's `generateLineClassName`), so it survives every re-render and leaves nothing behind when it
 * stops being generated: nothing here changes the diff's ordinary appearance.
 *
 * The wash beats the insert/delete backgrounds react-diff-view paints on the same cells by
 * specificity alone — no importance override — which is why both selectors name the row class *and*
 * the cell class. The left rule is what a Reviewer actually finds with their eye after the scroll —
 * a wash alone is easy to miss on a diff that is already coloured line by line.
 */
.mrdiff-navtarget td.diff-gutter,
.mrdiff-navtarget td.diff-code {
  background: var(--accent-wash);
}

.mrdiff-navtarget td.diff-gutter:first-child {
  box-shadow: inset 2px 0 0 0 var(--accent);
}

/*
 * The line a comment is being written about, for as long as its composer is open (2026-08-18).
 *
 * Same construction as `.mrdiff-navtarget` above and for the same reasons — generated by the render,
 * naming both the row and the cell so it beats the insert/delete backgrounds on specificity alone.
 * A different token from the navigation mark deliberately: one says "this is where you landed" and
 * lasts a couple of seconds, the other says "this is what you are writing about" and lasts as long
 * as the box is open, and a Reviewer who has both on screen must be able to tell them apart.
 */
.mrdiff-commenting td.diff-gutter,
.mrdiff-commenting td.diff-code {
  background: var(--info-wash);
}

.mrdiff-commenting td.diff-gutter:first-child {
  box-shadow: inset 2px 0 0 0 var(--info);
}

/*
 * A line that **carries** a comment — an unresolved thread, or an unsent draft (2026-08-18).
 *
 * Quieter than the two marks above on purpose: those are about a moment (where you landed, what you
 * are writing), this one is a standing property of the line and may be true of a dozen lines in a
 * file at once. A wash on all of them would tint the diff; an edge on the gutter marks them without
 * competing with the insert/delete colours the code cells carry.
 */
.mrdiff-commented td.diff-gutter:first-child {
  box-shadow: inset 2px 0 0 0 var(--info);
}

.mrdiff-commented td.diff-gutter {
  background: var(--info-wash);
}

/*
 * Every line of a multiline span (MLC-02, MLC-11) — the composer's in-progress range while it is
 * being extended, and the standing range of a placed thread or unsent draft. Quieter than the
 * anchor's own mark (`.mrdiff-commenting` / `.mrdiff-commented`, which the span's end line wears on
 * top of this): a wash on the code cells says "this comment claims these lines" without repainting
 * the insert/delete colours, and the gutter edge keeps the span findable when the wash is subtle.
 */
.mrdiff-ranged td.diff-gutter,
.mrdiff-ranged td.diff-code {
  background: var(--info-wash);
}

.mrdiff-ranged td.diff-gutter:first-child {
  box-shadow: inset 2px 0 0 0 var(--info);
}

/*
 * Whole-file conversation, atop the card (MLC-08): file-level threads and unsent whole-file drafts
 * share the diff widgets' own container styling, so a thread reads the same whether it hangs under
 * a line or under the header. Bordered below to hand over to the diff body.
 */
.mrdiff-filethreads {
  display: flex;
  flex-direction: column;
  gap: 8px;
  /* Docked right and to the same width as the composer above (2026-08-20), so a whole-file comment
     stays in the column it was written in — the composer sliding left as it turned into a comment
     was a jump with nothing behind it. */
  width: min(640px, 100%);
  padding: 8px 12px;
  margin-left: auto;
  border-bottom: 1px solid var(--border);
  background: var(--bg-surface);
}

/*
 * The whole-file composer floats instead of injecting (MLC-06, UAT amendment 2026-08-20): its
 * affordance is in the card header, which is pinned at every scroll depth of a long file (DVR-13),
 * so the box it opens must be on screen at every depth too — a block at the top of the card was
 * invisible from anywhere past the first screenful. Sticky just below the pinned header, and the
 * wrapper is zero-height so opening it never shifts a single diff row; the panel draws above them
 * (z-index above the header's 2). The shadow is deliberate, against the flat-surface house rule:
 * an overlay that covers code needs to read as *over* it, exactly as the dialogs do.
 */
.mrdiff-filecomposer-overlay {
  position: sticky;
  /*
   * The height of `.mrdiff-cardhead` — 9px padding, a 28px `sm` IconButton, 9px padding — so the
   * panel pins directly beneath the pinned header. It docks flush against it (no top margin, square
   * top corners), which makes this number load-bearing: change the header's padding or the size of
   * its controls and this has to move with it, or the dock opens a gap.
   */
  top: 46px;
  z-index: 3;
  display: flex;
  /* Docked right (2026-08-20), under the header control that opens it: a box that appeared at the
     opposite end of the card from the button pressed made the Reviewer look twice to find it. */
  justify-content: flex-end;
  height: 0;
  overflow: visible;
}

/*
 * Layout only — **no surface of its own** (fixed 2026-08-20). `.mrdiff-composer` inside it is
 * already a bordered, rounded, padded card, so giving this wrapper the same treatment drew a second
 * one a few pixels larger: a white lip around the real box that read as a rendering artifact,
 * because that is what it was.
 */
.mrdiff-filecomposer {
  width: min(640px, 100%);
  /* Flush to the header above it, on the same 14px side gutter the header itself uses — so the
     panel hangs off the header it was opened from rather than floating near it, and its right edge
     lines up under the header controls. */
  margin: 0 14px;
}

/*
 * The elevation belongs to the one real card, which is what has to read as floating over the code.
 * Square where it meets the header and rounded where it leaves the diff: the panel is docked, and a
 * rounded top edge against a straight one draws two lines nobody meant to draw.
 */
.mrdiff-filecomposer > .mrdiff-composer {
  border-top-left-radius: 0;
  border-top-right-radius: 0;
  border-color: var(--border-strong);
  box-shadow: 0 12px 32px rgb(0 0 0 / 35%);
}

/*
 * The mark arrives with a short pulse so the eye is drawn to it when the scroll settles, and holds
 * the wash afterwards — it is removed by the screen, never by the animation, so a Reviewer whose
 * system asks for less motion still sees exactly the same row marked for exactly as long.
 */
@media (prefers-reduced-motion: no-preference) {
  .mrdiff-navtarget td.diff-gutter,
  .mrdiff-navtarget td.diff-code {
    animation: mrdiff-navtarget-pulse 900ms ease-out;
  }
}

@keyframes mrdiff-navtarget-pulse {
  0% {
    background: var(--accent);
  }

  100% {
    background: var(--accent-wash);
  }
}

/*
 * What navigation had to change to reach an entry, or why it could not reach one (DWB-02/03). Its
 * own row above the layout, in the same shape as the discussion and Findings notices: a sentence,
 * never an error box — nothing here failed, and a switch that was turned on for the Reviewer is
 * information rather than a fault.
 */
.mrdiff-navnote {
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg-inset);
  color: var(--text-secondary);
  font-size: var(--fs-sm);
}

/*
 * The card takes focus when navigation lands on it (DWB-02) and must not draw a ring around a whole
 * file for a pointer click. `:focus-visible` still applies the browser's own ring when the Reviewer
 * got here from the keyboard, which is the case where it is the only thing telling them where they
 * are.
 */
.mrdiff-card:focus {
  outline: none;
}

.mrdiff-card:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/*
 * =========================================================================================
 * The unsent review (RVS-01 … RVS-05). A draft is the Reviewer's own comment, held: it reads as
 * a sibling of a thread rather than as a different kind of object, but nothing about it is allowed
 * to read as *sent*. The warning-tinted left edge and the `Unsent` badge are the two things that
 * carry that at a glance, and neither is a hover affordance.
 * =========================================================================================
 */

/* One unsent comment, wherever it renders: inline on its line, inside its thread, or in a panel. */
.mrdiff-draft {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: var(--space-3);
  border: 1px solid var(--warning);
  border-left-width: 3px;
  border-radius: var(--radius-sm);
  background: var(--warning-wash);
}

/*
 * Room for the two controls pinned to the top-right corner below, so the badge and the visibility
 * sentence wrap before they reach them rather than running underneath.
 */
.mrdiff-draft-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding-right: 64px;
  flex-wrap: wrap;
}

/* Says what "unsent" actually costs the author, in the tone of a fact rather than a warning. */
.mrdiff-draft-visibility {
  font-size: 12px;
  color: var(--text-secondary);
}

/* The Reviewer's own words, plain text — never parsed, exactly like a note body. */
.mrdiff-draft-body {
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--text-primary);
  white-space: pre-wrap;
  word-break: break-word;
}

/*
 * Edit and Delete, icon-only and pinned to the draft's top-right corner (2026-08-20).
 *
 * Out of the flow rather than merely right-aligned in it: as a row of their own they cost every
 * draft a line of vertical space, and a line inside a diff pushes code off screen — on a line
 * carrying several drafts that added up. Absolute against the card, level with the badge, so the
 * card is exactly as tall as the words in it. The head above reserves the width they occupy.
 *
 * `space-1` rather than `space-3`: two square buttons side by side are one group, and the wider gap
 * read as two unrelated controls.
 */
.mrdiff-draft-actions {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.mrdiff-draft-error {
  font-size: 12px;
  color: var(--danger);
}

/* Unsent comments hiding behind a collapsed diff — the one thing on screen nobody else can see. */
.mrdiff-stub-drafts {
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  color: var(--warning);
}

/* The drafts list failed to load: its own notice, worded apart from the discussion one (RVS-02). */
.mrdiff-draft-notice {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--warning-wash);
  color: var(--warning);
  font-size: var(--fs-sm);
}

/*
 * Send Review, in the header action group beside Analyse (RVS-03). Same shape as `.mrdiff-analyse`:
 * the sentence leads and the button follows, so the group ends on its buttons.
 */
.mrdiff-sendreview {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  justify-content: flex-end;
}

.mrdiff-sendreview-note {
  font-size: var(--fs-sm);
  color: var(--text-secondary);
  text-align: right;
}

.mrdiff-sendreview-unshown {
  font-size: var(--fs-xs);
  color: var(--text-tertiary);
  text-align: right;
}

/* A promoted Finding is in the review, not with the author, until it is sent (FND-09, RVS-01). */
.mrdiff-finding-drafted {
  font-size: 11px;
  font-weight: var(--fw-semibold);
  color: var(--warning);
}

/*
 * DVR-16: the branch chips copy their name. They keep the visual reference's appearance exactly —
 * this is a button because it acts, not because it should look like one.
 *
 * `position: relative` and the absolutely positioned confirmation are what keep the header from
 * reflowing: the chip holds its width while the word inside it changes, on a row that already wraps
 * under a long merge request title.
 */
.mrdiff-branchchip {
  position: relative;
  padding: 1px 6px;
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: inherit;
  cursor: pointer;
  background: var(--bg-inset);
  border: 0;
  border-radius: 4px;
}

.mrdiff-branchchip:hover {
  background: var(--bg-inset-strong, var(--bg-inset));
  outline: 1px solid var(--border);
}

.mrdiff-branchchip-said {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--success);
  background: var(--bg-inset);
  border-radius: 4px;
}

/*
 * The "an analysis exists" marker beside the Analyse control (2026-08-15). Deliberately not a
 * button and not accented: it reports a state rather than offering an action, and the control it
 * sits next to is the thing meant to be pressed.
 */
.mrdiff-analysis-report {
  align-self: center;
  padding: 3px 10px;
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  cursor: pointer;
  background: var(--bg-inset);
  border: 1px solid transparent;
  border-radius: 999px;
}

/*
 * The tone carries the one thing worth knowing without opening the report (2026-08-15): grey when
 * the findings stand as they are, amber when something qualifies them — an earlier revision, or
 * files the analysis never read — and red when the Reviewer's last press failed, so the report on
 * offer is older than what they just asked for.
 */
.mrdiff-analysis-report[data-tone='normal'] {
  color: var(--text-tertiary);
}

.mrdiff-analysis-report[data-tone='warning'] {
  color: var(--warning);
  border-color: var(--warning);
}

.mrdiff-analysis-report[data-tone='error'] {
  color: var(--danger);
  border-color: var(--danger);
}

.mrdiff-analysis-report:hover {
  border-color: var(--border-strong, var(--border));
}

/*
 * The hint field on the Analyse confirmation (ARN-08). Deliberately quiet: it is optional, and a
 * field that shouted would make a neutral run feel like an omission.
 */
.mrdiff-analyse__hint {
  display: flex;
  flex-direction: column;
  gap: var(--space-1, 4px);
}

.mrdiff-analyse__hintlabel {
  font-size: var(--fs-xs, 0.75rem);
  color: var(--text-tertiary);
}

/*
 * `resize: vertical` only — a horizontal drag would push it past the modal it sits in.
 */
.mrdiff-analyse__hintinput {
  width: 100%;
  padding: var(--space-2);
  font-family: inherit;
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: var(--text-primary);
  background: var(--surface-raised, transparent);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm, 6px);
  resize: vertical;
}

.mrdiff-analyse__hintinput:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
/*
 * MR list screen styles. The screen predates this file and still carries inline styles in the
 * author cell and the centred card messages; every cell touched since — the merge request title
 * and its tags, the Team text, Waiting Time and its three bands (MRL-18/19/22) — is styled here.
 */

/*
 * The screen's own frame. The shell's content canvas is full-bleed (there is no top bar to sit
 * under any more), so every screen carries the page padding it used to inherit from the canvas.
 */
.mrlist-screen {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  padding: var(--space-6) var(--space-8);
}

/*
 * The filter row and anything that has to sit directly under it — today the fail-open notice of
 * TMF-05. A column, so the notice stays attached to the control it explains instead of drifting to
 * the bottom of the page with the rest of the meta line.
 */
/*
 * The toolbar is one row, as the design's list screens draw it (Stale MRs, Branch Health): the
 * filters at the left, then how fresh the data is and the control that refreshes it at the right.
 * The freshness used to sit under the table and the refresh up in the page header — the fact and
 * its remedy a screen apart.
 *
 * It wraps rather than scrolls, and the degraded-Team notice claims a whole line of its own, so a
 * narrow viewport stacks the parts instead of squeezing the filters.
 */
.mrlist-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

.mrlist-toolbar-spacer {
  flex: 1 1 auto;
}

.mrlist-toolbar-freshness {
  font-size: var(--fs-xs);
  color: var(--text-tertiary);
  white-space: nowrap;
}

.mrlist-team-notice {
  flex-basis: 100%;
}

/*
 * The control that holds the hidden name is positioned, so the name is laid out against it rather
 * than against the page — see the note in `DiffFileTree.css`, where an unpositioned parent let a
 * 1px hidden span stretch the document past the viewport.
 */
.mrlist-toolbar button {
  position: relative;
}

/*
 * The accessible name of an icon-only control — visible to a screen reader, never on screen. Same
 * treatment as the diff's `.mrdiff-sronly`.
 */
.mrlist-sronly {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/*
 * TMF-05: "your own Team could not be determined". Tertiary, one line, no icon and no colour — it
 * reports a degraded filter, not a failure: every merge request is on screen and everything works.
 */
.mrlist-team-notice {
  margin: 0;
  color: var(--text-tertiary);
  font-size: var(--fs-xs);
}

/*
 * The filter row itself: the three server-side filters and the two browser-side toggles, wrapping
 * on a narrow window rather than overflowing.
 */
.mrlist-filter-bar {
  display: flex;
  flex: 1 1 auto;
  flex-wrap: wrap;
  align-items: center;
  min-width: 0;
  gap: var(--space-3);
}

/*
 * "Show drafts" (MRL-20) — smaller and quieter than the two controls beside it.
 *
 * Those are how a Reviewer narrows the list and get the weight to match; this is a rarely-touched
 * exception to it, and at full size it read as the third equal control on the row. The label's size
 * and colour come from the `style` prop at the call site, because the design system sets both inline
 * and inline wins over this file. Scaled rather than resized for the same reason the diff toolbar's
 * switch is: the track's width, height and knob offsets are inline styles the component computes.
 */
.mrlist-showdrafts {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
}

.mrlist-showdrafts label > span:first-of-type {
  transform: scale(0.82);
  transform-origin: left center;
}

/*
 * "20 hidden · 17 other teams, 6 drafts" (TMF-02, MRL-22). Secondary rather than tertiary, so it
 * separates from the count it qualifies — a Reviewer seeing 4 of 24 rows must be able to tell that
 * the other 20 were filtered rather than missing.
 */
.mrlist-hidden {
  color: var(--text-secondary);
}

/*
 * The merge request cell: title line above, project!iid · target branch beneath.
 */
.mrlist-title-cell {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

/*
 * The title and the tags that qualify it — Draft (MRL-20) and Crucial (MRL-22). Both are tags
 * rather than columns because both are true of a minority of rows: a column would have spent
 * list-wide width on mostly empty cells.
 */
.mrlist-title-line {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}

/*
 * The title text itself needs to be allowed to shrink and truncate, not just its flex parents —
 * a flex item's default min-width is auto (its content size), so without `min-width: 0` here
 * ellipsis never actually engages, table-layout: fixed or not.
 */
.mrlist-title {
  overflow: hidden;
  /* `0 1 auto`, not `1 1 auto`: the title must be able to *shrink* and truncate, but it must not
     **grow** — growing pushed the Draft and Crucial tags to the far right of the cell, so on a short
     title they floated away from the name they qualify (reported 2026-08-21). Shrink yes, grow no. */
  flex: 0 1 auto;
  min-width: 0;
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
  text-overflow: ellipsis;
  white-space: nowrap;
}

/*
 * MRL-24: the subtitle line — where the row identifies itself — now carries the merge request's
 * size too. A flex row so the path can shrink and the size cannot: the path is the part that can
 * afford an ellipsis, the numbers are not.
 */
.mrlist-subline {
  display: flex;
  gap: var(--space-2);
  align-items: baseline;
  min-width: 0;
}

.mrlist-subtitle {
  flex: 0 1 auto;
  max-width: 420px;
  min-width: 0;
  overflow: hidden;
  color: var(--text-tertiary);
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  white-space: nowrap;
  text-overflow: ellipsis;
}

/*
 * The size never shrinks and never wraps — it is four short tokens, and a half-shown line count is
 * worse than none. Tabular figures so the counts line up down the column, which is the whole point
 * of being able to see them at a glance.
 */
/*
 * MRL-24: the file count, in its own column. Tabular figures and right alignment are the whole
 * point — this is the one number in the row that is read *down* the page rather than across it.
 */
.mrlist-files {
  color: var(--text-secondary);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.mrlist-size {
  flex: 0 0 auto;
  color: var(--text-tertiary);
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/*
 * The author's name (`authorName` column, 160px). Every group renders its own separate `<table>`
 * (`table-layout: auto`, `DataTable`'s default — it cannot be changed here, ADR 0008/0009), so an
 * explicit `column.width` on the `<th>` is only ever a hint: a long real name in a `<tbody>` cell
 * grows that table's column past it, while the shared sticky header's own table (built from an
 * empty row set) has no content to fight against and stays exactly at the hint. Truncating the
 * name here, rather than trusting the column width alone, is what keeps every group's Author
 * column the same rendered width as the header above it.
 */
.mrlist-author-name {
  overflow: hidden;
  flex: 1 1 auto;
  min-width: 0;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/*
 * The author's Team (TMF-01), as plain text (MRL-22). Muted, because it is on every row and
 * therefore distinguishes nothing by being loud; no badge and no tone, so the row's only colour is
 * Waiting Time. Truncated for the same reason `.mrlist-author-name` is: the `authorTeam` column's
 * 130px width is only a hint under `table-layout: auto`, and a long Team name would otherwise grow
 * one group's table out of line with the shared sticky header above it.
 */
.mrlist-team {
  display: inline-block;
  overflow: hidden;
  max-width: 100px;
  color: var(--text-secondary);
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}

/*
 * "Unknown team" (TMF-01): dimmer than a Team the directory knows, so a data gap reads as a gap
 * rather than as a team called Unknown — and still text, so nothing here re-introduces a colour.
 */
.mrlist-team--unknown {
  color: var(--text-tertiary);
}

/*
 * Last activity. Tertiary: it is context for the Waiting Time beside it, never a competitor to it.
 */
.mrlist-updated {
  color: var(--text-tertiary);
  font-size: var(--fs-xs);
}

/*
 * Waiting Time (MRL-18/19) — the one number in this list a Reviewer is meant to react to, and the
 * only coloured one. Tabular figures so the ages line up down the column, which is the whole
 * reason it is a column.
 *
 * Its tooltip (MRL-25, the exact opened/updated dates) used to be the native `title` attribute —
 * whose hover delay is the browser's own, fixed, and nothing here can shorten it. `::after` below
 * is a CSS tooltip instead, reading `data-tooltip` (set in the component) and appearing on `:hover`
 * with no transition at all — instant, the actual point of replacing `title`. `position: relative`
 * is what anchors the absolutely-positioned tooltip to this cell rather than the table.
 */
/*
 * The positioned ancestor the cell's sr-only span (MRL-25, `#mrlist-waiting-tooltip-*`) needs —
 * see the comment at its render site in MergeRequestListScreen.tsx. Without it the absolutely
 * positioned span lays out against the page instead of this cell, and the group's `overflow: auto`
 * cannot clip it.
 */
.mrlist-waiting-cell {
  position: relative;
}

.mrlist-waiting {
  position: relative;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-variant-numeric: tabular-nums;
  font-weight: var(--fw-semibold);
}

.mrlist-waiting::after {
  content: attr(data-tooltip);
  position: absolute;
  right: 0;
  bottom: calc(100% + var(--space-2));
  z-index: 3;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-elevated);
  box-shadow: var(--shadow-md);
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-weight: var(--fw-regular);
  font-variant-numeric: normal;
  /* `pre`, not `pre-line`: both preserve the `\n` in `data-tooltip` as a line break, but `pre-line`
     still lets a single line wrap if the box isn't wide enough — verified live, it wrapped one
     line across four. `pre` additionally forbids wrapping within a line, which is what actually
     forces the tooltip's shrink-to-fit width to the longer of the two dates instead of guessing. */
  white-space: pre;
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
}

.mrlist-waiting:hover::after {
  visibility: visible;
  opacity: 1;
}

/*
 * The three bands (MRL-19): 0–2 days, 3–7, 8+. Green is deliberately the quietest of the three —
 * "nothing to do here" should not draw the eye — while red is the tool's danger colour, unshared
 * with anything else in this row.
 */
.mrlist-waiting--green {
  color: var(--success);
}

.mrlist-waiting--amber {
  color: var(--warning);
}

.mrlist-waiting--red {
  color: var(--danger);
}

/*
 * The line under the card: row count, when the data was fetched, and whether a refresh is in
 * flight or failed (MRL-17). One flex row so the freshness note sits beside the count instead of
 * pushing the card around when it appears — a background refresh must not move the layout.
 */
.mrlist-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  color: var(--text-tertiary);
  font-size: var(--fs-xs);
}

/*
 * "Refreshing…" while a background revalidation runs (MRL-17): deliberately the same tertiary
 * weight as the rest of the line — the Reviewer should be able to notice it, not be pulled to it.
 */
.mrlist-refreshing {
  color: var(--text-secondary);
}

/*
 * A refresh that failed over data that is still good (MRL-17). Coloured, because "what you are
 * reading is older than it should be" is the one thing here the Reviewer must not miss — but
 * still only a line of text: the rows stay exactly where they are.
 */
.mrlist-refresh-failed {
  color: var(--danger);
}

/*
 * One group of merge requests, sharing the top-level GitLab group segment right after the
 * namespace (e.g. "games" in "softgames/games/bubble-shooter-saga"). Home Groups mean more of the
 * namespace surfaces on this list now, and grouping is the browser's way of keeping an overview —
 * `DataTable` has no native grouping concept (ADR 0008/0009), so each group renders its own
 * `DataTable` instance rather than teaching the vendored component something new.
 *
 * Each group is its own `Card` (bordered, rounded, `--bg-surface` white) — not a tinted region of
 * one shared card — so the page's own grey canvas (`--bg-app`) shows through the gaps *between*
 * groups. `.mrlist-group` itself carries no visual styling of its own: `Card` supplies the border,
 * radius and background, and `.mrlist-groups` below supplies the spacing between groups.
 */
.mrlist-groups {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

/*
 * The one column-header bar shared by every group (see MergeRequestListScreen.tsx): a `DataTable`
 * rendered with no rows, so its `<thead>` is the only header a Reviewer sees, kept forever in sync
 * with `COLUMNS` at zero cost. It lives outside every `Card` and outside `.mrlist-groups`, as a
 * plain sibling in normal flow directly under the toolbar — so it sticks relative to `<main>`
 * (the shell's own scroll container, see DashboardLayout.tsx) rather than relative to a `Card`'s
 * own frame. A `Card` sets `overflow: hidden` unconditionally, and `position: sticky` sticks to
 * the nearest ancestor whose overflow is anything but `visible` even when that ancestor never
 * itself scrolls — which is why a header living inside a `Card` (or inside each group individually)
 * would behave exactly like `position: static` and never actually stick.
 */
.mrlist-sticky-header {
  position: sticky;
  top: 0;
  z-index: 2;
}

/*
 * The group's own header: its name, how many merge requests it holds, and a rule that fills the
 * rest of the row — the same shape as the design's `.grouphead`/`.groupname`/`.groupcount`/
 * `.grouprule`, renamed into this file's `.mrlist-` convention. No namespace prefix: the browser
 * has no verified source of truth for the literal namespace string (`GITLAB_NAMESPACE` is a
 * server-only Firebase Functions param), so showing one anyway would be exactly the kind of
 * unverified assumption this codebase's culture avoids.
 *
 * Carries its own horizontal and top inset now that the group's frame is `Card` (padding 0): the
 * table below stays flush with the card's edges, exactly like the single flush table this screen
 * rendered before grouping existed, but the header text still wants breathing room from the
 * border either side of it.
 */
.mrlist-group-header {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4) var(--space-2);
}

.mrlist-group-name {
  color: var(--text-secondary);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
}

.mrlist-group-count {
  color: var(--text-tertiary);
  font-size: var(--fs-xs);
  white-space: nowrap;
}

.mrlist-group-rule {
  flex: 1 1 auto;
  height: 1px;
  background: var(--border);
}

/*
 * The shared bar above (`.mrlist-sticky-header`) is the only column header a Reviewer sees: every
 * group's own `<thead>` is visually suppressed, unconditionally — none of them show one any more,
 * not just the repeats. `DataTable` wraps its `<table>` in an unclassed `<div>` (see
 * ../ds/components/DataTable.tsx), so the selector reaches through that wrapper rather than
 * assuming the table is `.mrlist-group`'s direct child. Scoped to `.mrlist-groups` so it can never
 * reach the shared header's own `DataTable`, which lives outside that wrapper on purpose.
 *
 * `visibility: hidden` here, deliberately not `display: none`: under `table-layout: fixed` (below),
 * column widths are taken from the first row's cells, and `display: none` removes a `<thead>` from
 * layout entirely — the browser then has no width information left in any *visible* row (`<td>`
 * cells carry no `width` of their own, only `<th>` does), and silently falls back to splitting the
 * table evenly across all six columns. Verified live: with `display: none` every group's columns
 * rendered at an identical 229px regardless of `column.width`, while the header table (its own
 * `<thead>` never hidden) rendered the real 826/160/130/70/100/90 split — the exact "everything is
 * misaligned to equal widths" bug this fixes. `visibility: hidden` keeps the `<th>` cells in the
 * layout box (so their widths still count) while making them invisible; the height rules below
 * collapse the now-invisible row to zero so it leaves no gap above the first data row.
 */
.mrlist-groups table thead {
  visibility: hidden;
}

.mrlist-groups table thead tr {
  height: 0;
}

.mrlist-groups table thead th {
  height: 0;
  overflow: hidden;
  padding: 0;
  border: 0;
  line-height: 0;
}

/*
 * `table-layout: fixed`, forced from outside `DataTable` (it never sets this property itself, only
 * `width`/`border-collapse`/`font-size` inline — so this overrides an unset default, not an inline
 * style, and needs no `!important`). Every group is its own separate `<table>`, and so is the
 * shared header (`.mrlist-sticky-header`'s empty-row `DataTable`); under the browser's default
 * `table-layout: auto`, a `column.width` hint is only ever a suggestion that a table's own actual
 * content can override, so the `title` column — the one column with no explicit width, by design
 * the one that absorbs whatever space is left — ends up a different width in every table depending
 * on what its rows happen to contain, and drags every column after it out of line with its
 * neighbours (author/team already truncate their own content, but that alone cannot fix a column
 * *before* them shifting the whole layout). `table-layout: fixed` makes column widths depend only
 * on the widths declared on the first row's cells, identically in every table regardless of content
 * — the header's empty row included — which is what actually guarantees alignment. The cost: a cell
 * whose content doesn't fit is no longer proportionally shrunk, it must be explicitly clipped
 * (`.mrlist-title`/`.mrlist-author-name`/`.mrlist-team` all already truncate with an ellipsis).
 */
.mrlist-groups table,
.mrlist-sticky-header table {
  table-layout: fixed;
}

.mrlist-person {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}

/*
 * The reviewers column. A **column** layout whatever the count, which is what makes several of them
 * stack rather than run into one line — one reviewer is simply a stack of one, so there is no
 * threshold to get wrong.
 */
.mrlist-reviewers {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  max-width: 100%;
}

/* Usernames, so mono — the same treatment every other identifier in this list gets. Each truncates
   on its own line: this column is scanned for a name, not read. */
.mrlist-reviewer {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-tertiary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/*
 * Webhook log (WHK-03). One row per delivery, expandable to the raw payload — a diagnostics page,
 * not a product surface, so it is deliberately plain: no filters, no chrome, and the payload
 * monospaced because that is the thing somebody came here to read.
 * Every value goes through a design-system token — see ../ds/README.md.
 */

/*
 * The shell's content canvas is full-bleed, so the screen carries its own page padding — the same
 * shape as `.settings-screen`.
 */
.whl-screen {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  padding: var(--space-6) var(--space-8);
}

.whl-message {
  margin: 0;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
  line-height: 1.5;
}

/*
 * The empty-log explanation. Dimmer than the statement above it: the sentence is the answer, this is
 * why it might be the answer.
 */
.whl-detail-text {
  margin: var(--space-2) 0 0;
  color: var(--text-disabled);
  font-size: var(--fs-xs);
  line-height: 1.5;
}

.whl-message-action {
  margin-top: var(--space-3);
}

.whl-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}

.whl-count {
  color: var(--text-secondary);
  font-size: var(--fs-sm);
}

.whl-spacer {
  flex: 1;
}

.whl-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.whl-row {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg-surface-2);
}

/*
 * The whole summary line is the disclosure control, so it takes the pointer and the focus ring
 * rather than a nested button — the same "the row *is* the control" rule the project step follows.
 */
.whl-summary {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  cursor: pointer;
  list-style: none;
}

.whl-summary::-webkit-details-marker {
  display: none;
}

.whl-received {
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
}

/*
 * Who and where, between the timestamp and the outcome summary. Both truncate rather than wrap: the
 * summary line is one row deep by design, and a long namespaced path must not push the outcome
 * counts — the thing that decides whether the row is worth opening — onto a second line.
 */
.whl-project {
  overflow: hidden;
  flex: 0 1 auto;
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.whl-actor {
  color: var(--text-secondary);
  font-size: var(--fs-xs);
  white-space: nowrap;
}

.whl-outcome-summary {
  overflow: hidden;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.whl-detail {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: 0 var(--space-4) var(--space-4);
  border-top: 1px solid var(--border);
  padding-top: var(--space-3);
}

.whl-label {
  color: var(--text-disabled);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.whl-outcomes {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.whl-outcome {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--text-primary);
  font-size: var(--fs-sm);
}

/*
 * The payload. Scrolls in both directions inside its own box rather than stretching the page: a
 * push payload with twenty commits is tall, and a minified one is very wide.
 */
.whl-payload {
  max-height: 24rem;
  margin: 0;
  padding: var(--space-3);
  overflow: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-surface);
  color: var(--text-secondary);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  line-height: 1.5;
  white-space: pre;
}
/*
 * The version indicator at the foot of the rail, above the Reviewer's name: the last thing about
 * the tool, above the thing about you. Tertiary and small — it is a fact to check, not a control
 * to notice — but a real button, because pressing it opens the changelog.
 */

.adminshell-version {
  display: block;
  width: 100%;
  padding: 6px 12px;
  font-family: var(--font-mono);
  font-size: var(--fs-2xs, 11px);
  color: var(--text-tertiary);
  text-align: left;
  cursor: pointer;
  background: transparent;
  border: 0;
  border-top: 1px solid var(--border);
}

.adminshell-version:hover {
  color: var(--text-secondary);
  background: var(--bg-hover);
}

/*
 * The rail's footer row: the version, and the light/dark switch.
 *
 * Both are about the tool rather than about whatever screen is open, which is why they share a row
 * here instead of sitting in a topbar the review screens deliberately do not render.
 */
.adminshell-footer {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  min-width: 0;
}

.adminshell-footer .adminshell-version {
  flex: 1 1 auto;
  min-width: 0;
  text-align: left;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/*
 * Collapsed to an icon-only strip, the two controls stack instead of competing for ~48px: side by
 * side they overflowed the rail, which is what "doesn't work well with the sidebar collapsing"
 * looked like. Centred, because in the collapsed rail everything else is.
 */
.adminshell-footer[data-collapsed='true'] {
  flex-direction: column;
  gap: var(--space-1);
}

.adminshell-footer[data-collapsed='true'] .adminshell-version {
  flex: 0 0 auto;
  text-align: center;
  /* The version number itself, with no room for a `v` prefix to earn its place. */
  font-size: 10px;
}


/* Firebase Admin Design System — entry point. Import this once, from main.tsx.
 *
 * Mirrors the design system's own styles.css (@import lines only). Fonts come
 * from Google Fonts, matching the design system; if SOFTGAMES ever licenses the
 * brand fonts, replace the @import in fonts.css with local @font-face rules.
 */
/* Firebase Admin Design System — Webfonts
 * NOTE: These are Google Fonts substitutes chosen for a geometric-sans +
 * mono instrumentation feel. No brand font files were provided.
 *   Display / UI : Space Grotesk (headings, brand)
 *   Body / UI    : Manrope
 *   Mono / data  : JetBrains Mono (numbers, IDs, logs, code)
 * If SOFTGAMES has licensed brand fonts, drop the files in this folder and
 * replace the @import below with local @font-face rules.
 *
 * VENDORED from the Firebase Admin Design System — see ../README.md. Do not edit
 * by hand; re-copy from the design system instead.
 */
/* Firebase Admin Design System — Color tokens
 * Dark-first. Light theme lives under [data-theme="light"].
 * Base ramps are raw values; semantic aliases are what components consume.
 *
 * VENDORED from the Firebase Admin Design System — see ../README.md. Do not edit
 * by hand; re-copy from the design system instead.
 */
:root {
  /* ---- Brand / accent (Firebase amber) ---- */
  --amber-300: #fbbf24;
  --amber-400: #f59e0b; /* primary accent */
  --amber-500: #e08600;
  --amber-600: #b86c00;
  --amber-050: #4a3410; /* dark tint for wash/backgrounds */

  /* ---- Neutral ramp (cool slate) ---- */
  --slate-950: #0b0e14;
  --slate-900: #12161f;
  --slate-850: #161b26;
  --slate-800: #181d28;
  --slate-750: #1e2430;
  --slate-700: #262d3a;
  --slate-600: #333b4a;
  --slate-500: #4a5468;
  --slate-400: #6b7385;
  --slate-300: #9aa3b2;
  --slate-200: #c3c9d4;
  --slate-100: #e7eaf0;
  --slate-050: #f4f6fa;
  --white: #ffffff;

  /* ---- Semantic status hues ---- */
  --blue-400: #3b82f6;
  --blue-tint: #142743;
  --green-400: #22c55e;
  --green-tint: #0f2c1c;
  --yellow-400: #eab308;
  --yellow-tint: #33290a;
  --red-400: #ef4444;
  --red-tint: #3a1414;
  --violet-400: #8b5cf6;
  --teal-400: #2dd4bf;

  /* ---- Data-viz categorical palette ---- */
  --viz-1: #f59e0b;
  --viz-2: #3b82f6;
  --viz-3: #2dd4bf;
  --viz-4: #8b5cf6;
  --viz-5: #ec4899;
  --viz-6: #22c55e;

  /* ================= SEMANTIC ALIASES (dark) ================= */
  --bg-app: var(--slate-950);
  --bg-surface: var(--slate-900);
  --bg-surface-2: var(--slate-800);
  --bg-elevated: var(--slate-750);
  --bg-inset: #090b10;
  --bg-hover: rgba(255, 255, 255, 0.04);
  --bg-active: rgba(255, 255, 255, 0.07);

  --border: rgba(255, 255, 255, 0.08);
  --border-strong: var(--slate-600);
  --border-focus: var(--amber-400);

  --text-primary: var(--slate-100);
  --text-secondary: var(--slate-300);
  --text-tertiary: var(--slate-400);
  --text-disabled: var(--slate-500);
  --text-inverse: var(--slate-950);

  --accent: var(--amber-400);
  --accent-hover: var(--amber-300);
  --accent-active: var(--amber-500);
  --accent-wash: var(--amber-050);
  --accent-contrast: #1a1205;

  --info: var(--blue-400);
  --info-wash: var(--blue-tint);
  --success: var(--green-400);
  --success-wash: var(--green-tint);
  --warning: var(--yellow-400);
  --warning-wash: var(--yellow-tint);
  --danger: var(--red-400);
  --danger-wash: var(--red-tint);

  --focus-ring: 0 0 0 2px var(--bg-app), 0 0 0 4px var(--amber-400);
}

/* ================= LIGHT THEME =================
 * Soft, low-contrast content canvas. Pairs with the dark navigation rail
 * (the sidebar keeps the :root dark tokens). Surfaces sit close together and
 * text is intentionally softened (not near-black) to reduce contrast.
 */
[data-theme='light'] {
  --bg-app: #edeff4; /* page behind cards */
  --bg-surface: #fbfbfd; /* cards — off-white, subtle */
  --bg-surface-2: #f1f3f7; /* table headers, insets, wells */
  --bg-elevated: #ffffff; /* overlays lift slightly above surface */
  --bg-inset: #e9ecf2; /* input wells */
  --bg-hover: rgba(24, 30, 44, 0.035);
  --bg-active: rgba(24, 30, 44, 0.06);

  --border: rgba(24, 30, 44, 0.08);
  --border-strong: #d9dee7;
  --border-focus: var(--amber-500);

  --text-primary: #2b3342; /* softened, not near-black */
  --text-secondary: #5b6578;
  --text-tertiary: #8a93a3;
  --text-disabled: #aeb6c2;
  --text-inverse: #ffffff;

  --accent: var(--amber-500);
  --accent-hover: var(--amber-400);
  --accent-active: var(--amber-600);
  --accent-wash: #fbf1de;
  --accent-contrast: #ffffff;

  --info-wash: #e9f1fd;
  --success-wash: #e7f5ec;
  --warning-wash: #faf2dc;
  --danger-wash: #fbe9e9;

  --focus-ring: 0 0 0 2px var(--bg-app), 0 0 0 4px var(--amber-500);
}
/* Firebase Admin Design System — Typography tokens
 *
 * VENDORED from the Firebase Admin Design System — see ../README.md. Do not edit
 * by hand; re-copy from the design system instead.
 */
:root {
  --font-display: 'Space Grotesk', 'Manrope', system-ui, sans-serif;
  --font-sans: 'Manrope', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;

  /* Type scale (dashboard-tuned; compact) */
  --fs-display: 30px;
  --fs-h1: 24px;
  --fs-h2: 20px;
  --fs-h3: 16px;
  --fs-body: 14px;
  --fs-sm: 13px;
  --fs-xs: 12px;
  --fs-2xs: 11px;
  --fs-metric: 28px; /* KPI numbers */
  --fs-metric-lg: 40px;

  --lh-tight: 1.15;
  --lh-snug: 1.3;
  --lh-normal: 1.5;

  --fw-regular: 400;
  --fw-medium: 500;
  --fw-semibold: 600;
  --fw-bold: 700;
  --fw-extrabold: 800;

  --tracking-tight: -0.02em;
  --tracking-normal: 0;
  --tracking-wide: 0.02em;
  --tracking-caps: 0.06em;

  /* tabular figures for data alignment */
  --num-tabular: 'tnum' 1, 'lnum' 1;
}
/* Firebase Admin Design System — Spacing & sizing tokens
 * 4px base grid.
 *
 * VENDORED from the Firebase Admin Design System — see ../README.md. Do not edit
 * by hand; re-copy from the design system instead.
 */
:root {
  --space-0: 0;
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-10: 40px;
  --space-12: 48px;
  --space-16: 64px;

  /* Control heights */
  --control-sm: 28px;
  --control-md: 34px;
  --control-lg: 40px;

  /* Layout dimensions */
  --sidebar-w: 240px;
  --sidebar-w-collapsed: 60px;
  --topbar-h: 56px;
  --content-max: 1440px;
}
/* Firebase Admin Design System — Radii, borders, shadows, motion
 *
 * VENDORED from the Firebase Admin Design System — see ../README.md. Do not edit
 * by hand; re-copy from the design system instead.
 */
:root {
  /* Radii */
  --radius-xs: 4px;
  --radius-sm: 6px;
  --radius-md: 8px;
  --radius-lg: 12px;
  --radius-xl: 16px;
  --radius-pill: 999px;

  /* Borders */
  --border-w: 1px;

  /* Shadows (dark-tuned, low & tight) */
  --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.35);
  --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.3);
  --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.45);
  --shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.5);
  --shadow-focus-amber: 0 0 0 3px rgba(245, 158, 11, 0.25);

  /* Motion */
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
  --dur-fast: 120ms;
  --dur-base: 180ms;
  --dur-slow: 260ms;
}

[data-theme='light'] {
  --shadow-xs: 0 1px 2px rgba(16, 21, 31, 0.06);
  --shadow-sm: 0 1px 3px rgba(16, 21, 31, 0.08), 0 1px 2px rgba(16, 21, 31, 0.06);
  --shadow-md: 0 4px 14px rgba(16, 21, 31, 0.1);
  --shadow-lg: 0 14px 38px rgba(16, 21, 31, 0.14);
}
/* Firebase Admin Design System — base element resets & helpers.
 *
 * VENDORED from the Firebase Admin Design System — see ../README.md. Do not edit
 * by hand; re-copy from the design system instead.
 */
*,
*::before,
*::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  line-height: var(--lh-normal);
  color: var(--text-primary);
  background: var(--bg-app);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1,
h2,
h3,
h4 {
  font-family: var(--font-display);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-tight);
  line-height: var(--lh-tight);
  margin: 0;
  color: var(--text-primary);
}

/* Tabular numerals utility for data */
.tnum {
  font-variant-numeric: tabular-nums lining-nums;
}

.mono {
  font-family: var(--font-mono);
}

::selection {
  background: rgba(245, 158, 11, 0.28);
}

/* Scrollbars (dark-friendly) */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--slate-600) transparent;
}

*::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

*::-webkit-scrollbar-thumb {
  background: var(--slate-600);
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: content-box;
}

*::-webkit-scrollbar-thumb:hover {
  background: var(--slate-500);
}
