/* ==========================================================================
   SwingAgent Portal — design system
   --------------------------------------------------------------------------
   Hand-written, self-hosted, no build step and no third-party CSS: the portal
   ships `default-src 'self'` (SecurityHeadersMiddleware), so a CDN stylesheet
   or a Google font would simply be blocked. Typography is the OS stack for the
   same reason — nothing here touches the network.

   Three layers, in order. Pages should only ever consume layer 2/3:
     1. PRIMITIVES  --sw-brand-*, --sw-n-*   raw ramps. Never used directly.
     2. SEMANTICS   --sw-surface, --sw-fg…   what a page means. Theme-aware.
     3. COMPONENTS  .panel, .btn, .badge…    the things you write in Razor.

   The brand anchor is Servico navy #002554 = hsl(214, 100%, 16.5%): a deep,
   fully-saturated blue. It is superb as a button fill or a heading (15:1 on
   white) but it is NOT usable as a dark-mode accent — against a dark surface
   it disappears. Hence a full ramp at hue 214 rather than the one hex: light
   mode accents on brand-700/900, dark mode on brand-300/500.
   ========================================================================== */

/* ---------- Layer 1: primitives ------------------------------------------ */

:root {
  /* Servico navy ramp. 900 is the brand hex exactly; the rest is that hue
     walked up and down in lightness so light and dark themes can both find a
     step that carries enough contrast. */
  --sw-brand-950: hsl(214, 100%, 8%);
  --sw-brand-900: #002554;                 /* the brand */
  --sw-brand-800: hsl(214, 100%, 22%);
  --sw-brand-700: hsl(214, 100%, 30%);
  --sw-brand-600: hsl(214, 100%, 38%);
  --sw-brand-500: hsl(214, 100%, 46%);
  --sw-brand-400: hsl(214, 100%, 58%);
  --sw-brand-300: hsl(214, 100%, 70%);
  --sw-brand-200: hsl(214, 100%, 82%);
  --sw-brand-100: hsl(214, 100%, 91%);
  --sw-brand-50:  hsl(214, 100%, 96.5%);

  /* Neutrals carry a trace of the brand hue (214) instead of being pure grey.
     A cool-tinted neutral sits beside navy as one family; #808080 next to
     #002554 reads as two unrelated decisions. */
  --sw-n-950: hsl(214, 30%, 7%);
  --sw-n-900: hsl(214, 28%, 11%);
  --sw-n-850: hsl(214, 26%, 15%);
  --sw-n-800: hsl(214, 24%, 19%);
  --sw-n-700: hsl(214, 20%, 28%);
  --sw-n-600: hsl(214, 17%, 40%);
  --sw-n-500: hsl(214, 15%, 52%);
  --sw-n-400: hsl(214, 16%, 66%);
  --sw-n-300: hsl(214, 18%, 78%);
  --sw-n-200: hsl(214, 20%, 88%);
  --sw-n-100: hsl(214, 24%, 94%);
  --sw-n-50:  hsl(214, 28%, 97%);
  --sw-n-25:  hsl(214, 30%, 98.5%);
  --sw-white: #fff;

  /* Status hues. Deliberately desaturated next to the brand so a page of
     badges does not turn into a fruit salad. */
  --sw-green-700: hsl(148, 60%, 26%);
  --sw-green-600: hsl(148, 52%, 34%);
  --sw-green-300: hsl(148, 45%, 62%);
  --sw-green-100: hsl(148, 50%, 90%);
  --sw-green-950: hsl(148, 45%, 11%);
  --sw-amber-700: hsl(35, 78%, 30%);
  --sw-amber-600: hsl(35, 85%, 40%);
  --sw-amber-300: hsl(38, 88%, 64%);
  --sw-amber-100: hsl(40, 90%, 88%);
  --sw-amber-950: hsl(33, 70%, 12%);
  --sw-red-700:   hsl(2, 68%, 38%);
  --sw-red-600:   hsl(2, 72%, 47%);
  --sw-red-300:   hsl(2, 80%, 70%);
  --sw-red-100:   hsl(2, 85%, 92%);
  --sw-red-950:   hsl(2, 60%, 13%);

  /* ---------- Scales: one source of truth for rhythm ---------------------- */
  /* 4px base. The old stylesheet had no scale, so 13 pages invented their own
     padding (8px / 12px / 0.5rem / 1em) inline — that inconsistency is most of
     what reads as "messy". */
  --sw-s1: 0.25rem;   /*  4 */
  --sw-s2: 0.5rem;    /*  8 */
  --sw-s3: 0.75rem;   /* 12 */
  --sw-s4: 1rem;      /* 16 */
  --sw-s5: 1.5rem;    /* 24 */
  --sw-s6: 2rem;      /* 32 */
  --sw-s7: 3rem;      /* 48 */

  --sw-r-sm: 4px;
  --sw-r:    6px;
  --sw-r-md: 8px;
  --sw-r-lg: 12px;
  --sw-r-full: 999px;

  /* A control's non-text width: 2x12px of padding + 2x1px of border (box-sizing is
     border-box everywhere, so max-width covers both) + room for a <select> arrow.
     Added to every ch step below, so the number in a `.w-*` class name is the number
     of CHARACTERS the value has rather than a box width. */
  --sw-field-chrome: 3rem;

  /* 14px base: an operator console is denser than a marketing page, and the
     whitespace comes from the spacing scale rather than from big type. */
  --sw-text-xs:  0.75rem;
  --sw-text-sm:  0.8125rem;
  --sw-text:     0.875rem;
  --sw-text-md:  1rem;
  --sw-text-lg:  1.125rem;
  --sw-text-xl:  1.375rem;
  --sw-text-2xl: 1.75rem;

  --sw-font: "Segoe UI Variable Text", "Segoe UI", system-ui, -apple-system,
             Roboto, "Helvetica Neue", Arial, sans-serif;
  --sw-font-mono: "Cascadia Mono", "Cascadia Code", Consolas,
                  ui-monospace, SFMono-Regular, "Liberation Mono", monospace;

  /* The shell top bar's height, which is also the offset every box pinned under it
     needs. It was written as a literal in TWO places (`.app-top { height }` and
     `.app-body { min-height: calc(100vh - 52px) }`) and is now read in six, so it
     stops being a number you have to find again. */
  --sw-top-h: 52px;
  --sw-side-w: 232px;
  --sw-main-w: 1200px;

  --sw-transition: 120ms ease;
}

/* ---------- Layer 2: semantics -------------------------------------------
   The old sheet declared `color-scheme: light dark` and then hard-coded light
   colours, so a dark-OS visitor got UA-darkened form controls inside
   light-assuming cards. This is that promise actually kept.

   Every theme-varying token is declared ONCE via `light-dark()`, which
   resolves against the used `color-scheme`. Two consequences worth knowing:
     - there is no duplicated dark block to drift out of sync, and
     - the `[data-theme]` hook below works by flipping `color-scheme` alone,
       so a future in-app toggle needs no new token plumbing.

   Dark is not an inversion. Surfaces step UP from the page background (the
   physical metaphor: closer to the light), shadows lean on borders because a
   shadow over near-black is invisible, and the accent climbs to brand-600/300
   — #002554 on a dark surface is unreadable. */

:root {
  color-scheme: light dark;

  --sw-bg:            light-dark(var(--sw-n-50),  var(--sw-n-950));
  --sw-surface:       light-dark(var(--sw-white), var(--sw-n-900));
  --sw-surface-2:     light-dark(var(--sw-n-50),  var(--sw-n-850));  /* pre, thead */
  --sw-surface-3:     light-dark(var(--sw-n-100), var(--sw-n-800));  /* hover, wells */
  --sw-surface-inset: light-dark(var(--sw-n-25),  hsl(214, 30%, 9%));

  --sw-fg:           light-dark(var(--sw-n-900), var(--sw-n-100));
  --sw-fg-muted:     light-dark(var(--sw-n-600), var(--sw-n-400));
  /* n-500 measured 3.92:1 light / 4.42:1 dark against surface — under the 4.5:1
     floor for body text, and this token carries placeholders, help text and
     .msg-status. Pulled to either side of it per theme (WCAG 1.4.3). */
  --sw-fg-subtle:    light-dark(hsl(214, 16%, 44%), hsl(214, 15%, 60%));
  --sw-fg-on-accent: var(--sw-white);

  --sw-border:        light-dark(var(--sw-n-200), var(--sw-n-800));
  --sw-border-strong: light-dark(var(--sw-n-300), var(--sw-n-700));
  /* A form control's boundary is what identifies it, so it needs 3:1 against the
     surface (WCAG 1.4.11) — n-300/n-700 measured 1.73:1 and 1.78:1. This is a
     visibly heavier border than a purely decorative one, which is the point. */
  --sw-border-input:  light-dark(hsl(214, 16%, 58%), hsl(214, 14%, 50%));

  --sw-accent:        light-dark(var(--sw-brand-900), var(--sw-brand-600));
  --sw-accent-hover:  light-dark(var(--sw-brand-800), var(--sw-brand-500));
  --sw-accent-active: light-dark(var(--sw-brand-950), var(--sw-brand-700));
  --sw-accent-quiet:  light-dark(var(--sw-brand-50),  hsl(214, 60%, 16%));
  --sw-link:          light-dark(var(--sw-brand-700), var(--sw-brand-300));
  --sw-link-hover:    light-dark(var(--sw-brand-900), var(--sw-brand-200));
  --sw-focus:         light-dark(var(--sw-brand-500), var(--sw-brand-400));
  /* A ticked checkbox or a chosen radio. The accent in light; one step up from it in dark,
     where brand-600 measured 2.51:1 against the surface, under the 3:1 a control's state
     needs (WCAG 1.4.11). brand-500 measures 3.37:1 and still carries the white tick at 5.14:1. */
  --sw-check:         light-dark(var(--sw-brand-900), var(--sw-brand-500));
  --sw-check-hover:   light-dark(var(--sw-brand-800), var(--sw-brand-400));
  /* A ticked control the reader cannot change: grey, and quieter than one they can. Light: the
     subtle foreground, 5.22:1 against the surface beside navy's 15.08:1. Dark: n-600 at 2.86:1
     beside brand-500's 3.37:1; the subtle foreground (5.83:1) made the locked boxes outshine the
     ones a reader ticks. The mark on it: white at 5.22:1 light, n-100 at 5.27:1 dark. */
  --sw-check-locked:      light-dark(hsl(214, 16%, 44%), var(--sw-n-600));
  --sw-check-locked-mark: light-dark(var(--sw-white), var(--sw-n-100));

  /* Light: the shell is navy, anchoring the brand somewhere permanent so the
     content column can stay quiet and near-white. Dark: navy would sink into
     the page, so the shell becomes a raised neutral and the brand shows up on
     the accent instead. */
  --sw-shell-bg:        light-dark(var(--sw-brand-900),   var(--sw-n-900));
  --sw-shell-fg:        light-dark(hsl(214, 40%, 92%),    var(--sw-n-200));
  /* n-500 on the dark shell measured 4.42:1 — just under the text floor. */
  --sw-shell-fg-muted:  light-dark(hsl(214, 30%, 72%),    var(--sw-n-400));
  --sw-shell-border:    light-dark(hsl(214, 100%, 24%),   var(--sw-n-800));
  --sw-shell-hover:     light-dark(hsl(214, 100%, 22%),   var(--sw-n-850));
  --sw-shell-active-bg: light-dark(hsl(214, 100%, 26%),   hsl(214, 60%, 16%));

  --sw-ok-bg:         light-dark(var(--sw-green-100), var(--sw-green-950));
  --sw-ok-fg:         light-dark(var(--sw-green-700), var(--sw-green-300));
  --sw-ok-border:     light-dark(var(--sw-green-300), hsl(148, 40%, 26%));
  --sw-warn-bg:       light-dark(var(--sw-amber-100), var(--sw-amber-950));
  --sw-warn-fg:       light-dark(var(--sw-amber-700), var(--sw-amber-300));
  --sw-warn-border:   light-dark(var(--sw-amber-300), hsl(35, 50%, 30%));
  --sw-danger-bg:     light-dark(var(--sw-red-100),   var(--sw-red-950));
  --sw-danger-fg:     light-dark(var(--sw-red-700),   var(--sw-red-300));
  --sw-danger-border: light-dark(var(--sw-red-300),   hsl(2, 45%, 32%));
  --sw-info-bg:       light-dark(var(--sw-brand-50),  hsl(214, 60%, 16%));
  --sw-info-fg:       light-dark(var(--sw-brand-700), var(--sw-brand-300));
  --sw-info-border:   light-dark(var(--sw-brand-200), hsl(214, 60%, 28%));

  /* light-dark() takes <color>, not a whole shadow value — so only the colour
     component is swapped and the geometry stays shared. */
  --sw-shadow-sm: 0 1px 2px light-dark(hsl(214 30% 12% / 0.06), hsl(214 40% 2% / 0.5));
  --sw-shadow:    0 1px 3px light-dark(hsl(214 30% 12% / 0.08), hsl(214 40% 2% / 0.6)),
                  0 4px 12px light-dark(hsl(214 30% 12% / 0.04), transparent);
  --sw-shadow-lg: 0 2px 6px light-dark(hsl(214 30% 12% / 0.08), hsl(214 40% 2% / 0.4)),
                  0 12px 32px light-dark(hsl(214 30% 12% / 0.10), hsl(214 40% 2% / 0.7));
}

/* Override hook: flipping `color-scheme` re-resolves every light-dark() above,
   so this alone is a complete theme switch in both directions. */
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"]  { color-scheme: dark; }

/* ---------- Base ---------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

/* `scrollbar-gutter`: the content column is centred, so a page short enough to need no scrollbar
   centred it 7px further right than a long one, and the header, tab bar and first panel visibly
   jumped sideways when an operator moved between tenant tabs. The gutter is reserved on every page. */
html { -webkit-text-size-adjust: 100%; scrollbar-gutter: stable; }

body {
  margin: 0;
  background: var(--sw-bg);
  color: var(--sw-fg);
  font-family: var(--sw-font);
  font-size: var(--sw-text);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 {
  margin: 0;
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.011em;
  color: var(--sw-fg);
}
h1 { font-size: var(--sw-text-2xl); }
h2 { font-size: var(--sw-text-lg); }
h3 { font-size: var(--sw-text-md); }
h4 { font-size: var(--sw-text); }

/* Section headings inside a flowing page need their own air. Pages use bare
   h2/h3 today, so the rhythm has to come from the element, not a class. */
h2 { margin: var(--sw-s6) 0 var(--sw-s3); }
h3 { margin: var(--sw-s5) 0 var(--sw-s2); }
h4 { margin: var(--sw-s4) 0 var(--sw-s2); }
h1 + h2, h2 + h3, .page-head + h2 { margin-top: var(--sw-s5); }
h1 small, h2 small, h3 small {
  font-weight: 400;
  font-size: var(--sw-text-sm);
  color: var(--sw-fg-muted);
  letter-spacing: 0;
}

p { margin: 0 0 var(--sw-s3); }
p:last-child { margin-bottom: 0; }
small { font-size: var(--sw-text-sm); color: var(--sw-fg-muted); }
strong { font-weight: 600; }
hr {
  border: 0;
  border-top: 1px solid var(--sw-border);
  margin: var(--sw-s6) 0;
}

a { color: var(--sw-link); text-decoration: none; }
a:hover { color: var(--sw-link-hover); text-decoration: underline; }

code, kbd, samp, pre { font-family: var(--sw-font-mono); font-size: 0.9em; }
:not(pre) > code {
  background: var(--sw-surface-3);
  border: 1px solid var(--sw-border);
  border-radius: var(--sw-r-sm);
  padding: 0.1em 0.35em;
  color: var(--sw-fg);
  /* NOT `nowrap`: identifiers like SWINGAGENT_EMAIL_HOSTED_SERVICES are long enough
     to push the page sideways on a narrow viewport (WCAG 1.4.10 Reflow).
     `break-word` keeps them intact until the word alone cannot fit. */
  overflow-wrap: break-word;
}

/* One focus treatment for the entire portal. */
:focus-visible {
  outline: 2px solid var(--sw-focus);
  outline-offset: 2px;
  border-radius: var(--sw-r-sm);
}

.skip-link {
  /* `fixed`, not `absolute`. Absolutely positioned it resolves against the initial
     containing block — document coordinates — so at any scroll offset past its own
     height it is off-screen entirely and Tab reveals nothing at all. Fixed keeps it in
     the viewport at every offset, and the z-index has to outrank the sticky top bar
     (30) or the bar paints over it. Its own --sw-surface fill and --sw-border-strong
     border already carry it over the navy. */
  position: fixed;
  left: -9999px;
  top: var(--sw-s2);
  z-index: 200;
  background: var(--sw-surface);
  color: var(--sw-link);
  padding: var(--sw-s2) var(--sw-s4);
  border: 1px solid var(--sw-border-strong);
  border-radius: var(--sw-r);
  box-shadow: var(--sw-shadow);
}
.skip-link:focus { left: var(--sw-s4); }

/* ---------- App shell ----------------------------------------------------
   Topbar across the full width, then a fixed-width sidebar beside a scrolling
   content column. Replaces the old `main { max-width: 900px }`, which forced
   666-line pages like Tenants/Detail into a single narrow scroll wall and left
   wide tables to overflow. */

.app-top {
  display: flex;
  align-items: center;
  gap: var(--sw-s4);
  height: var(--sw-top-h);
  padding: 0 var(--sw-s5);
  background: var(--sw-shell-bg);
  color: var(--sw-shell-fg);
  border-bottom: 1px solid var(--sw-shell-border);
  /* Pinned. Tenant detail is 8 400px tall, so an operator who had scrolled to the
     bottom of it had no sign-out, no theme control and no route to any other page
     without scrolling all the way back up. The z-index is not optional: `.app-main`
     comes LATER in the DOM, so without it the content column paints over the bar. */
  position: sticky;
  top: 0;
  z-index: 30;
}
.app-top .brand {
  display: flex;
  align-items: center;
  gap: var(--sw-s2);
  font-weight: 600;
  font-size: var(--sw-text-md);
  letter-spacing: -0.015em;
  color: var(--sw-white);
  text-decoration: none;
}
.app-top .brand:hover { color: var(--sw-white); text-decoration: none; }
.app-top .brand-mark {
  display: grid;
  place-items: center;
  width: 24px; height: 24px;
  border-radius: var(--sw-r-sm);
  background: var(--sw-white);
  color: var(--sw-brand-900);
  font-size: var(--sw-text-sm);
  font-weight: 700;
}
.app-top .brand { min-width: 0; overflow: hidden; }
/* SHL-3. The bar now has two clusters — product identity on the left (wordmark, then the
   deployment badge), session controls on the right (who, theme, sign out) — and this auto
   margin is the boundary between them. It used to sit on `.brand` as `margin-right: auto`,
   which put anything inserted "after the brand" into the RIGHT-hand cluster beside the
   address, the opposite of how that instruction reads. Stated on the first child of the
   right cluster instead, so adding to either cluster no longer moves it. */
.app-top .user { margin-left: auto; }
/* The mark holds its 24px and the WORDMARK is what gives way. Before this the wordmark was a
   bare text node that wrapped to a second line and was clipped by the fixed 52px bar — at
   320px it read "SwingAger / Portal".
   ⭐ Measured, because the arithmetic says otherwise: this costs the bar NOTHING in reflow.
   `.brand`'s own `min-width: 0` already lets the whole anchor be squeezed past its content,
   so `flex: none` here governs only the layout INSIDE the anchor. The bar's overflow
   threshold is byte-identical with and without these two rules at every label length. */
.app-top .brand-mark { flex: none; }
.app-top .brand-text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The signed-in address can be long; truncate it rather than let the bar push the
   page sideways. It stays readable via the title attribute and on wider screens. */
.app-top .user {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--sw-text-sm);
  color: var(--sw-shell-fg-muted);
}

/* The theme control sits ON the shell, so it drops the form-field slab treatment
   the base `select` rule gives it. */
.app-top .theme-select {
  width: auto;
  flex: none;
  padding: 0.1875rem var(--sw-s2);
  font-size: var(--sw-text-sm);
  background: var(--sw-shell-hover);
  color: var(--sw-shell-fg);
  /* NOT --sw-shell-border: that is a divider colour and measured 1.48:1 against the
     bar, while the control's own fill barely differs from it — so nothing conveyed
     "this is a control" (WCAG 1.4.11 wants 3:1). The muted shell foreground is
     already proven legible on both shells, so it clears the bar comfortably. */
  border-color: var(--sw-shell-fg-muted);
  box-shadow: none;
}
.app-top .theme-select:hover { border-color: var(--sw-shell-fg); }
/* The theme select is the only form control in the navy shell, on every page. It used
   to be the one control with NO focus indicator at all: `.app-top .theme-select`
   (0,2,0) cancels the box-shadow ring that `select:focus` (0,1,1) supplies, and
   `.app-top :focus-visible` (0,2,0) sets only `outline-color`, which paints nothing
   while the outline's style is `none` — so focus on it was a 1px border shift. With
   the transparent outline now on `select:focus` that colour override alone would
   paint a 2px ring hugging the control; this rule is (0,3,0) and states the whole
   treatment — offset, halo and border — outright rather than leaving it to emerge
   from three rules interacting. */
.app-top .theme-select:focus-visible {
  outline: 2px solid var(--sw-brand-200);
  outline-offset: 2px;
  border-color: var(--sw-brand-200);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--sw-brand-200) 35%, transparent);
}
/* The native popup does not inherit the shell colours reliably — on a navy select
   the options could come out light-on-light. State them explicitly. */
.app-top .theme-select option {
  background: var(--sw-surface);
  color: var(--sw-fg);
}
/* brand-500 is too close to the navy shell to read as a focus ring; inside the
   shell the indicator moves up the ramp. */
.app-top :focus-visible { outline-color: var(--sw-brand-200); }
.app-top form { margin: 0; }

/* `dvh` rather than `vh`, here and on `.app-side` below. `vh` is the LARGE viewport
   — the height with the mobile browser's chrome hidden — so a `100vh`-tall pinned box
   extends underneath that chrome and its bottom rows are unreachable. `dvh` tracks the
   height that is actually visible. Deliberately WITHOUT a `100vh` line above it as a
   fallback: every colour in this sheet is declared through `light-dark()` (Chrome 123,
   Safari 17.5), which is years newer than `dvh` (Chrome 108, Safari 15.4), so any engine
   that cannot parse `dvh` has already lost the entire palette and a fallback height
   would be the least of it. */
.app-body { display: flex; align-items: flex-start; min-height: calc(100dvh - var(--sw-top-h)); }

.app-side {
  flex: 0 0 var(--sw-side-w);
  /* This rule has declared `position: sticky` since the shell was built and it has
     never stuck, because `align-self: stretch` stretched the sidebar to the flex
     container's CONTENT height — 8 372px on tenant detail. A sticky box the full
     height of its containing block has nowhere to slide, so it sat still while the
     page moved past it. `flex-start` plus an explicit viewport-height box is what
     gives it the room; the sticky was never the missing half. */
  align-self: flex-start;
  height: calc(100dvh - var(--sw-top-h));
  /* Once the box is the viewport rather than the content, a nav taller than a short
     viewport would simply be clipped. */
  overflow-y: auto;
  padding: var(--sw-s4) var(--sw-s3);
  background: var(--sw-surface);
  border-right: 1px solid var(--sw-border);
  position: sticky;
  /* Was 0, which is under the now-sticky bar rather than below it. */
  top: var(--sw-top-h);
  z-index: 20;
}
.nav-group { margin-bottom: var(--sw-s5); }
.nav-group > .nav-label {
  padding: 0 var(--sw-s3);
  margin-bottom: var(--sw-s2);
  font-size: var(--sw-text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--sw-fg-subtle);
}
.app-side a {
  display: block;
  padding: var(--sw-s2) var(--sw-s3);
  margin-bottom: 1px;
  border-radius: var(--sw-r);
  color: var(--sw-fg-muted);
  font-weight: 500;
  transition: background var(--sw-transition), color var(--sw-transition);
}
.app-side a:hover {
  background: var(--sw-surface-3);
  color: var(--sw-fg);
  text-decoration: none;
}
.app-side a[aria-current] {
  background: var(--sw-accent-quiet);
  color: var(--sw-link);
  font-weight: 600;
}

/* CL7 (D-402): the language switcher, in the sidebar's Account group and under the signed-out card. Each language
   names itself, so the control stays small: a select and its button on one line, never wider than its column. */
.language-form { display: flex; align-items: center; gap: var(--sw-s2); margin: 0; }
.language-form select {
  width: auto;
  flex: 0 1 auto;
  min-width: 0;
  padding: 0.1875rem var(--sw-s2);
  font-size: var(--sw-text-sm);
}
.app-side .language-form { padding: var(--sw-s2) var(--sw-s3); }
/* The CL7 walk: the button took the 12px top margin a button stacked under a control gets, and sat 6px below its select. */
.language-form > button { margin: 0; }

.app-main { flex: 1 1 auto; min-width: 0; padding: var(--sw-s6) var(--sw-s6) var(--sw-s7); }
.page { max-width: var(--sw-main-w); margin: 0 auto; }
/* An in-page anchor scrolls its target to the top of the scrollport, which is now behind
   the sticky bar — so `#main` (the skip link's own target) and every panel anchor F12
   adds would land underneath it. One step of air below the bar so the heading is not
   flush against it. Inert until something is anchored. */
.app-main, .app-main [id] { scroll-margin-top: calc(var(--sw-top-h) + var(--sw-s4)); }
/* F43. `#main` is the skip link's target and now carries a negative tabindex, so activating the
   link moves FOCUS there rather than only the scroll position - without it the next Tab returns
   to the top bar and the link has skipped nothing. The ring the portal-wide `:focus-visible`
   rule paints belongs on a control, not around the whole content column, so it is suppressed
   here. Specificity (0,2,0) against that rule's (0,1,0), so this wins wherever it sits.

   `:focus`, not `:focus-visible`: a programmatic focus is not a `:focus-visible` match in every
   engine, and the ring being suppressed is the one `:focus` shows.

   Transparent rather than switched off - D-98 / PX-D1, pinned by
   The_focus_outline_is_never_switched_off, whose own comment names this exact case. A real
   outline stays in the cascade for the forced-colors block at the end of this sheet to repaint,
   and that block ties on specificity and comes later, so it still wins there. */
.app-main:focus { outline: 2px solid transparent; outline-offset: 0; }

/* Auth pages and other chrome-less screens. */
/* `align-content` is the half `place-items` does not set, and SHL-3 is what makes it matter:
   with a second child the two auto ROWS stretch to fill the 100vh box, so the head floated at
   a quarter height and the card at three quarters. Packed and centred, they read as one stack.
   Only the block axis — `justify-content` is left alone, because the column track filling the
   container is what `.card`'s `width: 100%` resolves against. */
.app-plain {
  display: grid;
  place-items: center;
  align-content: center;
  gap: var(--sw-s4);
  min-height: 100vh;
  padding: var(--sw-s5);
}

/* SHL-3 / F44. The product and the deployment, above the signed-out card.
   🛑 NOT `.app-top`'s selectors with `.plain-head` added to them: those paint the shell
   palette (`--sw-shell-*`, white on navy) and this sits on the page background, so the mark
   would have come out white-on-white. The accent pair is the one already measured for
   `.btn-primary`, and it flips with the theme where `--sw-white` would not. */
.plain-head {
  display: flex;
  align-items: center;
  gap: var(--sw-s2);
  font-size: var(--sw-text-md);
  font-weight: 600;
  color: var(--sw-fg);
}
.plain-head .brand-mark {
  display: grid;
  place-items: center;
  flex: none;
  width: 24px; height: 24px;
  border-radius: var(--sw-r-sm);
  background: var(--sw-accent);
  color: var(--sw-fg-on-accent);
  font-size: var(--sw-text-sm);
  font-weight: 700;
}

@media (max-width: 900px) {
  /* SHL-3. The bar's own chrome tightens below the shell breakpoint, and this is the ONLY
     lever that buys it room. Measured on the running site: `.brand` and `.user` both carry
     `min-width: 0`, so they are already squeezed to nothing at 320px and hiding either of
     them — or giving the wordmark an ellipsis — frees exactly ZERO. What the bar cannot
     shrink is its own `gap` and `padding` plus the `flex: none` theme select, so the
     threshold at which the document starts scrolling sideways is
     `221px + the badge's width`, against `256px + the badge's width` before this rule.
     That is what carries the UNSET default: `SWINGAGENT_PORTAL_ENVIRONMENT` blank means the
     badge reads `Production` (78px), which overflowed 320px by 14px and now clears it by 27.
     The label budget at 320px is 99px of badge — measured at a real 320px viewport by
     growing the label until `documentElement.scrollWidth` exceeded `clientWidth`, which is
     the only oracle for 1.4.10; never a per-element rect comparison. That is about thirteen
     characters of ordinary text, and `Staging-EU-West` (111px) is already 7px over.
     16px also aligns the bar's inset with `.app-main`'s below, which 24px did not.
     🛑 This rule lives INSIDE this block deliberately. `The_app_shell_is_pinned` reads "the
     one `.app-top` rule outside the narrow-viewport block", so a second breakpoint declaring
     `.app-top` would fork what that guard reads. `.app-side` is declared in both blocks for
     the same reason and the guard is written around it. */
  .app-top { gap: var(--sw-s2); padding: 0 var(--sw-s4); }
  /* `align-items: flex-start` governs the CROSS axis — which is the width once the
     direction flips to column. Without resetting it to stretch, the content column
     shrink-wraps to its max-content width and the whole page scrolls sideways
     (WCAG 1.4.10 Reflow). */
  .app-body { flex-direction: column; align-items: stretch; }
  .app-side {
    flex: none;
    width: 100%;
    /* Sticky under the bar rather than static, so the strip stays reachable on the long
       pages here too — but the desktop viewport-height box has to be undone or the
       horizontal nav strip becomes a full-viewport-height column with its links bunched
       at the top. `height` is the ONE declaration in this group that is load-bearing,
       and it is the one way this ships broken that a desktop-only check cannot see.
       The other two are stated rather than needed, and the reason is worth writing down
       once: `align-self: flex-start` leaks in harmlessly because `width: 100%` above
       already fixes the cross-axis size in a column container (it is here as insurance
       for whoever removes that), and `overflow-y` is a no-op because `overflow-x: auto`
       below already computes the other axis from `visible` to `auto` — with
       `height: auto` neither can produce a vertical scrollbar. */
    position: sticky;
    top: var(--sw-top-h);
    z-index: 20;
    height: auto;
    align-self: auto;
    overflow-y: auto;
    border-right: 0;
    border-bottom: 1px solid var(--sw-border);
    display: flex;
    gap: var(--sw-s1);
    overflow-x: auto;
    padding: var(--sw-s2) var(--sw-s3);
  }
  .nav-group { display: flex; gap: var(--sw-s1); margin: 0; }
  .nav-group > .nav-label { display: none; }
  .app-side a { white-space: nowrap; margin: 0; }
  .app-main { padding: var(--sw-s5) var(--sw-s4) var(--sw-s6); }
}

/* ---------- Page header + breadcrumb ------------------------------------- */

.crumbs {
  display: flex;
  align-items: center;
  gap: var(--sw-s2);
  margin-bottom: var(--sw-s3);
  font-size: var(--sw-text-sm);
  color: var(--sw-fg-muted);
}
.crumbs a { color: var(--sw-fg-muted); }
.crumbs a:hover { color: var(--sw-link); }
.crumbs .sep { color: var(--sw-n-400); }

.page-head {
  display: flex;
  align-items: flex-start;
  gap: var(--sw-s4);
  flex-wrap: wrap;
  padding-bottom: var(--sw-s4);
  margin-bottom: var(--sw-s5);
  border-bottom: 1px solid var(--sw-border);
}
.page-head > .page-titles { flex: 1 1 20rem; min-width: 0; }
.page-head .page-sub {
  margin: var(--sw-s2) 0 0;
  color: var(--sw-fg-muted);
  max-width: 68ch;
}
.page-actions { display: flex; align-items: center; gap: var(--sw-s2); flex-wrap: wrap; }
.page-actions form { margin: 0; }

/* ---------- Tenant tab bar ------------------------------------------------
   A tenant's seven surfaces are one object's facets, so they get one bar rather than
   seven hand-written cross-links. WRAPS rather than scrolls: seven items fit two rows
   at 320px, and a horizontally scrolling strip hides the tab you are looking for. NOT
   sticky - it would fight the pinned bar above and cost a second band of vertical
   space on the pages that need it least.

   Named `tab-bar`, which is what its consumer writes: DS-9a's own block called it
   `tabs`, but SHARED-1's partial, SHARED-1's tests and the plan's component list in
   §3.3 all say `tab-bar`. A component whose consumer writes the other name renders
   unstyled, which is the exact class-drift defect F-118 minted CLASSGUARD for. */
.tab-bar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sw-s1);
  margin: 0 0 var(--sw-s5);
  border-bottom: 1px solid var(--sw-border);
}
.tab-bar > a {
  display: inline-flex;
  align-items: center;
  gap: var(--sw-s2);
  padding: var(--sw-s2) var(--sw-s3);
  /* so the current tab's rule sits ON the container's border rather than under it */
  margin-bottom: -1px;
  border-bottom: 2px solid transparent;
  border-radius: var(--sw-r) var(--sw-r) 0 0;
  color: var(--sw-fg-muted);
  font-weight: 500;
  white-space: nowrap;
}
/* `--sw-surface-3`, NOT the `--sw-surface-2` SHARED-1's draft block reaches for: in LIGHT
   mode `--sw-surface-2` and `--sw-bg` resolve to the same value (both `--sw-n-50`), and
   this bar sits directly on the page background - so a surface-2 hover is invisible in
   exactly one of the two themes. */
.tab-bar > a:hover { background: var(--sw-surface-3); color: var(--sw-fg); text-decoration: none; }
/* Not colour alone (WCAG 1.4.1): the weight, the 2px rule and `aria-current` all carry it.
   The colour-and-weight pair is `.app-side a[aria-current]`'s, deliberately - the shell
   already has one current-state convention and this is the second surface to use it. */
.tab-bar > a[aria-current] {
  color: var(--sw-link);
  font-weight: 600;
  border-bottom-color: var(--sw-accent);
  background: var(--sw-surface);
}
/* The bar belongs directly under the page header, so the header gives up its gap. Silently
   inert if a consumer puts anything between the two. */
.page-head:has(+ .tab-bar) { margin-bottom: var(--sw-s3); }

/* ---------- Panels ------------------------------------------------------- */

.panel {
  background: var(--sw-surface);
  border: 1px solid var(--sw-border);
  border-radius: var(--sw-r-lg);
  box-shadow: var(--sw-shadow-sm);
  margin-bottom: var(--sw-s5);
}
.panel > .panel-head {
  display: flex;
  align-items: baseline;
  gap: var(--sw-s3);
  flex-wrap: wrap;
  padding: var(--sw-s4) var(--sw-s5);
  border-bottom: 1px solid var(--sw-border);
}
.panel > .panel-head > h2,
.panel > .panel-head > h3 { margin: 0; flex: 1 1 auto; }
/* A head subtitle sits WITH its heading; only a pill is pushed to the right edge.
   ⚠ DS-9a specifies `flex: 0 1 auto` for this, which is the flex INITIAL value and so
   cannot do what its own sentence says. What pushes a trailing child to the right edge is
   the HEADING's `flex: 1 1 auto` directly above, so a subtitle placed after it lands hard
   right beside the pill. The growth is handed to the subtitle instead, scoped with `:has()`
   so a head WITHOUT a subtitle is byte-for-byte unchanged. */
.panel > .panel-head:has(> .panel-sub) > h2,
.panel > .panel-head:has(> .panel-sub) > h3 { flex: 0 1 auto; }
.panel > .panel-head > .panel-sub {
  flex: 1 1 auto;
  margin: 0;
  color: var(--sw-fg-muted);
  font-size: var(--sw-text-sm);
}
.panel > .panel-body { padding: var(--sw-s5); }
.panel > .panel-body > :first-child { margin-top: 0; }
.panel > .panel-body > :last-child { margin-bottom: 0; }
.panel > .panel-foot {
  display: flex;
  align-items: center;
  gap: var(--sw-s2);
  flex-wrap: wrap;
  padding: var(--sw-s3) var(--sw-s5);
  border-top: 1px solid var(--sw-border);
  background: var(--sw-surface-2);
  border-radius: 0 0 var(--sw-r-lg) var(--sw-r-lg);
}
.panel > .panel-foot form { margin: 0; }
/* Save on the left, the folded-away destructive action on the right.
   ⚠ DS-9a also specifies `.panel > .panel-foot > .foot-end form { margin: 0; }`. Refuted
   and NOT shipped: the rule directly above is a DESCENDANT selector, so it already zeroes a
   form nested inside this box. A stricter second copy of a rule that is already in force is
   the dead declaration this tranche exists to remove. */
.panel > .panel-foot > .foot-end { margin-left: auto; }

/* /Platform/Settings is ONE form with ONE save, fifteen panels tall — so the save sat
   nine screens below the top of the page (F20). The bar is a direct child of the FORM,
   not of a panel: `position: sticky` is bounded by its containing block, and a bar
   inside its own short section would have no travel and would read as doing nothing.
   That is the DS-4 failure in `.app-side` above, from the other direction. It borrows
   `.panel-foot`'s look without being one — that rule is scoped `.panel > .panel-foot`,
   and this box is not inside a panel at all.

   `bottom`, not `top`: a top-pinned bar sticks UNDER `.app-top` and the operator loses
   the page heading instead of gaining the save.

   `z-index: 5` sits below every piece of sticky chrome in this sheet — `.app-side` is
   20, `.app-top` 30, `.skip-link` 200. The item that specified this said "below
   `.app-top`'s proposed 10 (F11)"; F11 has since landed at 30, so the conclusion holds
   and only the number was stale. It is still needed: the panels above the bar come
   EARLIER in the DOM, but their `box-shadow` and the focus rings of controls near a
   panel edge would otherwise paint over a bar with no stacking level of its own. */
.save-bar {
  position: sticky;
  bottom: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  gap: var(--sw-s3);
  flex-wrap: wrap;
  padding: var(--sw-s3) var(--sw-s5);
  background: var(--sw-surface-2);
  border: 1px solid var(--sw-border);
  border-radius: var(--sw-r-lg);
  box-shadow: var(--sw-shadow-lg);
  margin-bottom: var(--sw-s5);
}
/* The global `label` is `display: block` with a bottom margin, which in this flex row
   would push the control off the baseline. `flex: 0 1 auto` lets the sentence shrink
   before the input does. */
.save-bar label { margin: 0; flex: 0 1 auto; }
.save-bar-meta { color: var(--sw-fg-muted); font-size: var(--sw-text-sm); margin-left: auto; }
/* PS-4: a refused save re-renders in place and its message rides this bar, so the message is on
   screen at whatever scroll position the operator submitted from. It takes a whole flex LINE
   (`1 1 100%`) rather than a slot in the control row — the concurrency copy is a paragraph, and as
   an ordinary flex item it would squeeze the label, the input and the button onto slivers. The
   margin goes to zero because the bar already supplies its own padding and `gap`; the notice's own
   `margin-bottom` would otherwise open a gap below the row it is stacked on. */
.save-bar .notice,
.save-bar .error { flex: 1 1 100%; margin: 0; }
/* The FIELD message sits beside the control that refused, so it keeps its intrinsic width — and
   loses the `margin-top` that separates it from an input it is normally UNDER, because here it is
   next to one. */
.save-bar .field-validation-error { margin: 0; flex: 0 1 auto; }

/* A panel with no head/body/foot scaffolding — content padded directly. Lets an
   existing `<section>` become a panel without restructuring its children. */
.panel.panel-pad { padding: var(--sw-s5); }
.panel.panel-pad > :first-child { margin-top: 0; }
.panel.panel-pad > :last-child { margin-bottom: 0; }

/* A panel that is a table shell: the table draws its own edges. The table sits
   flush against the panel's border, and its own radius is stripped below, so the
   panel has to CLIP — otherwise the table's square corners paint straight over
   the panel's rounded ones. Scoped to `.panel-flush` rather than `.panel`: a
   padded panel holds form controls, and clipping there would crop the focus ring
   of a control near the edge. */
.panel.panel-flush { overflow: hidden; }
.panel.panel-flush > .panel-body { padding: 0; }
.panel.panel-flush table { margin: 0; border: 0; border-radius: 0; }
.panel.panel-flush table tr:last-child > * { border-bottom: 0; }

/* The signed-out card (login, 2FA, reset). Kept as `.card` — the Account
   pages already use it. */
.card {
  width: 100%;
  max-width: 400px;
  padding: var(--sw-s6);
  background: var(--sw-surface);
  border: 1px solid var(--sw-border);
  border-radius: var(--sw-r-lg);
  box-shadow: var(--sw-shadow-lg);
}
.card h1 { font-size: var(--sw-text-xl); margin-bottom: var(--sw-s5); }
.card .btn, .card button[type=submit] { width: 100%; justify-content: center; }

/* ---------- Forms -------------------------------------------------------- */

label {
  display: block;
  margin-bottom: var(--sw-s1);
  font-size: var(--sw-text-sm);
  font-weight: 600;
  color: var(--sw-fg);
}
/* An inline control label (checkbox, radio, Cc pruning) is a row, not a
   stacked field label. */
label:has(> input[type=checkbox]),
label:has(> input[type=radio]) {
  display: inline-flex;
  align-items: center;
  gap: var(--sw-s2);
  font-weight: 400;
  margin: 0;
  cursor: pointer;
  /* The box itself is 16px; the label is the real hit area, so give it the
     24px minimum (WCAG 2.2 AA 2.5.8 — advisory under 2.1, but nearly free). */
  min-height: 24px;
}
/* A label that WRAPS its text control — the only way to label a control that repeats per table
   row without minting colliding ids. Through the base rule's `font: inherit` the control took the
   label's 600 weight and small size, so the mailbox fields on /Tenants/Email rendered bold. It
   keeps body type, one step under the label text. (0,2,2) is what outranks `font: inherit`. */
label > :is(input:not([type=checkbox]):not([type=radio]), select, textarea) {
  display: block;
  margin-top: var(--sw-s1);
  font-weight: 400;
  font-size: var(--sw-text);
}
/* The file picker's own button is the one control on these pages the browser still drew in its
   default grey. It takes the secondary-button look; the file name beside it stays plain text.
   A file input's intrinsic width is sized for the browser's own smaller button, so the larger one
   left "No file chosen" ellipsized; it gets a stated width instead, capped by its container. */
input[type=file] { width: 24rem; max-width: 100%; }
input[type=file]::file-selector-button {
  margin-right: var(--sw-s3);
  padding: 0.4375rem var(--sw-s4);
  border: 1px solid var(--sw-border-input);
  border-radius: var(--sw-r);
  background: var(--sw-surface);
  color: var(--sw-fg);
  font: inherit;
  font-weight: 500;
  cursor: pointer;
}
input[type=file]::file-selector-button:hover { background: var(--sw-surface-3); }

/* Every text-entry type is listed. A type missing here keeps the browser default — a 21px
   monospace box with a grey 2px border next to 37px slabs — which is what the date fields on
   /Platform/Conversations and every date cell on /Platform/Settings looked like until the
   date, month and tel types joined. */
input[type=text], input[type=email], input[type=password], input[type=number],
input[type=url], input[type=search], input[type=tel], input:not([type]),
input[type=date], input[type=month], input[type=week], input[type=time],
input[type=datetime-local], textarea, select {
  display: block;
  width: 100%;
  padding: 0.4375rem var(--sw-s3);
  background: var(--sw-surface);
  color: var(--sw-fg);
  border: 1px solid var(--sw-border-input);
  border-radius: var(--sw-r);
  font: inherit;
  line-height: 1.45;
  box-shadow: var(--sw-shadow-sm);
  transition: border-color var(--sw-transition), box-shadow var(--sw-transition);
}
input::placeholder, textarea::placeholder { color: var(--sw-fg-subtle); }
/* Checkboxes and radios are left out of the hover and focus rules of the text fields (inside
   `:where()`, so each rule keeps the specificity it had): they have their own states below, and a field's
   focus ring on a mouse click would ring every box that was ticked. */
input:where(:not([type=checkbox]):not([type=radio])):hover:not(:disabled),
textarea:hover:not(:disabled), select:hover:not(:disabled) {
  border-color: var(--sw-border-strong);
}
input:where(:not([type=checkbox]):not([type=radio])):focus, textarea:focus, select:focus {
  /* `transparent`, NOT `none`. A control's focus signal here is the box-shadow ring,
     but Windows High Contrast does not paint box-shadow and overrides border colours,
     so `outline: none` left EVERY input, select and textarea in the portal with no
     focus indicator at all — including every control on the settings form, which is
     by a wide margin the largest form here. A transparent outline is invisible in
     normal rendering and is repainted by the forced-colors block at the end of this
     sheet. */
  outline: 2px solid transparent;
  outline-offset: 0;
  /* Restated: the portal-wide :focus-visible rule sets border-radius: --sw-r-sm,
     which otherwise shrinks a focused control's corners from 6px to 4px. */
  border-radius: var(--sw-r);
  border-color: var(--sw-focus);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--sw-focus) 22%, transparent);
}
input:disabled, textarea:disabled, select:disabled {
  background: var(--sw-surface-3);
  color: var(--sw-fg-subtle);
  cursor: not-allowed;
}
/* Checkboxes and radios are drawn here rather than by the browser, so they wear the fields' border,
   corner and shadow instead of the browser's 13px box tinted navy. The tick and the dot are
   pseudo-elements drawn with borders and a fill: the CSP (`default-src 'self'`) blocks a `data:`
   image, so there is no SVG to lean on. Keyboard focus is the portal-wide `:focus-visible` ring,
   and forced-colors hands both controls back to the system at the end of this sheet. */
input[type=checkbox], input[type=radio] {
  appearance: none;
  display: inline-grid;
  place-content: center;
  width: 1rem;
  height: 1rem;
  margin: 0;
  flex: none;
  /* Centred on the text line where the control is not in a flex label (a table cell). `middle`,
     not a length: a grid's baseline is its tick's or dot's, which sit at different heights, so a
     length put a checkbox 3.5px under the text beside it and a radio lower still. */
  vertical-align: middle;
  background: var(--sw-surface);
  border: 1px solid var(--sw-border-input);
  border-radius: var(--sw-r-sm);
  box-shadow: var(--sw-shadow-sm);
  cursor: pointer;
  transition: background-color var(--sw-transition), border-color var(--sw-transition);
  /* "On" is a fill, and a browser drops fills when it prints unless told otherwise: a ticked box
     printed empty and a chosen radio lost its dot. Inherited, so the tick and the dot follow. */
  print-color-adjust: exact;
}
input[type=radio] { border-radius: 50%; }
input:is([type=checkbox], [type=radio]):hover:not(:disabled) { border-color: var(--sw-check); }
input:is([type=checkbox], [type=radio]):checked {
  background: var(--sw-check);
  border-color: var(--sw-check);
}
input:is([type=checkbox], [type=radio]):checked:hover:not(:disabled) {
  background: var(--sw-check-hover);
  border-color: var(--sw-check-hover);
}
/* The tick: the right and bottom borders of a narrow box, turned 45deg. Its strokes sit low in
   that box, so it is lifted a pixel to look centred. */
input[type=checkbox]::before {
  content: "";
  width: 0.3125rem;
  height: 0.5625rem;
  border: solid var(--sw-fg-on-accent);
  border-width: 0 2px 2px 0;
  transform: translateY(-1px) rotate(45deg) scale(0);
  transition: transform var(--sw-transition);
}
input[type=checkbox]:checked::before { transform: translateY(-1px) rotate(45deg) scale(1); }
input[type=radio]::before {
  content: "";
  width: 0.375rem;
  height: 0.375rem;
  border-radius: 50%;
  background: var(--sw-fg-on-accent);
  transform: scale(0);
  transition: transform var(--sw-transition);
}
input[type=radio]:checked::before { transform: scale(1); }
/* A control that cannot change: a flat grey well when off, a grey fill when on (`--sw-check-locked`),
   so it still reads as on without wearing the colour of a control the reader can use. The Email
   page's hint ("the grey ones are always ticked") relies on that grey. */
input:is([type=checkbox], [type=radio]):disabled {
  background: var(--sw-surface-3);
  border-color: var(--sw-border-strong);
  box-shadow: none;
  cursor: not-allowed;
}
input:is([type=checkbox], [type=radio]):disabled:checked {
  background: var(--sw-check-locked);
  border-color: var(--sw-check-locked);
}
input[type=checkbox]:disabled::before { border-color: var(--sw-check-locked-mark); }
input[type=radio]:disabled::before { background: var(--sw-check-locked-mark); }
label:has(> input:disabled) { cursor: not-allowed; }

/* Chromium pads the date fields' inner wrapper by 1px top and bottom, so a date control
   came out 2px taller than the text input beside it (38.3px against 36.3px, measured).
   WebKit-only selectors sit in rules of their own: one unknown pseudo-element in a
   selector list makes Firefox drop the whole rule, shared selectors included. */
input::-webkit-datetime-edit,
input::-webkit-datetime-edit-fields-wrapper { padding: 0; }
input::-webkit-calendar-picker-indicator { cursor: pointer; }

/* An explicit `size` is the page author stating how wide the content actually is
   — a turn cap is 8 characters, not a full row. Honour it rather than stretching
   the control, but never let it overflow its container. */
input[size] { width: auto; max-width: 100%; }

/* Field width scale. max-width, NOT `size`: `size` is inert on type=number, reaches no
   input type but text/search/url/tel/email/password, and on a <select> sets ROWS rather
   than width — so a scale built on it can only tune text inputs. Which is why the three
   catalogue selects on /Platform/Settings measured 38/50/66px while a five-character
   effort select on the same page measured 940px. Pick the step that fits the CONTENT:
     w-xs   4-6 chars   a company id, a 2-digit cap, max results
     w-sm   7-10        a turn cap, a price, a date, a percentage
     w-md   11-18       a tenant slug, a language list, a short display name
     w-lg   19-30       a model id, an email address, a public widget key
     w-xl   31-52       a URL, a base URL, a note
     w-full             explicit reset back to the full row

   ORDER IS LOAD-BEARING: this block MUST stay BELOW `input[size]` above. A bare `.w-sm`
   scores (0,1,0) and LOSES to `input[size]`'s (0,1,1), so every step here is
   element-qualified to score (0,1,1) as well — a tie, and a tie is broken by source
   order and nothing else. Move this above `input[size]` and every step stops applying to
   any input that still carries a `size` attribute — which is every sized input in the
   portal, i.e. exactly the controls each page cluster is about to convert. No rule is
   dropped, nothing fails to parse, nothing goes red. That is what
   The_width_scale_outranks_the_size_attribute pins. */
:is(input, select, textarea):is(.w-xs, .w-sm, .w-md, .w-lg, .w-xl, .w-full) { width: 100%; min-width: 0; }
:is(input, select, textarea).w-xs   { max-width: calc(6ch  + var(--sw-field-chrome)); }
:is(input, select, textarea).w-sm   { max-width: calc(10ch + var(--sw-field-chrome)); }
:is(input, select, textarea).w-md   { max-width: calc(18ch + var(--sw-field-chrome)); }
:is(input, select, textarea).w-lg   { max-width: calc(30ch + var(--sw-field-chrome)); }
:is(input, select, textarea).w-xl   { max-width: calc(52ch + var(--sw-field-chrome)); }
:is(input, select, textarea).w-full { max-width: 100%; }

/* A <select> in a table cell is squeezed by its column, and a squeezed select shows
   nothing at all — not a truncated value, nothing. Its widest option is its real
   minimum. A `.w-*` step still overrides this, which it has to: min-width always beats
   max-width, so the shared `min-width: 0` above must reach the select or a long-optioned
   one would ignore its cap. It does — on SPECIFICITY, (0,1,1) against this rule's
   (0,0,2), not merely on source order. */
td select { width: auto; min-width: max-content; }
/* A date control is squeezed the same way, and has no `size` to state its width: its
   fields and picker button ARE its width, so in a cell it takes exactly that. (0,1,2)
   outranks the full-row width of the base rule above. */
td :is(input[type=date], input[type=month], input[type=week], input[type=time],
       input[type=datetime-local]) { width: auto; }

textarea { resize: vertical; min-height: 5rem; }
textarea[name=instructions], textarea[name=body] { font-family: var(--sw-font-mono); }

/* `.field` groups a label, a control and its help text so vertical rhythm
   stops depending on a `<p><button></p>` wrapper. */
.field { margin-bottom: var(--sw-s4); }
/* `.card .hint` rather than a `>`: the signed-out card's forms are plain label/input pairs
   with no `.field` wrapper, so the hint under its submit button is a grandchild. Until SHL-3
   put one there, `.hint` had no consumer anywhere in the portal. */
.field > .hint,
.field > small,
.card .hint,
.actions + .hint {
  display: block;
  margin-top: var(--sw-s1);
  font-size: var(--sw-text-sm);
  color: var(--sw-fg-muted);
  max-width: 74ch;
}
/* A hint directly under a row of buttons explains what they do — the CSV note under
   Download CSV. Without it that note ran the full panel width tight against the row. */
.actions + .hint { margin-top: var(--sw-s2); }
.field-row {
  display: flex;
  align-items: flex-start;
  gap: var(--sw-s2);
  flex-wrap: wrap;
}
.field-row > input, .field-row > select { flex: 1 1 16rem; width: auto; }
.field-row > .btn, .field-row > button { flex: none; }
.field-grid {
  display: grid;
  gap: var(--sw-s4);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
}
/* Compact fields packed to the left, each as wide as its control — a filter bar.
   `.field-grid` gives every field an equal column, so a capped control floats in a wide
   one and two grids with different field counts never line up; that is what made the
   /Platform/Conversations filters read as scattered. Bottom-aligned so the controls share
   a line however their labels wrap. */
.field-inline {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--sw-s3) var(--sw-s4);
}
.field-inline > .field { margin-bottom: 0; min-width: 0; }
/* Fields packed left whose hints differ: their controls share the top line instead (the CL7 walk: in a .field-grid the folder
   field floated mid-panel, away from the category beside it). */
.field-inline.field-inline-top { align-items: flex-start; }
/* A word between two fields ("or"). The control's line-height, and its padding plus its
   1px border, put the word on the controls' text line. */
.field-inline-sep {
  padding-block: calc(0.4375rem + 1px);
  line-height: 1.45;
  color: var(--sw-fg-muted);
}
.check-row { display: flex; flex-wrap: wrap; gap: var(--sw-s2) var(--sw-s5); }

fieldset { border: 0; margin: 0; padding: 0; }
/* A group of controls inside a form (checkboxes, radios): its legend labels the group the way a label labels one control,
   in the label's size, weight and left edge. The CL7 walk found the browser's own legend lighter than the labels around it
   and indented by its default padding (the Agent page's languages, the Website page's chat memory, the Email page's groups). */
fieldset.field > legend {
  padding: 0;
  margin-bottom: var(--sw-s1);
  font-size: var(--sw-text-sm);
  font-weight: 600;
  color: var(--sw-fg);
}
/* A group of LABELLED fields (the Email page's "When a reply is sent"): its title reads above its fields' own labels. */
fieldset.field-group > legend {
  margin-bottom: var(--sw-s2);
  font-size: var(--sw-text);
}
/* A fieldset that is a whole SECTION of a long form names it with its legend, so the legend wears
   the h3 it stands in for: /Tenants/Email's mailbox-handling block sat between h3 sections with a
   legend in body type. A class rather than `form > fieldset`, which would also reach two of the
   knowledge row editor's legends and leave its other two on their own, smaller rule. */
fieldset.form-section > legend {
  padding: 0;
  margin: var(--sw-s5) 0 var(--sw-s2);
  font-size: var(--sw-text-md);
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.011em;
  color: var(--sw-fg);
}

/* ---------- Buttons -----------------------------------------------------
   Bare `<button>` is now the SECONDARY (neutral) style, not primary. Every
   button in the portal used to be the same saturated blue, which made
   "Purge permanently" visually identical to "Rename". Intent is opt-in:
   .btn-primary for the one affirmative action, .btn-danger for destruction. */

.btn, button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sw-s2);
  padding: 0.4375rem var(--sw-s4);
  background: var(--sw-surface);
  color: var(--sw-fg);
  border: 1px solid var(--sw-border-strong);
  border-radius: var(--sw-r);
  box-shadow: var(--sw-shadow-sm);
  font: inherit;
  font-weight: 500;
  line-height: 1.45;
  /* Long labels ("Clear settings (turn availability off)") must wrap rather than
     overflow a narrow container — nowrap here broke reflow at 320px. */
  white-space: normal;
  max-width: 100%;
  cursor: pointer;
  text-decoration: none;
  transition: background var(--sw-transition), border-color var(--sw-transition),
              color var(--sw-transition);
}
.btn:hover, button:hover {
  background: var(--sw-surface-3);
  border-color: var(--sw-n-400);
  color: var(--sw-fg);
  text-decoration: none;
}
.btn:active, button:active { background: var(--sw-border); }
.btn:disabled, button:disabled {
  background: var(--sw-surface-3);
  color: var(--sw-fg-subtle);
  border-color: var(--sw-border);
  box-shadow: none;
  cursor: not-allowed;
}

.btn-primary, button.btn-primary {
  background: var(--sw-accent);
  border-color: var(--sw-accent);
  color: var(--sw-fg-on-accent);
}
.btn-primary:hover, button.btn-primary:hover {
  background: var(--sw-accent-hover);
  border-color: var(--sw-accent-hover);
  color: var(--sw-fg-on-accent);
}
.btn-primary:active, button.btn-primary:active { background: var(--sw-accent-active); }

.btn-danger, button.btn-danger {
  background: var(--sw-surface);
  border-color: var(--sw-danger-border);
  color: var(--sw-danger-fg);
}
.btn-danger:hover, button.btn-danger:hover {
  background: var(--sw-danger-bg);
  border-color: var(--sw-danger-fg);
  color: var(--sw-danger-fg);
}
/* The irreversible ones (purge, offboard) get the filled treatment. */
.btn-danger-solid, button.btn-danger-solid {
  background: var(--sw-red-600);
  border-color: var(--sw-red-600);
  color: var(--sw-white);
}
.btn-danger-solid:hover, button.btn-danger-solid:hover {
  background: var(--sw-red-700);
  border-color: var(--sw-red-700);
  color: var(--sw-white);
}

.btn-ghost, button.btn-ghost {
  background: transparent;
  border-color: transparent;
  box-shadow: none;
  color: var(--sw-fg-muted);
}
.btn-ghost:hover, button.btn-ghost:hover {
  background: var(--sw-surface-3);
  color: var(--sw-fg);
  border-color: transparent;
}

.btn-sm, button.btn-sm {
  padding: 0.1875rem var(--sw-s2);
  font-size: var(--sw-text-sm);
  border-radius: var(--sw-r-sm);
}

/* COPY (F47) - a value you can copy. BOTH of these are injected by wwwroot/js/copy.js,
   immediately after the value they belong to, so with no script there is no control at
   all and the page is exactly what it was. Nothing here is ever authored in Razor: a
   control with no script behind it is a control that lies.

   NO `margin-top: 0` reset. The rule above that gives a following button a top margin
   fires only next to an input/select/textarea, and every call site is a `code` or a
   `pre`, so it never matches - measured 2026-08-26 rather than guarded against.

   `.copy-status` is MUTED, not green. One element carries both the confirmation and the
   press-Ctrl+C instruction, so a hue would be a lie on one of the two; the words carry
   the state, which is what WCAG 1.4.1 asks for regardless. */
.copy-btn { margin-left: var(--sw-s2); vertical-align: middle; }
.copy-status {
  margin-left: var(--sw-s2);
  font-size: var(--sw-text-sm);
  color: var(--sw-fg-muted);
}

/* `<form style="display:inline">` appears 8+ times so adjacent submit buttons
   sit on one line. `.actions` does it once, with real gaps and alignment. */
.actions {
  display: flex;
  align-items: center;
  gap: var(--sw-s2);
  flex-wrap: wrap;
  margin-top: var(--sw-s4);
}
.actions > form { margin: 0; display: contents; }
.actions--end { justify-content: flex-end; }

/* Row-level actions: a one-button form inside a table cell or list row should sit
   beside its siblings, not stack. This is what the old `display:inline` attributes
   were reaching for — and they never applied, because the CSP dropped them. */
td > form, li > form { display: inline-block; margin: 0 var(--sw-s1) 0 0; vertical-align: middle; }
td > form:last-child, li > form:last-child { margin-right: 0; }

/* A button stacked directly beneath a full-width control needs vertical rhythm.
   `.actions` supplies it when the button is wrapped, but a bare adjacent sibling
   — <input> then <button> inside a form — had none, so the two touched.
   A hidden input occupies no space, so it must not trigger a gap.

   The negations sit inside `:where()` so they contribute NO specificity — chained
   `:not()`s would score (0,3,2) and beat the `.field-row` / `.actions` resets
   below, re-breaking the very rows those resets exist to protect. */
input:where(:not([type=hidden]):not([type=checkbox]):not([type=radio])) + button,
input:where(:not([type=hidden]):not([type=checkbox]):not([type=radio])) + .btn,
select + button, select + .btn,
textarea + button, textarea + .btn {
  margin-top: var(--sw-s3);
}
/* ...except where the control and the button deliberately share a row. `.actions`
   lifts a form's children into the row via display:contents, so its buttons are
   grandchildren. */
.field-row > button, .field-row > .btn,
.actions > button, .actions > .btn,
.actions > form > button, .actions > form > .btn,
.chat-input > button, .chat-input > .btn {
  margin-top: 0;
}

/* ---------- Tables ------------------------------------------------------- */

table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  margin: var(--sw-s4) 0;
  background: var(--sw-surface);
  border: 1px solid var(--sw-border);
  border-radius: var(--sw-r-lg);
  overflow: hidden;
  font-variant-numeric: tabular-nums;
}
thead th {
  background: var(--sw-surface-2);
  font-size: var(--sw-text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--sw-fg-muted);
  white-space: nowrap;
}
th, td {
  text-align: left;
  padding: var(--sw-s3) var(--sw-s4);
  border-bottom: 1px solid var(--sw-border);
  vertical-align: top;
}
tbody tr:last-child > * { border-bottom: 0; }
tbody tr:hover > td { background: var(--sw-surface-inset); }
td .actions { margin: 0; }
/* A column of quantities reads down its ones digit: right-aligned, so 21,590 and 337,256 line up by
   magnitude instead of by first digit. The header cell takes the class too, so the label sits over
   the numbers it names. `table` already sets tabular figures. */
th.num, td.num { text-align: right; }
/* A long machine value in a cell — an Exchange X.500 sender, a JSON audit detail, a filesystem path.
   Unbroken, its full width became the column's MINIMUM, and the table paid for it by squeezing the
   prose column beside it to one word per line (the park log's "Why", measured at 117px beside a
   529px "From"). It still wraps only where it must. */
td.wrap-anywhere { overflow-wrap: anywhere; min-width: 10rem; }
/* Restated on the code pill: it declares its own `break-word`, which does not lower a column's
   minimum width, and its own value beats the one inherited from the cell. */
td.wrap-anywhere code { overflow-wrap: anywhere; }

/* KB-1: a row's editors live in a second row under it — `<tr class="row-edit">`, one cell
   spanning the table, a <details> inside — so the row above stays one line of facts instead of
   three forms and a dozen inputs per cell. The two rows read as one item: no rule between them,
   the hover tint stays on the facts, and a CLOSED disclosure is a quiet link aligned with the
   cell text rather than a box under every row. The <details> must BE the second row's content:
   opening one from a link elsewhere in the row is not reliable across browsers. */
tbody tr:has(+ tr.row-edit) > td { border-bottom: 0; }
tbody tr.row-edit > td { padding-top: 0; background: none; }
tbody tr.row-edit > td > details { margin: 0; }
/* Below the facts row's minimum width the table scrolls sideways — acceptable for a row of facts
   (WCAG 1.4.10 exempts data tables), not for an editor's sentences and fields, which would then
   stretch to the full table width (measured: 764px of text inside a 293px box at phone width). The
   editor is held to the VISIBLE part of the scroll box instead: no wider than it, and sticky, so it
   stays in view while the row above is scrolled. Scoped by :has() to tables that carry editors, so
   no other scroll box becomes a size container. */
.table-scroll:has(tr.row-edit) { container-type: inline-size; }
/* `clip`, not the table's usual `hidden`: both clip the rounded corners, but `hidden` makes the
   table a scroll container, which captures the sticky editor and stops it sticking. */
.table-scroll:has(tr.row-edit) > table { overflow: clip; }
tbody tr.row-edit > td > details {
  position: sticky;
  left: var(--sw-s4);
  max-width: calc(100cqi - 2 * var(--sw-s4) - 2px);
}
tbody tr.row-edit > td > details:not([open]) { border-color: transparent; background: none; }
tbody tr.row-edit > td > details:not([open]) > summary { padding: var(--sw-s1) 0; background: none; }
tbody tr.row-edit > td > details:not([open]) > summary:hover { text-decoration: underline; }

/* The editor inside: groups flow into as many columns as fit, each a fieldset with a legend so
   a screen reader hears which document and which group a control belongs to. A fieldset's
   default `min-width: min-content` would refuse to shrink inside a grid track. */
.row-editor {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 26rem), 1fr));
  gap: var(--sw-s5) var(--sw-s6);
}
.row-editor fieldset { min-width: 0; }
.row-editor legend {
  padding: 0;
  margin-bottom: var(--sw-s3);
  font-weight: 600;
  color: var(--sw-fg);
}
.row-editor .field-grid { grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr)); }
/* A full-width group's fields come out three to a row at desktop width — six metadata fields as
   two rows of three rather than five and one. */
.row-editor .row-editor-wide .field-grid { grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr)); }
.row-editor .field-grid .field { margin-bottom: 0; }
.row-editor .actions { margin-top: var(--sw-s3); }
.row-editor-wide { grid-column: 1 / -1; }
.version-list { margin: 0 0 var(--sw-s4); padding: 0; list-style: none; }
.version-list > li { padding: var(--sw-s2) 0; border-bottom: 1px solid var(--sw-border); }
.version-list > li:first-child { padding-top: 0; }
.version-list > li:last-child { padding-bottom: 0; border-bottom: 0; }
.version-list .actions { margin-top: var(--sw-s2); }
/* An ingest failure is a sentence, not a notice box: it sits under the version it belongs to. */
.ingest-error {
  display: block;
  margin-top: var(--sw-s1);
  max-width: 60ch;
  font-size: var(--sw-text-sm);
  color: var(--sw-danger-fg);
  overflow-wrap: anywhere;
}

/* M4.2: rows the page PREFILLED rather than rows the operator entered — the model-catalogue
   suggestions on Platform/Settings. The tint marks the block at a glance; it is never the only
   signal (WCAG 1.4.1), because the block opens with a header row that says what it is.
   Only the two declarations that actually change something: `thead th` does not match a th inside
   tbody, so the uppercase/tracking treatment was never going to apply to it — the bold from the
   browser's own th default is. */
tbody tr.row-suggested > td,
tbody tr.row-suggested > th { background: var(--sw-info-bg); }
tbody tr.row-suggested > th { font-weight: 400; }

/* Key/value shape: `<table><tr><th>Status</th><td>…</td></tr>` with no thead.
   The row-header column becomes a quiet label instead of a shouting header. */
table:not(:has(thead)) th {
  width: 1%;
  min-width: 11rem;
  background: transparent;
  font-size: var(--sw-text);
  font-weight: 500;
  text-transform: none;
  letter-spacing: 0;
  color: var(--sw-fg-muted);
}

/* Wide tables scroll inside their own box; the page never scrolls sideways. */
.table-scroll { overflow-x: auto; margin: var(--sw-s4) 0; }
.table-scroll > table { margin: 0; }

/* On a narrow viewport a key/value sheet stacks instead of scrolling. An 11rem
   label column beside a control cannot fit 320px, and Platform/Settings builds
   its whole form this way — stacking reflows it (WCAG 1.4.10); sideways scrolling
   a form would technically pass but is miserable to fill in. */
@media (max-width: 600px) {
  table:not(:has(thead)),
  table:not(:has(thead)) tbody,
  table:not(:has(thead)) tr,
  table:not(:has(thead)) th,
  table:not(:has(thead)) td { display: block; width: auto; }
  table:not(:has(thead)) th {
    min-width: 0;
    border-bottom: 0;
    padding-bottom: 0;
    font-weight: 600;
    color: var(--sw-fg);
  }
  table:not(:has(thead)) td { padding-top: var(--sw-s1); }
  table:not(:has(thead)) tr { border-bottom: 1px solid var(--sw-border); }
  table:not(:has(thead)) tr:last-child { border-bottom: 0; }
}

/* ---------- Badges ------------------------------------------------------
   Replaces the ad-hoc `<mark style="background:#fecaca">` / `#fde68a` /
   `#dbeafe` spread across Detail and EmailQueue — six hard-coded light-mode
   fills that were unreadable under a dark theme. */

.badge, mark {
  display: inline-flex;
  align-items: center;
  gap: var(--sw-s1);
  padding: 0.0625rem var(--sw-s2);
  border: 1px solid var(--sw-border-strong);
  border-radius: var(--sw-r-full);
  background: var(--sw-surface-3);
  color: var(--sw-fg-muted);
  font-size: var(--sw-text-xs);
  font-weight: 600;
  line-height: 1.6;
  white-space: nowrap;
  vertical-align: baseline;
}
.badge-ok     { background: var(--sw-ok-bg);     color: var(--sw-ok-fg);     border-color: var(--sw-ok-border); }
.badge-warn   { background: var(--sw-warn-bg);   color: var(--sw-warn-fg);   border-color: var(--sw-warn-border); }
.badge-danger { background: var(--sw-danger-bg); color: var(--sw-danger-fg); border-color: var(--sw-danger-border); }
.badge-info   { background: var(--sw-info-bg);   color: var(--sw-info-fg);   border-color: var(--sw-info-border); }
.badge-neutral{ background: var(--sw-surface-3); color: var(--sw-fg-muted);  border-color: var(--sw-border-strong); }

/* A leading dot reads as status at a glance in a dense table. */
.badge-dot::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}

/* ---------- Notices ----------------------------------------------------
   `.message` was USED by EmailQueue and Detail but never defined in the old
   stylesheet, so those confirmations rendered as unstyled body text. All four
   intents are defined here, and the legacy class names still resolve. */

/* Every intent is in this list, not just `.notice` and `.notice-ok`. The colour modifiers below
   declare a background, a border COLOUR and a text colour and nothing else, so while this list named
   only two of them there were two ways to write one intent: `notice notice-warn` painted the real
   component, and a standalone `notice-warn` painted a coloured strip of unpadded text with no left
   rule. Nothing said which one you had written. `.notice-danger` and `.notice-success` have no call
   site today; they are here so the set is closed, and because the severity-mapping work needs them.
   Guarded by DesignSystemGuardTests.Every_notice_intent_modifier_also_gets_the_notice_box. */
.notice, .notice-ok, .notice-warn, .notice-info, .notice-danger, .notice-success,
.message, .error, .validation-summary-errors {
  /* Block, not flex. A notice is prose, and as a flex container its anonymous text
     runs become flex items with `min-width: auto` — they then refuse to shrink below
     their min-content width and push the page sideways at 320px. There is no icon
     slot to justify the flex box. */
  display: block;
  padding: var(--sw-s3) var(--sw-s4);
  overflow-wrap: break-word;
  margin: 0 0 var(--sw-s4);
  border: 1px solid var(--sw-border);
  border-left-width: 3px;
  border-radius: var(--sw-r);
  background: var(--sw-surface);
  color: var(--sw-fg);
  font-size: var(--sw-text);
}
/* Widened with :is() so the margin collapse follows the box rather than staying on `.notice`
   alone — otherwise a multi-paragraph `.notice-warn` gets a stray top margin inside its new box.
   The :is() contributes (0,1,0) — it takes the specificity of its most specific argument and every
   argument here is a class — which is exactly what the bare `.notice` contributed, so the whole
   selector stays where it was. */
:is(.notice, .notice-ok, .notice-warn, .notice-info, .notice-danger, .notice-success,
    .message, .error) > :first-child { margin-top: 0; }
:is(.notice, .notice-ok, .notice-warn, .notice-info, .notice-danger, .notice-success,
    .message, .error) > :last-child { margin-bottom: 0; }

.notice-ok, .message, .notice-success {
  background: var(--sw-ok-bg);
  border-color: var(--sw-ok-border);
  color: var(--sw-ok-fg);
}
.error, .validation-summary-errors, .notice-danger {
  background: var(--sw-danger-bg);
  border-color: var(--sw-danger-border);
  color: var(--sw-danger-fg);
}
.notice-warn { background: var(--sw-warn-bg); border-color: var(--sw-warn-border); color: var(--sw-warn-fg); }
.notice-info { background: var(--sw-info-bg); border-color: var(--sw-info-border); color: var(--sw-info-fg); }
/* Empty summaries must not paint an empty coloured bar. The tag helper emits a
   `validation-summary-valid` container holding one `<li style="display:none">`
   placeholder; the CSP discards that inline hide, so the empty bullet is styled
   away here rather than relying on an attribute that never applies. */
.validation-summary-valid, .validation-summary-errors:empty { display: none; }
.validation-summary-errors ul { margin: 0; padding-left: var(--sw-s5); }
.validation-summary-errors li:empty { display: none; }

/* Field-level validation stays inline text, not a banner. */
.field-validation-error {
  display: block;
  margin-top: var(--sw-s1);
  font-size: var(--sw-text-sm);
  font-weight: 500;
  color: var(--sw-danger-fg);
  background: none;
  border: 0;
  padding: 0;
}
input.input-validation-error, textarea.input-validation-error {
  border-color: var(--sw-danger-fg);
}

/* ---------- Code / payload blocks --------------------------------------
   Five inline variants existed (#f7f7f7, #f0f0f0, differing padding). One
   component, and it is the surface untrusted inbound email text renders into
   — always Razor-encoded, never Html.Raw. */

pre, .code-block {
  margin: var(--sw-s2) 0 var(--sw-s4);
  padding: var(--sw-s3) var(--sw-s4);
  background: var(--sw-surface-inset);
  border: 1px solid var(--sw-border);
  border-radius: var(--sw-r);
  color: var(--sw-fg);
  white-space: pre-wrap;
  overflow-wrap: break-word;
  overflow-x: auto;
  max-height: 28rem;
  overflow-y: auto;
  line-height: 1.5;
}
pre > code, .code-block > code {
  background: none;
  border: 0;
  padding: 0;
  white-space: inherit;
}
.code-block--quiet { background: var(--sw-surface-2); color: var(--sw-fg-muted); }

/* ---------- Meta rows --------------------------------------------------
   Replaces `<strong>From:</strong> x | <strong>Received:</strong> y` — the
   pipe-separated runs in EmailQueue that wrap unpredictably. */

.meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sw-s2) var(--sw-s5);
  margin: 0 0 var(--sw-s3);
  padding: 0;
  list-style: none;
  font-size: var(--sw-text-sm);
}
.meta > div, .meta > li { display: flex; gap: var(--sw-s2); min-width: 0; }
.meta dt, .meta .meta-k {
  color: var(--sw-fg-subtle);
  font-weight: 500;
  white-space: nowrap;
}
.meta dd, .meta .meta-v { margin: 0; color: var(--sw-fg); min-width: 0; overflow-wrap: anywhere; }

/* ---------- Disclosure -------------------------------------------------- */

details {
  margin: var(--sw-s3) 0;
  border: 1px solid var(--sw-border);
  border-radius: var(--sw-r);
  background: var(--sw-surface);
  overflow: hidden;
}
details > summary {
  padding: var(--sw-s2) var(--sw-s4);
  cursor: pointer;
  font-weight: 500;
  font-size: var(--sw-text-sm);
  color: var(--sw-link);
  background: var(--sw-surface-2);
  list-style-position: inside;
}
details > summary:hover { background: var(--sw-surface-3); }
/* Inset: `details` clips with `overflow: hidden`, so the portal's 2px OUTWARD focus ring was cut
   off on every summary — the only focus cue a closed disclosure has. */
details > summary:focus-visible { outline-offset: -2px; }
details[open] > summary { border-bottom: 1px solid var(--sw-border); }
details > :not(summary) { margin-left: var(--sw-s4); margin-right: var(--sw-s4); }
details > :not(summary):first-of-type { margin-top: var(--sw-s3); }
details > :not(summary):last-child { margin-bottom: var(--sw-s3); }
details > ol, details > ul { padding-left: var(--sw-s5); }

/* ---------- Retrieval preview ------------------------------------------- */

/* DS-9c (F48/F51). These two selectors are the ones the PRODUCT has used since K1 and the design
   system never declared: Knowledge.cshtml renders `.preview-result` with a `<blockquote>` inside it
   and neither resolved to a rule, so every retrieval-preview excerpt has rendered as unstyled body
   text. A class census is what found them; a page walk cannot, because unstyled text looks like a
   choice.
   ⚠ DS-9c as drafted also minted `.disclosure`, `.disclosure-danger` and `td > details.details-row`.
   All three were CUT before execution (operator decision, phase-06 head): their consumers were cut
   or already shipped on the default card, and `details-row` contradicted the two phase-10 items that
   would have used it. `F45` is recorded UNCLOSED as a result. No guard can see an unused selector —
   CLASSGUARD proves a class USED resolves to a rule, never that a rule has a user — so an orphan
   here would have been permanent and invisible. */
.preview-result {
  margin: 0 0 var(--sw-s3);
  padding: var(--sw-s3) var(--sw-s4);
  background: var(--sw-surface-2);
  border: 1px solid var(--sw-border);
  border-radius: var(--sw-r);
}
.preview-result > p:first-child { margin-bottom: var(--sw-s2); }

/* `border:` shorthand, NOT `border-left-width` + `border-radius` in one block: that exact pairing is
   asserted to occur EXACTLY ONCE in this sheet (the notice base box, :1264), and a second occurrence
   turns that guard red with a message about notice boxes that reads as unrelated to this change. */

/* Bare element selector, deliberately. Knowledge.cshtml's excerpt is the only blockquote the portal
   renders today, so scoping this under `.preview-result` would leave the next one unstyled — and the
   next one is the likelier case, since a blockquote is what anyone reaches for to show quoted source
   text. `overflow-wrap` because the excerpt is UNTRUSTED corpus text: a 45-character unbreakable file
   name is what made KB-2's fold 328px wide, and an excerpt can carry the same. */
blockquote {
  margin: 0;
  padding-left: var(--sw-s3);
  border-left: 3px solid var(--sw-border-strong);
  color: var(--sw-fg-muted);
  overflow-wrap: break-word;
}

/* ---------- Lists ------------------------------------------------------- */

ul, ol { margin: 0 0 var(--sw-s3); padding-left: var(--sw-s5); }
li { margin: var(--sw-s1) 0; }

/* Origins-style list: a value plus a per-row action, aligned. */
.list-rows { list-style: none; padding: 0; margin: 0 0 var(--sw-s4); }
.list-rows > li {
  display: flex;
  align-items: center;
  gap: var(--sw-s3);
  padding: var(--sw-s2) 0;
  margin: 0;
  border-bottom: 1px solid var(--sw-border);
}
.list-rows > li:last-child { border-bottom: 0; }
.list-rows > li > :first-child { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.list-rows > li form { margin: 0; }

/* ---------- Empty state ------------------------------------------------- */

.empty {
  padding: var(--sw-s6) var(--sw-s5);
  border: 1px dashed var(--sw-border-strong);
  border-radius: var(--sw-r-lg);
  text-align: center;
  color: var(--sw-fg-muted);
  background: var(--sw-surface-inset);
}
.empty > h3 { margin: 0 0 var(--sw-s2); color: var(--sw-fg); }
.empty > p { max-width: 48ch; margin-inline: auto; }

/* ---------- Staff chat (K4.8) ------------------------------------------ */

.chat-log {
  display: flex;
  flex-direction: column;
  gap: var(--sw-s3);
  min-height: 12rem;
  max-height: 60vh;
  overflow-y: auto;
  padding: var(--sw-s4);
  margin: var(--sw-s4) 0;
  background: var(--sw-surface);
  border: 1px solid var(--sw-border);
  border-radius: var(--sw-r-lg);
  box-shadow: var(--sw-shadow-sm) inset;
}
.msg {
  max-width: min(80%, 46rem);
  padding: var(--sw-s2) var(--sw-s3);
  border-radius: var(--sw-r-md);
  line-height: 1.5;
}
.msg-user {
  align-self: flex-end;
  background: var(--sw-accent);
  color: var(--sw-fg-on-accent);
  border-bottom-right-radius: var(--sw-r-sm);
}
.msg-assistant {
  align-self: flex-start;
  background: var(--sw-surface-3);
  color: var(--sw-fg);
  border-bottom-left-radius: var(--sw-r-sm);
}
.msg-body { white-space: pre-wrap; overflow-wrap: break-word; }
/* Streaming text stays pre-wrap; once rendered, `msg-rich` hands spacing to
   the block children. */
.msg-body.msg-rich { white-space: normal; }
.msg-body.msg-rich > :first-child { margin-top: 0; }
.msg-body.msg-rich > :last-child { margin-bottom: 0; }
.msg-p { margin: 0 0 var(--sw-s2); }
.msg-list { margin: 0 0 var(--sw-s2); padding-left: var(--sw-s5); }
.msg-list > li { margin: 2px 0; }
.msg-body strong { font-weight: 600; }
/* staff-chat.js's appendLinked() gives a linkified URL inside a bubble this class, and nothing
   defined it. The base `a` rule already supplies the colour, so the gap was the UNDERLINE: an inline
   link in a wall of reply text would have been distinguished by hue alone until hover (WCAG 1.4.1).
   Dormant on the staff surface as it stands — appendLinked needs a link allow-list and the portal
   passes none — so this is the definition arriving ahead of its first caller, not a repair. */
.msg-link { color: var(--sw-link); text-decoration: underline; }
.msg-status {
  margin-top: var(--sw-s1);
  font-size: var(--sw-text-xs);
  font-style: italic;
  color: var(--sw-fg-subtle);
}
.msg-notice { margin-top: var(--sw-s1); font-size: var(--sw-text-sm); font-weight: 500; }
/* The chat-bubble severity set, namespaced `msg-notice-*` instead of sharing the bare `notice-`
   prefix with the page-notice intents above. Those carry a BOX; these carry a text colour inside a
   bubble that already supplies its own padding, so a padded bordered card here would be wrong. The
   two families genuinely collided: staff-chat.js composes this class at runtime from the server's
   notice kind, and its `kind || "info"` fallback made the DEFAULT bubble class the page intent
   `notice-info`. The five kinds are the whole closed vocabulary (IChatTurnSink.cs:20); `-info` is
   that fallback, which was reachable and undefined before this rule.
   Guarded by DesignSystemGuardTests.The_chat_bubble_notice_family_is_namespaced. */
.msg-notice-error, .msg-notice-rejected { color: var(--sw-danger-fg); }
.msg-notice-escalated, .msg-notice-handoff, .msg-notice-truncated { color: var(--sw-warn-fg); }
.msg-notice-busy { color: var(--sw-fg-subtle); }
.msg-notice-info { color: var(--sw-fg-muted); }
.msg-user .msg-status, .msg-user .msg-notice { color: var(--sw-brand-100); }

.sources { margin-top: var(--sw-s2); font-size: var(--sw-text-sm); }
.sources summary { cursor: pointer; color: var(--sw-link); }
.sources ul { margin: var(--sw-s1) 0 0; padding-left: var(--sw-s5); }
.src-title { font-weight: 600; }
.src-meta { color: var(--sw-fg-muted); }
.src-external { font-style: italic; } /* KH3.6: another organization's page */
.sources .src-sections { margin-top: 0; }

.chat-input {
  display: flex;
  gap: var(--sw-s2);
  align-items: flex-end;
  /* wrap, so the 20rem composer minimum below cannot push the page sideways at 320px */
  flex-wrap: wrap;
  margin: 0;
}
/* The basis stays 0%, NOT auto: `auto` resolves to the global width:100%, so the textarea’s
   hypothetical size becomes the whole container and Send is pushed onto a second line at
   EVERY width. `min-width` is what stops that collapse, and min() is what lets a 320px
   viewport reflow instead of scrolling sideways. (CL6 removed the page-language select
   that shared this row, D-406.) */
.chat-input textarea { flex: 1 1 0%; min-width: min(20rem, 100%); resize: vertical; min-height: 2.5rem; }
/* SHARED-6 (d): the composer is read-only, not disabled, while a reply streams, so it keeps
   focus; this tells the reader it is waiting. */
.chat-input textarea[readonly] { background: var(--sw-surface-2); }
/* SHARED-6: the one live region (role="status"). min-height, so the line appearing does not
   shift the composer. */
.chat-status {
  min-height: 1.25rem;
  margin: var(--sw-s2) 0 0;
  font-size: var(--sw-text-sm);
  color: var(--sw-fg-muted);
}

/* ---------- Metrics: stat tiles + sparklines ----------------------------
   The dashboards' two new primitives. Phase 6 specified them and then CUT them
   because nothing consumed them; they land here WITH their call sites
   (/Platform/Engagements) in the same commit, which is the rule that cut them.

   Three constraints shaped these rules rather than taste:

   - No `@media` block. A second `@media (max-width: 900px)` is a guarded
     duplicate, so the tile row reflows with `auto-fit` + `minmax` instead. That
     is the better mechanism anyway: it responds to the CONTAINER it lands in
     rather than to the viewport, so a tile row inside a panel and one at page
     width both behave.
   - No colour-only signal. There is deliberately no `.stat-bad`/`.stat-ok`
     variant: a tile that meant something by being red would fail the
     colour-alone guard and, more to the point, would be unreadable in
     forced-colors. A tile says what it means in `.stat-note`.
   - `border-radius` here is NOT paired with `border-left-width`. An accent left
     rule is the natural shape for a stat tile and it is a guarded combination,
     so emphasis comes from type scale instead. */

.stat-row {
  display: grid;
  /* Reflows without a breakpoint: as many ~11rem tiles as fit, then fewer. */
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: var(--sw-s3);
  margin: 0 0 var(--sw-s4);
}

.stat {
  background: var(--sw-surface-inset);
  border: 1px solid var(--sw-border);
  border-radius: var(--sw-r-md);
  padding: var(--sw-s3) var(--sw-s4);
  min-width: 0;
  /* Tiles in one row share three row tracks — label, value, note — so a label that wraps to two
     lines moves every value in the row down with it. As independent boxes, one wrapped label left
     its own number a line lower than its neighbours'. Every tile has exactly those three children;
     a second, conditional note sits INSIDE the first rather than beside it, or it would land in a
     fourth track the subgrid does not have. */
  display: grid;
  grid-row: span 3;
  grid-template-rows: subgrid;
  row-gap: 0;
}

.stat-label {
  display: block;
  font-size: var(--sw-text-xs);
  color: var(--sw-fg-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.stat-value {
  display: block;
  font-size: var(--sw-text-2xl);
  line-height: 1.15;
  color: var(--sw-fg);
  /* Digits share a width, so a column of tiles does not jitter as counts change. */
  font-variant-numeric: tabular-nums;
}

/* Where the number's meaning lives — a denominator, a date, or why it is absent.
   A ratio without its denominator is the thing the Costs page refuses to render,
   and this is where that denominator goes. */
.stat-note {
  display: block;
  font-size: var(--sw-text-sm);
  color: var(--sw-fg-subtle);
}

/* DA1 (Home): a tile's facts, one per line, and the link to the page that owns its number. They sit INSIDE
   `.stat-note`, so the tile keeps the three children its subgrid has rows for. A marked line says what it means
   in its own words (a ⚠ and the age); the colour only repeats it. */
.stat-note > .stat-line { display: block; }
.stat-note > .stat-line.warn {
  color: var(--sw-warn-fg);
  font-weight: 600;
}
.stat-note > .stat-link {
  display: inline-block;
  margin-top: var(--sw-s2);
}
/* DA2: a tile's second link (the Mailbox tile's "Add a mailbox") is a link of its own; a bare space ran the two together
   into what read as one link. */
.stat-note > .stat-link + .stat-link { margin-left: var(--sw-s4); }

/* DA2: Home's quick actions sit between two bands, so they keep the panels' own spacing below as well as above. */
.home-actions {
  margin-top: 0;
  margin-bottom: var(--sw-s5);
}

/* §38: when the NUMBER ITSELF is qualified — a floor rather than a total. Subtle grey is right for
   a denominator and wrong for "this figure understates reality", so this one leaves the subtle ramp.
   Not a badge: it belongs to the value above it, not beside it. */
.stat-note.warn {
  color: var(--sw-warn-fg);
  font-weight: 600;
}

/* Server-rendered inline SVG. Geometry rides ATTRIBUTES (viewBox, points) because
   CSP forbids `style=` and a `<style>` inside the <svg>; only paint is set here.
   🛑 The polyline is not an aesthetic choice: a <path d="M0 20 L10 5"> reddens
   No_page_renders_an_internal_spec_reference, whose bare-milestone alternative
   `\b[BKLMSRE]\d+` reads M0 and L10 as milestone ids. `points` has no command
   letters at all, so the trap cannot be reintroduced by reformatting. */
.spark {
  display: block;
  width: 100%;
  /* A stated height, not `auto`: the viewBox is 100x30, so `auto` made the line 30% as tall as the
     panel is wide — a 330px chart at desktop width, most of it empty when the series is flat. The
     markup's preserveAspectRatio="none" lets the line stretch to this box instead. */
  height: 4.5rem;
  overflow: visible;
  color: var(--sw-accent);
}
.spark > polyline {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  /* The line is scaled by the viewBox, which would scale the stroke with it and
     make a wide sparkline hairline-thin. */
  vector-effect: non-scaling-stroke;
}
/* The baseline, so a flat series reads as "zero throughout" rather than as a line
   floating at an arbitrary height. */
.spark > line {
  stroke: var(--sw-border-strong);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

/* The QR code on the two-factor set-up page (Composition/AuthenticatorQrCode). Server-rendered inline SVG like the
   sparklines: geometry in the viewBox and the path, paint here.
   Dark modules on WHITE in both themes, never the theme's surface: many scanners do not read an inverted code, so the
   dark theme must not flip it. The white also paints the quiet zone, which sits inside the viewBox.
   15rem is about 4px a module at the usual size (53 to 57 modules a side), comfortably scannable, and it still fits a
   320px screen inside the panel. `forced-color-adjust: none` keeps the two colours in Windows High Contrast, which
   would otherwise repaint them. */
.qr {
  display: block;
  width: 15rem;
  max-width: 100%;
  height: auto;
  margin: var(--sw-s3) 0;
  background: var(--sw-white);
  color: var(--sw-n-950);
  forced-color-adjust: none;
}
.qr > path {
  fill: currentColor;
}

/* ---------- Review card, the queue and All mail (RF-3, RF-5) -------------
   One mail on its own page, and the one-line-per-mail list (docs/email-review-feed-plan.md §3.2, §4.7). Every
   well sits INSIDE the card's surface: in light mode `--sw-surface-2` equals `--sw-bg`, so a well on the page
   ground would vanish. The callout's rule is the `border-left` SHORTHAND, never `border-left-width`: that
   property paired with a radius is the notice box's signature, which a guard counts. */

/* The page head's Queue | All mail switch: a segmented pair of links. */
.review-switch {
  gap: 0;
  border: 1px solid var(--sw-border-strong);
  border-radius: var(--sw-r);
  overflow: hidden;
  align-self: center;
}
.review-switch > a {
  padding: var(--sw-s1) var(--sw-s3);
  color: var(--sw-fg-muted);
  font-size: var(--sw-text-sm);
  font-weight: 500;
  white-space: nowrap;
}
.review-switch > a + a { border-left: 1px solid var(--sw-border-strong); }
.review-switch > a:hover { background: var(--sw-surface-3); color: var(--sw-fg); text-decoration: none; }
/* Not colour alone (WCAG 1.4.1): the fill, the weight and aria-current carry it. */
.review-switch > a[aria-current] {
  background: var(--sw-accent-quiet);
  color: var(--sw-link);
  font-weight: 600;
}

.review-card { padding: var(--sw-s5); }
.review-card-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sw-s2) var(--sw-s4);
  margin-bottom: var(--sw-s4);
}
.review-card-head > div { min-width: 0; }
.review-card-from {
  margin: 0 0 var(--sw-s1);
  color: var(--sw-fg-muted);
  font-size: var(--sw-text-sm);
  overflow-wrap: anywhere;
}
.review-card-head h2 { margin: 0; overflow-wrap: anywhere; }
.review-card-facts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sw-s2);
  margin: 0;
  color: var(--sw-fg-muted);
  font-size: var(--sw-text-sm);
}

/* What the reviewer is asked to do: platform copy first, the agent's words quoted beneath it. */
.review-callout {
  margin: 0 0 var(--sw-s5);
  padding: var(--sw-s3) var(--sw-s4);
  border: 1px solid var(--sw-border);
  border-left: 3px solid var(--sw-border-strong);
  border-radius: var(--sw-r);
  background: var(--sw-surface-2);
}
.review-callout > :last-child { margin-bottom: 0; }
.review-callout-ok     { border-left: 3px solid var(--sw-ok-border); }
.review-callout-warn   { border-left: 3px solid var(--sw-warn-border); }
.review-callout-danger { border-left: 3px solid var(--sw-danger-border); }
.review-callout-label {
  margin: 0 0 var(--sw-s1);
  color: var(--sw-fg-subtle);
  font-size: var(--sw-text-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.review-callout-headline { margin: 0 0 var(--sw-s2); font-size: var(--sw-text-md); font-weight: 600; }
.review-callout blockquote { margin: 0 0 var(--sw-s2); color: var(--sw-fg); }

/* The reply as the customer will read it: the envelope's own lines muted around the drafted text. */
.review-reply {
  margin: var(--sw-s2) 0 var(--sw-s4);
  padding: var(--sw-s3) var(--sw-s4);
  border: 1px solid var(--sw-border);
  border-radius: var(--sw-r);
  background: var(--sw-surface-inset);
}
.review-reply-frame { margin: 0; color: var(--sw-fg-muted); white-space: pre-line; }
.review-reply-body {
  margin: var(--sw-s3) 0;
  white-space: pre-wrap;
  overflow-wrap: break-word;
}

.review-checks { list-style: none; margin: 0 0 var(--sw-s4); padding: 0; }
.review-checks > li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sw-s1) var(--sw-s3);
  margin: 0;
  padding: var(--sw-s2) 0;
  border-bottom: 1px solid var(--sw-border);
}
.review-checks > li:last-child { border-bottom: 0; }
.review-checks > li > span:not(.badge) { flex: 1 1 18rem; min-width: 0; }
.review-checks form { flex: 1 1 100%; margin: 0; }

/* RF-5: what the page script reveals is written with `hidden`, and several of those elements set a display of their own
   (`.notice` is block, the confirm strip flex). An author rule outranks the browser's `[hidden]` whatever its weight, so
   this one restores it inside the card: (0,2,0) beats every single-class display rule. */
.review-card [hidden] { display: none; }

/* D-322: the reply box is the framed reply's body, between the envelope's greeting and its closing, at the base
   control's full width inside the frame's padding. It is never a disclosure's own child again: the Disclosure rules
   give such a child side margins, and a full-width control ran one margin past the box's clipped edge. The note
   beside it, and the take-over panel's fields, sit in a label (in a form), and fit as they are. */
.review-reply > textarea { margin: var(--sw-s3) 0; }
.review-note { margin: 0 0 var(--sw-s4); }
/* The take-over panel lives in the card body; the action bar's link opens it. */
.review-takeover { margin: 0 0 var(--sw-s4); }
.review-takeover label { display: block; margin-top: var(--sw-s2); }

/* RF-5 (§3.8): the decisions, in one bar at the foot of the card that stays in view while the card is. It spans the
   card's padding and keeps the panel's surface, so what scrolls beneath it does not show through. */
.review-bar {
  position: sticky;
  bottom: 0;
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  /* Baseline, not centre: with the confirm strip open, Previous and Skip line up with the row of buttons, not with
     the middle of the strip beneath it. */
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sw-s2) var(--sw-s3);
  /* The bottom margin assumes the bar is the card's LAST child: whatever follows it is pulled up underneath. */
  margin: var(--sw-s5) calc(-1 * var(--sw-s5)) calc(-1 * var(--sw-s5));
  padding: var(--sw-s3) var(--sw-s5);
  border-top: 1px solid var(--sw-border);
  border-radius: 0 0 var(--sw-r-lg) var(--sw-r-lg);
  background: var(--sw-surface);
  box-shadow: var(--sw-shadow);
}
.review-bar form { margin: 0; }
/* Previous and Skip keep the bar's row however much the actions hold, wherever the bar has room for both beside the
   actions' basis. The actions are sized by that fixed basis, never by their content: content-sized, the open confirm
   strip's sentence made them wider than the row and pushed the bar's last control (Next then, Skip since D-322) onto a
   line of its own. The basis is about the widest single control, so a bar too narrow for it wraps rather than squeezing
   a button's label: Previous first, Skip last and at the right edge, in their reading order. */
.review-bar-actions {
  display: flex;
  flex: 1 1 16rem;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--sw-s2);
  min-width: 0;
}
/* A settled mail offers no decision, and its empty actions must not claim the basis: it would push Back to All mail
   (or Skip) under an empty line on a narrow bar. */
.review-bar-actions:not(:has(*)) { flex-basis: 0; }
/* Skip, or Back to All mail, keeps the right edge when a narrow bar wraps it onto a line of its own. */
.review-bar > :last-child { margin-left: auto; }
.review-bar-actions > p { flex: 1 1 100%; margin: 0; text-align: right; }
/* The send form lays out nothing itself: Send reply stays in the row of buttons, and the confirm strip, a line
   of its own, opens beneath the whole row rather than beneath that one button. */
.review-send { display: contents; }
/* A focused control never hides behind the bar (WCAG 2.4.11). */
:root:has(.review-bar) { scroll-padding-bottom: 9rem; }

/* EQ-1's inline confirm (§3.7): who it goes to, from where, under what subject, and Send now. */
.review-confirm {
  display: flex;
  flex: 1 1 100%;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sw-s2);
  padding: var(--sw-s3);
  border: 1px solid var(--sw-warn-border);
  border-radius: var(--sw-r);
  background: var(--sw-surface-2);
}
.review-confirm > p { flex: 1 1 18rem; margin: 0; overflow-wrap: anywhere; }

/* The shortcut hints: shown only while the shortcuts are on (the script sets the class), and kept out of each
   control's accessible name, which aria-keyshortcuts carries instead. */
.review-kbd { display: none; }
.review-keys-on .review-kbd {
  display: inline-block;
  margin-left: var(--sw-s1);
  padding: 0 var(--sw-s1);
  border: 1px solid var(--sw-border-strong);
  border-radius: var(--sw-r);
  color: var(--sw-fg-muted);
  font-size: var(--sw-text-xs);
  line-height: 1.4;
}
.review-keys kbd {
  padding: 0 var(--sw-s1);
  border: 1px solid var(--sw-border-strong);
  border-radius: var(--sw-r);
}

/* RF-5 (§3.6): a colleague has the mail open — a warning only, with nothing to claim (D-322). */
.review-lease > p { margin: 0; }

/* RF-5 (§3.5, §3.6 item 5): the queue head — the chips (`.review-counts`, shared with All mail) and the count line. */
.review-queue-head { margin: 0 0 var(--sw-s4); }
.review-queue-head > .review-counts { margin-bottom: var(--sw-s2); }
.review-queue-counts { margin: 0 0 var(--sw-s1); font-weight: 600; }

.review-details > h4 { margin: var(--sw-s4) 0 var(--sw-s2); }
.review-details > h4:first-of-type { margin-top: var(--sw-s3); }

/* All mail: how many mails each state holds, as filter links. */
.review-counts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sw-s2);
  list-style: none;
  margin: 0 0 var(--sw-s4);
  padding: 0;
}
.review-counts > li { margin: 0; }
.review-counts a {
  display: inline-flex;
  align-items: center;
  gap: var(--sw-s2);
  padding: var(--sw-s1) var(--sw-s3);
  border: 1px solid var(--sw-border);
  border-radius: var(--sw-r-full);
  color: var(--sw-fg);
  font-size: var(--sw-text-sm);
}
.review-counts a:hover { background: var(--sw-surface-3); text-decoration: none; }
.review-counts a[aria-current] { border-color: var(--sw-accent); font-weight: 600; }

/* ---------- Utilities ---------------------------------------------------
   Deliberately few. Anything reached for more than twice should become a
   component above instead. */

.stack     { display: flex; flex-direction: column; gap: var(--sw-s4); }
.row       { display: flex; align-items: center; gap: var(--sw-s3); flex-wrap: wrap; }
.muted     { color: var(--sw-fg-muted); }
.subtle    { color: var(--sw-fg-subtle); }
.nowrap    { white-space: nowrap; }
.text-sm   { font-size: var(--sw-text-sm); }
.mono      { font-family: var(--sw-font-mono); }
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
  }
}

/* Windows High Contrast / forced-colors: box-shadow is not painted and border
   colours are overridden, so the outline is the ONLY signal that survives. Every
   focus treatment above therefore states a real outline — transparent where the
   normal-rendering signal is the box-shadow ring — and this block repaints it. */
@media (forced-colors: active) {
  a:focus-visible, button:focus-visible, .btn:focus-visible, summary:focus-visible,
  input:focus-visible, select:focus-visible, textarea:focus-visible,
  [tabindex]:focus-visible {
    outline: 2px solid Highlight;
    outline-offset: 2px;
  }
  /* The drawn checkboxes and radios signal "on" with a fill, which forced-colors replaces with the
     page colour, so the radio's dot would vanish. The system draws them here, in the user's colours. */
  input[type=checkbox], input[type=radio] { appearance: auto; }
  input[type=checkbox]::before, input[type=radio]::before { content: none; }
}
