/* The control-plane console.
   The tokens began as the trimmed CapitalBridge system in docs/design-handoff/tokens; this file inlines
   them (one stylesheet, no @import chain under a strict CSP) and adds the components.

   THE BRAND HUES HERE ARE NO LONGER THOSE TOKENS. docs/design-handoff/tokens still carries the Acterys
   derivation and its own README already says this file wins when they disagree - they now disagree on
   the whole palette, deliberately: the console shipped a LICENSEE's brand hues, which is indefensible
   as soon as a third party reads it. See the seam comment on :root.

   This file is the OPERATOR DEFAULT (Proceptio). A white-label partner overrides the ten brand tokens
   from their own file, loaded after this one - see ProductBrand.StylesheetPath. Do not add a hue below
   the token block; a component with a literal colour is a component a re-brand cannot reach.

   FONTS: self-hosted in wwwroot/fonts and NOT imported from an external origin, because the CSP has
   no font-src beyond 'self' and a blocked font fails silently - the console renders in a system
   fallback and nobody notices. See wwwroot/fonts/LICENSES.md for the licences.

   Split by unicode-range rather than shipping one variable file per family: a latin-only reader
   fetches 64 KB instead of 90 KB, and the browser decides, not us. Both are real variable fonts, so
   one file covers the whole weight range with no synthetic bolding.

   A test in Proceptio.Ai.Tests asserts every url() here resolves to a committed file, because the
   previous version of this comment described two files that did not exist. */

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

:root {
  /* ---- Brand: Proceptio (ink + brass) ----
     THIS BLOCK IS THE WHITE-LABEL SEAM. A partner operator overrides these ten custom properties from
     their own stylesheet (Brand:Stylesheet, loaded after this file); everything below consumes the
     SEMANTIC aliases further down, so a re-brand is ten values rather than a component sweep.

     It used to be the Acterys palette, under the heading "Acterys brand" - a licensee's brand hues in
     the licensor's own console, which is indefensible the moment a third party reads it. The prefix was
     --ax-* for the same reason and is now brand-neutral, because a token named after one company is a
     token every future operator has to ignore.

     The accent is DARKER in light mode than the brass in the marketing palette (#C9A24B). That brass on
     white is about 2:1 - fine on the marketing page's ink background, unreadable as a link colour here.
     Same family, enough contrast to be text; the light brass returns in dark mode below.

     The status hues are deliberately UNCHANGED. They are semantic rather than brand - a red that means
     refused is not a licensee's red - and every one of them was already tuned for contrast, including
     --brand-danger being lightened for the near-black rail where the attention pip lands near 3:1.
     Re-hueing them to fit a palette would have been a contrast regression dressed as consistency. */
  --brand-primary:   #8a6a1f;
  --brand-accent:    #8a6a1f;
  --brand-accent-2:  #6e5418;
  --brand-secondary: #b08d3c;
  --brand-cyan:      #c9a24b;
  --brand-warning:   #b07d00;
  --brand-success:   #009650;
  --brand-danger:    #cc3232;
  --brand-info:      #5b6673;
  --brand-muted:     #79818e;

  /* ---- Surfaces (light): neutral cool page with white cards ----
     Neutral rather than the previous teal tint, which was the brand hue leaking into the substrate -
     the one place a re-brand cannot reach by overriding ten tokens. */
  --surface-b: #eff1f4;
  --surface-b-2: #ffffff;
  --surface-b-3: #f6f7f9;
  --rail: #0e1219;
  --rail-ink: #d8dee6;
  --rail-ink-2: #8a95a3;

  /* The attention pip on the rail. A token rather than a literal because it is a HUE, and the rail is
     near-black in both themes - so --brand-danger, tuned for a white card, lands around 3:1 there. This
     is that colour lightened for the rail, and as a literal it was the one hue a partner overriding the
     brand tokens could not reach: their console would carry Proceptio's red on the one element that is
     always on screen. Not redefined in the dark blocks: the rail is dark in both. */
  --rail-danger: #ff9a92;

  --on-surface: #1b2027;
  --on-surface-2: #545c68;
  --on-surface-muted: #79818e;

  --line: #cdd2da;
  --line-2: #afb6c0;
  --shadow-color: rgba(14, 18, 25, 0.10);
  --on-accent: #ffffff;

  --bg-page: var(--surface-b);
  --surface-card: var(--surface-b-2);
  --surface-inset: var(--surface-b-3);
  --text-body: var(--on-surface);
  --text-secondary: var(--on-surface-2);
  --text-muted: var(--on-surface-muted);
  --border-hairline: var(--line);
  --border-strong: var(--line-2);
  --accent: var(--brand-accent);
  --accent-pressed: var(--brand-accent-2);

  /* A wash and its foreground must come from the same family. There is no --wash-info on purpose:
     info severity is deliberately grey (see .pill.mute), so a semantic wash for it would be an
     unused invitation to make the most numerous findings look brand-accented and therefore
     important. */
  --wash-accent: rgba(138, 106, 31, 0.12);
  --wash-accent-soft: rgba(138, 106, 31, 0.05);
  --wash-amber: rgba(176, 125, 0, 0.16);
  --wash-danger: rgba(204, 50, 50, 0.12);
  --wash-success: rgba(0, 150, 80, 0.12);

  /* ---- Type: two families, console scale ---- */
  --font-ui: "Outfit", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: "JetBrains Mono", "Cascadia Mono", Consolas, ui-monospace, monospace;

  --text-h1: 1.75rem;
  --text-h2: 1.25rem;
  --text-h3: 1rem;
  --text-data-lg: 1.75rem;
  --text-body-md: 14px;
  --text-body-sm: 13px;
  --text-eyebrow: 11px;
  --text-ident: 13px;

  --tracking-tight: -0.015em;
  --tracking-eyebrow-wide: 2.5px;
  --leading-body: 1.5;

  /* ---- Space, radius, motion ---- */
  --space-1: 6px;  --space-2: 10px; --space-3: 14px; --space-4: 18px;
  --space-5: 22px; --space-6: 28px; --space-7: 36px;
  --radius-sharp: 4px; --radius-chip: 6px; --radius-card: 10px; --radius-pill: 999px;

  --ease-out-quint: cubic-bezier(0.16, 1, 0.3, 1);
  --dur-micro: 160ms;
  --transition-base: all var(--dur-micro) var(--ease-out-quint);
  --shadow-sm: 0 1px 2px var(--shadow-color);
  --shadow-card: 0 6px 18px -10px var(--shadow-color);
}

/* Dark, on the viewer's OS preference and on an explicit toggle. Given the same care as light: an
   operator on a dark desktop is looking at this all day. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    /* Brass proper here: on an ink surface #c9a24b is legible, which is why the light mode above uses a
       darker member of the same family instead. --on-accent goes DARK because the accent is now light. */
    --brand-accent: #c9a24b; --brand-accent-2: #b08d3c;
    --brand-warning: #e0a92b; --brand-success: #3fb883; --brand-danger: #f2726f; --brand-info: #9aa4b2;
    --surface-b: #0e1219; --surface-b-2: #161b22; --surface-b-3: #1b212a;
    --rail: #090c11;
    --on-surface: #e6e9ee; --on-surface-2: #a4adba; --on-surface-muted: #7b8593;
    --line: #2a313b; --line-2: #3c4552;
    --shadow-color: rgba(0, 0, 0, 0.55);
    --on-accent: #171a12;
    --wash-accent: rgba(201, 162, 75, 0.16);
    --wash-accent-soft: rgba(201, 162, 75, 0.06);
    --wash-amber: rgba(224, 169, 43, 0.18);
    --wash-danger: rgba(242, 114, 111, 0.16);
    --wash-success: rgba(63, 184, 131, 0.16);
  }
}
:root[data-theme="dark"] {
  /* The explicit toggle. Kept byte-identical to the media-query block above rather than factored out:
     one is "the OS says dark", the other is "the reader chose dark", and CSS has no way to share a
     declaration list between them without a preprocessor this project deliberately does not have. */
  --brand-accent: #c9a24b; --brand-accent-2: #b08d3c;
  --brand-warning: #e0a92b; --brand-success: #3fb883; --brand-danger: #f2726f; --brand-info: #9aa4b2;
  --surface-b: #0e1219; --surface-b-2: #161b22; --surface-b-3: #1b212a;
  --rail: #090c11;
  --on-surface: #e6e9ee; --on-surface-2: #a4adba; --on-surface-muted: #7b8593;
  --line: #2a313b; --line-2: #3c4552;
  --shadow-color: rgba(0, 0, 0, 0.55);
  --on-accent: #171a12;
  --wash-accent: rgba(201, 162, 75, 0.16);
  --wash-accent-soft: rgba(201, 162, 75, 0.06);
  --wash-amber: rgba(224, 169, 43, 0.18);
  --wash-danger: rgba(242, 114, 111, 0.16);
  --wash-success: rgba(63, 184, 131, 0.16);
}

*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }

body {
  font-family: var(--font-ui);
  font-size: var(--text-body-md);
  line-height: var(--leading-body);
  background: var(--bg-page);
  color: var(--text-body);
  -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The first focusable element on every page. Off-screen until focused, which is the only way a
   keyboard user discovers it. */
.skip {
  position: absolute; left: -9999px; top: 0; z-index: 10;
  background: var(--surface-card); color: var(--text-body);
  border: 1px solid var(--accent); border-radius: var(--radius-sharp);
  padding: var(--space-2) var(--space-3);
}
.skip:focus { left: var(--space-2); top: var(--space-2); text-decoration: none; }
/* The skip target is tabindex="-1", so suppress the ring it would otherwise draw around the whole
   main column on arrival - the browser has already moved focus, which is the point. */
main:focus { outline: none; }

/* ── shell ─────────────────────────────────────────────────────────────────────────────── */
.app { display: grid; grid-template-columns: 232px minmax(0, 1fr); min-height: 100vh; }
@media (max-width: 860px) { .app { grid-template-columns: 1fr; } }

.rail {
  background: var(--rail); color: var(--rail-ink);
  padding: var(--space-4) var(--space-2) var(--space-6);
  display: flex; flex-direction: column; gap: var(--space-5);
}
.rail .mark { display: flex; flex-direction: column; gap: 2px; padding: 0 var(--space-1); }
.rail .mark b { font-size: var(--text-h3); font-weight: 600; color: #fff; letter-spacing: var(--tracking-tight); }
.rail .mark span {
  font-family: var(--font-mono); font-size: 10px; letter-spacing: var(--tracking-eyebrow-wide);
  text-transform: uppercase; color: var(--rail-ink-2);
}
.rail nav { display: flex; flex-direction: column; gap: 2px; }
.rail .navgroup {
  font-family: var(--font-mono); font-size: 10px; letter-spacing: var(--tracking-eyebrow-wide);
  text-transform: uppercase; color: var(--rail-ink-2); padding: var(--space-3) var(--space-1) var(--space-1);
}
.rail nav a {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-2);
  color: var(--rail-ink); font-size: var(--text-body-md);
  padding: var(--space-1) var(--space-2); border-radius: var(--radius-sharp);
  transition: var(--transition-base);
}
.rail nav a:hover { background: rgba(255,255,255,.07); text-decoration: none; }
.rail nav a[aria-current="page"] { background: var(--accent); color: var(--on-accent); font-weight: 500; }
.rail nav a .pip { font-family: var(--font-mono); font-size: var(--text-body-sm); opacity: .7; }
/* Not --brand-danger: that red is tuned for a white card and lands near 3:1 against the near-black rail,
   which is below what small text needs. Lightened for this surface only, and full opacity, because the
   whole point of the pip is that it reads from anywhere in the console. */
.rail nav a .pip.alert { color: var(--rail-danger); opacity: 1; }
/* The global ring is the accent, which against the near-black rail is about 2.6:1 - under the 3:1 a
   focus indicator needs against its adjacent colour. Scoped override rather than changing the
   accent, because the accent is correct everywhere else. */
.rail :focus-visible { outline-color: #fff; }

.whoami {
  margin-top: auto; border-top: 1px solid rgba(255,255,255,.1);
  padding: var(--space-3) var(--space-1) 0; display: flex; flex-direction: column; gap: 2px;
}
.whoami b { font-size: var(--text-body-sm); font-weight: 500; color: #fff; word-break: break-all; }
.whoami span { font-family: var(--font-mono); font-size: var(--text-eyebrow); color: var(--rail-ink-2); }
.whoami form { margin-top: var(--space-2); }
.whoami button {
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--rail-ink-2); font-family: var(--font-ui); font-size: var(--text-body-sm);
}
.whoami button:hover { color: #fff; }

/* The client the console is pointed at. Above the identity block rather than in it: "which client" and
   "which of us" are different questions, and the first one changes far more often. margin-top:0 undoes the
   auto margin that pushes the identity block to the bottom of the rail. */
.whoami.acting { margin-top: auto; border-top: 0; padding-bottom: var(--space-2); }
.whoami.acting + .whoami { margin-top: 0; }
.whoami.acting b { font-size: var(--text-body-md); }
.whoami.acting form { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }
.whoami.acting select {
  flex: 1 1 100%; min-width: 0; background: rgba(255,255,255,.06); color: var(--rail-ink);
  border: 1px solid rgba(255,255,255,.14); border-radius: var(--radius-sharp);
  font-family: var(--font-ui); font-size: var(--text-body-sm); padding: 2px 4px;
}
.whoami.acting option { color: var(--text-body); background: var(--surface-card); }

main { min-width: 0; display: flex; flex-direction: column; }
.topbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-4);
  padding: var(--space-4) var(--space-6);
  border-bottom: 1px solid var(--border-hairline); background: var(--surface-card);
}
.topbar h1 { margin: 0; font-size: var(--text-h2); font-weight: 600; letter-spacing: var(--tracking-tight); }
.topbar .sub { color: var(--text-muted); font-family: var(--font-mono); font-size: var(--text-body-sm); }
.topbar .spacer { flex: 1 1 auto; }

.view { padding: var(--space-6); display: flex; flex-direction: column; gap: var(--space-5); }

/* Every write in this console is a POST-redirect-GET, and the redirect now carries the fragment of the row
   that was acted on - so unlocking the fortieth capability comes back to the fortieth capability instead of
   the top of the page. This gives the landing row breathing space rather than pinning it to the viewport
   edge, where it reads as "the page starts here" and the reader loses the rows above it. */
[id] { scroll-margin-top: var(--space-5); }

/* ── components ────────────────────────────────────────────────────────────────────────── */
.card {
  background: var(--surface-card); border: 1px solid var(--border-hairline);
  border-radius: var(--radius-sharp); box-shadow: var(--shadow-card);
}
.card > header {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-1) var(--space-3);
  padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--border-hairline);
}
.card > header h2 { margin: 0; font-size: var(--text-h3); font-weight: 600; }
.card > header .hint { color: var(--text-muted); font-size: var(--text-body-sm); }
.card > header .spacer { flex: 1 1 auto; }
.card .body { padding: var(--space-4); }
.card .body.tight { padding: 0; }

.tiles {
  display: grid; gap: 1px; grid-template-columns: repeat(auto-fit, minmax(min(100%, 10.5rem), 1fr));
  background: var(--border-hairline); border: 1px solid var(--border-hairline);
  border-radius: var(--radius-sharp); overflow: hidden;
}
.tile { background: var(--surface-card); padding: var(--space-3) var(--space-4); display: flex; flex-direction: column; }
.tile .k {
  font-family: var(--font-mono); font-size: 10px; letter-spacing: var(--tracking-eyebrow-wide);
  text-transform: uppercase; color: var(--text-muted);
}
.tile .v { font-size: var(--text-data-lg); font-weight: 600; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.tile .n { font-size: var(--text-body-sm); color: var(--text-secondary); }
.tile.alert .v, .tile.alert .n { color: var(--brand-danger); }
/* A tile that answers "how many" should also be the way to go and see them, so some tiles are links.
   Outline rather than a border on hover: an outline does not participate in layout, so a hovered tile
   cannot nudge the grid. */
a.tile { color: inherit; text-decoration: none; }
a.tile:hover { outline: 1px solid var(--accent); }
a.tile:hover .k { color: var(--accent); }

.scroller { overflow-x: auto; }
table { border-collapse: collapse; width: 100%; min-width: 40rem; font-size: var(--text-body-md); }
thead th {
  text-align: left; font-family: var(--font-mono); font-size: var(--text-eyebrow);
  letter-spacing: .06em; text-transform: uppercase; font-weight: 600; color: var(--text-muted);
  padding: var(--space-2) var(--space-4); background: var(--surface-inset);
  border-bottom: 1px solid var(--border-hairline); white-space: nowrap;
}
tbody td { padding: var(--space-2) var(--space-4); border-bottom: 1px solid var(--border-hairline); }
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: var(--wash-accent-soft); }
td.num { text-align: right; font-variant-numeric: tabular-nums; font-family: var(--font-mono); font-size: var(--text-body-sm); }
.sub2 { color: var(--text-muted); font-size: var(--text-body-sm); }

/* Identifiers: always mono, always selectable. A person copies these into a terminal. */
.ident { font-family: var(--font-mono); font-size: var(--text-ident); word-break: break-all; }

.pill {
  display: inline-flex; align-items: center; gap: 4px;
  font-family: var(--font-mono); font-size: var(--text-eyebrow); letter-spacing: .05em;
  text-transform: uppercase; font-weight: 600;
  padding: 2px 6px; border-radius: 2px; border: 1px solid transparent; white-space: nowrap;
}
.pill.ok { color: var(--brand-success); background: var(--wash-success); }
.pill.env { color: var(--accent); background: var(--wash-accent); border-color: var(--border-hairline); }
.pill.warn { color: var(--brand-warning); background: var(--wash-amber); }
.pill.bad { color: var(--brand-danger); background: var(--wash-danger); }
.pill.mute { color: var(--text-muted); background: var(--surface-inset); border-color: var(--border-hairline); }
/* A pill naming a project is also the way to it. The colour stays the state's, because that is what the
   pill is FOR - so the link is marked by the underline on hover rather than by turning accent, which would
   make a revoked grant look healthy. */
a.pill { text-decoration: none; }
a.pill:hover { text-decoration: underline; border-color: currentColor; }

.btn {
  appearance: none; cursor: pointer; font-family: var(--font-ui);
  font-size: var(--text-body-md); font-weight: 500;
  padding: 6px 12px; border-radius: var(--radius-sharp);
  border: 1px solid var(--border-hairline); background: var(--surface-card); color: var(--text-body);
  transition: var(--transition-base);
}
.btn:hover { border-color: var(--brand-secondary); }
.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.btn.primary:hover { filter: brightness(1.08); }
.btn[disabled] { opacity: .45; cursor: not-allowed; }

/* A switch that is a real form control: this page works without JavaScript. */
.switch { display: inline-flex; align-items: center; gap: var(--space-2); }
.switch button {
  appearance: none; cursor: pointer; width: 38px; height: 21px; flex: 0 0 auto;
  border-radius: var(--radius-pill); border: 1px solid var(--border-hairline);
  background: var(--surface-inset); position: relative; transition: var(--transition-base);
}
.switch button::after {
  content: ""; position: absolute; top: 1px; left: 1px; width: 17px; height: 17px;
  border-radius: var(--radius-pill); background: var(--text-muted); transition: var(--transition-base);
}
.switch button[aria-checked="true"] { background: var(--accent); border-color: var(--accent); }
.switch button[aria-checked="true"]::after { transform: translateX(17px); background: #fff; }
.switch button[disabled] { opacity: .4; cursor: not-allowed; }

textarea, input[type="text"], select {
  width: 100%; font-family: var(--font-ui); font-size: var(--text-body-md);
  padding: var(--space-2); border: 1px solid var(--border-hairline);
  border-radius: var(--radius-sharp); background: var(--surface-card); color: var(--text-body);
}
textarea { min-height: 6rem; resize: vertical; }
label {
  display: block; font-family: var(--font-mono); font-size: var(--text-eyebrow);
  letter-spacing: .08em; text-transform: uppercase; color: var(--text-muted); margin-bottom: 4px;
}

.banner {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
  padding: var(--space-3) var(--space-4); font-size: var(--text-body-md);
  border-bottom: 1px solid var(--border-hairline);
}
.banner.warn { background: var(--wash-amber); }
.banner.bad { background: var(--wash-danger); }
.banner .spacer { flex: 1 1 auto; }

.empty { padding: var(--space-7) var(--space-4); text-align: center; color: var(--text-muted); }
.empty b { display: block; color: var(--text-secondary); font-weight: 500; margin-bottom: 4px; }

.note { color: var(--text-secondary); font-size: var(--text-body-sm); max-width: 74ch; }
.note.spaced { margin-top: var(--space-3); }

/* Tenant-authored text. Rendered as escaped plain text, never markdown or HTML: a host admin reading
   a client's instruction is a privileged human looking at content that client controls. */
.tenant-text {
  font-family: var(--font-mono); font-size: var(--text-body-sm);
  white-space: pre-wrap; word-break: break-word;
  background: var(--surface-inset); border: 1px solid var(--border-hairline);
  border-left: 3px solid var(--brand-warning);
  border-radius: var(--radius-sharp); padding: var(--space-3);
}

/* ── the gate: sign-in, refusal, error ──────────────────────────────────────────────────────────
   One column by default, because a refusal and an error page have nothing to put beside the card.
   Sign-in opts into two by defining the Aside section, which is the only page with something worth
   the space: what this thing is, before somebody commits to signing in to it. */
.gate { max-width: 26rem; margin: 12vh auto; padding: var(--space-6); }
/* The local identity picker is a table, and 26rem turns it into a column of wrapped cells. Only the
   Development block asks for this, so the sign-in page a client sees keeps its narrow, single-purpose
   shape - the width is set by what the page contains rather than by what it might contain. */
.gate.wide { max-width: 52rem; margin-top: 6vh; }
.gate h1 { font-size: var(--text-h1); letter-spacing: var(--tracking-tight); margin: 0 0 var(--space-2); }

/* Single column until there is genuinely room for two. Below this the aside sits ABOVE the card
   rather than beside it, which keeps the reading order right on a phone: what it is, then the way in. */
@media (min-width: 62rem) {
  .gate.split {
    max-width: 64rem;
    display: grid;
    grid-template-columns: 1.1fr 26rem;
    gap: var(--space-7);
    align-items: center;
    margin-top: 10vh;
  }
  .gate.split .gateside { padding-right: var(--space-6); }
}

/* The proposition. Quiet by construction: the card is the only thing with a filled control on it, so
   the eye lands on the action rather than on the prose beside it. */
.gateside { margin: 0 0 var(--space-6); }
.gateside .eyebrow {
  display: block; margin: 0 0 var(--space-3);
  font-family: var(--font-mono); font-size: var(--text-eyebrow);
  letter-spacing: var(--tracking-eyebrow-wide); text-transform: uppercase; color: var(--accent);
}
.gateside h2 {
  margin: 0 0 var(--space-3); max-width: 18ch;
  font-size: clamp(1.6rem, 3.4vw, 2.3rem); line-height: 1.14;
  letter-spacing: var(--tracking-tight); font-weight: 600; text-wrap: balance;
}
.gateside .lede {
  margin: 0 0 var(--space-6); max-width: 46ch;
  font-size: var(--text-body-lg); line-height: 1.55; color: var(--text-secondary);
}

/* Three capabilities. A definition list because that is what it is - a term and what it means - and
   it gives a screen reader the pairing without any extra markup. */
.gatepoints { margin: 0; display: flex; flex-direction: column; gap: var(--space-4); }
.gatepoints > div { display: grid; grid-template-columns: 7.5rem minmax(0, 1fr); gap: var(--space-3); }
.gatepoints dt {
  font-family: var(--font-mono); font-size: var(--text-eyebrow);
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
  color: var(--text-body); padding-top: 0.15em;
}
.gatepoints dd {
  margin: 0; max-width: 44ch;
  font-size: var(--text-body-sm); line-height: 1.5; color: var(--text-muted);
}
@media (max-width: 40rem) {
  .gatepoints > div { grid-template-columns: minmax(0, 1fr); gap: var(--space-1); }
}

/* The wordmark, above the card. Separate from the card's h1 so the heading can say what the page is FOR
   ("Sign in") rather than what the software is called - the brand is a lockup, not the page's subject. */
.gatemark { display: flex; align-items: baseline; gap: var(--space-2); margin: 0 0 var(--space-3);
            padding-left: var(--space-1); }
.gatemark b { font-size: var(--text-h2); letter-spacing: var(--tracking-tight); color: var(--text-body); }
.gatemark span { font-size: var(--text-eyebrow); letter-spacing: var(--tracking-eyebrow-wide);
                 text-transform: uppercase; color: var(--text-muted); }

/* Who operates the deployment, under the card. Outside it because it describes the service rather than
   the action - and it is the OPERATOR name, which under a white-label is a different company from the
   product name in the lockup above. */
.gatefoot { margin: var(--space-3) 0 0; padding-left: var(--space-1);
            font-size: var(--text-body-sm); color: var(--text-muted); }

/* The Development identity picker, fenced. It used to run down the page at the same level as the real
   sign-in, so the page a developer sees daily and the page a client sees once differed only by length.
   The amber edge and the inset surface make "this is not the real thing" structural. */
.devfence { border: 1px dashed var(--brand-warning); border-radius: var(--radius-card);
            background: var(--wash-amber); padding: var(--space-4); }
.devfence > h2 { margin: 0 0 var(--space-3); font-size: var(--text-h3);
                 letter-spacing: var(--tracking-eyebrow-wide); text-transform: uppercase;
                 color: var(--brand-warning); }
.devfence > * + * { margin-top: var(--space-3); }

/* A button that is the only thing to do on its page. Sized to its label, a lone primary action reads as
   one option among several when there are none. */
.btn.wide { width: 100%; justify-content: center; }

/* An unadorned list of links - the handbook's "whose handbook?" chooser. A bullet would imply the items
   are prose rather than destinations. */
ul.plain { list-style: none; margin: 0; padding: 0; }
ul.plain > li + li { margin-top: var(--space-2); border-top: 1px solid var(--border-hairline);
                     padding-top: var(--space-2); }

/* ── layout primitives ─────────────────────────────────────────────────────────────────────
   These exist because the CSP has no 'unsafe-inline' in style-src, which governs the style
   ATTRIBUTE as well as <style> blocks. Every inline style in this application was therefore
   being dropped by the browser and the pages using them rendered as unstyled stacked divs -
   HTTP 200, no console error anyone was reading, no test that could see it. A grep test in
   Proceptio.Ai.Tests now fails the build on any style= attribute, so this cannot come back.

   Consequence worth knowing: the per-instance custom property trick (style="--needle: x")
   is unavailable to this application for the same reason. Anything genuinely per-row and
   numeric - bar widths, sparklines, expiry runways - takes its geometry from SVG
   ATTRIBUTES (width, points, x), which CSP does not police. */

/* The one spacing utility. Deliberately singular: a family of margin utilities is how a token
   system turns back into inline styles under a different name. */
.spaced { margin-top: var(--space-4); }

/* A row of controls under a form, or a group of status pills beside a value. */
.actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin-top: var(--space-3); }
.actions.tight { gap: var(--space-1); margin-top: 0; }

/* One tool: switch, identity, status. Stacks on a narrow viewport rather than scrolling. */
.tool-row { padding: var(--space-4); border-bottom: 1px solid var(--border-hairline); }
.tool-row:last-child { border-bottom: 0; }
.tool-row .lead { display: flex; align-items: flex-start; flex-wrap: wrap; gap: var(--space-4); }
.tool-row .lead > .who { flex: 1 1 20rem; min-width: 0; }
/* Focus survives the POST-redirect: the handler redirects with #tool-<name> so the browser
   restores the scroll position, and this restores the visible ring. Without it an admin
   configuring twelve tools loses their place twelve times. */
.tool-row:target { outline: 2px solid var(--accent); outline-offset: -2px; }

.tool-row details { margin-top: var(--space-3); }
.tool-row details summary { cursor: pointer; }
.tool-row details form { margin-top: var(--space-2); }

/* Filter bar. A real GET form, so the URL IS the audit query - bookmarkable, shareable, and correct
   under the back button. No JavaScript, which the CSP would block anyway. */
/* align-items: START, not flex-end.

   flex-end was the bug: it aligns the BOTTOM of each field, so a field carrying a .field-hint has its
   input pushed up by the height of that hint while its hintless neighbours stay at the bottom of the
   row. On the add-project form that put SLUG's box a full line above NAME and ENVIRONMENT. Aligning to
   the start instead lines up the labels, and because every label in a filter row is one line, the
   controls under them line up too - regardless of how many lines of hint any field carries.

   The cost is that anything WITHOUT a label - a button, a checkbox - now sits at the top of the row
   rather than beside the inputs, which is what --control-offset corrects below. */
.filters { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: flex-start; }
.filters .field { flex: 0 1 11rem; min-width: 0; }
/* 11rem fits a slug, an environment or a seat count - and cuts a GUID in half, which is what the Microsoft
   tenant field is. Wide enough for 36 characters plus the caret, and it takes its hint with it: at 11rem the
   OpenID configuration URL wrapped into four lines of mono text taller than the control it explains. */
.filters .field.wide { flex: 1 1 26rem; }

/* One label's worth of height: 11px eyebrow at the body leading, plus its 4px margin. Anything that
   has to sit level with the inputs but carries no label of its own gets pushed down by exactly this. */
.filters { --control-offset: calc(var(--text-eyebrow) * var(--leading-body) + 4px); }
.filters .actions { margin-top: var(--control-offset); }

.field label { margin-bottom: 4px; }

/* Footer strip: row count and legend on the left, paging on the right. */
.pager {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3);
  padding: var(--space-3) var(--space-4); border-top: 1px solid var(--border-hairline);
  background: var(--surface-inset);
}
.pager .spacer { flex: 1 1 auto; }
.pager .sub2 { max-width: 70ch; }

/* Change trail. A timeline rather than a table because these are events with prose attached, and the
   before/after pair does not fit a column. */
.trail { list-style: none; margin: 0; padding: 0; }
.trail-day {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-4) var(--space-4) var(--space-2);
  font-family: var(--font-mono); font-size: var(--text-eyebrow);
  letter-spacing: var(--tracking-eyebrow-wide); text-transform: uppercase; color: var(--text-muted);
}
.trail-day hr { flex: 1 1 auto; border: 0; border-top: 1px solid var(--border-hairline); margin: 0; }

.trail-event {
  position: relative;
  padding: var(--space-3) var(--space-4) var(--space-3) var(--space-6);
  border-bottom: 1px solid var(--border-hairline);
}
.trail-event:last-child { border-bottom: 0; }
.trail-event::before {
  content: ""; position: absolute; left: var(--space-4); top: 1.35em;
  width: 7px; height: 7px; border-radius: var(--radius-pill);
  background: var(--accent);
}
.trail-event.refused::before { background: var(--brand-danger); }
.trail-event .trail-meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
  font-size: var(--text-body-sm); color: var(--text-muted); margin-bottom: 2px;
}
.trail-event .trail-meta time { font-family: var(--font-mono); }

/* Before/after. Old is muted and struck through; new is body ink with an accent rule - the accent is
   a POINTER to the current value, never approval. Turning governance on is good and turning it off is
   bad, and CSS cannot know which, so neither gets a semantic colour. */
.diff { display: grid; grid-template-columns: minmax(5rem, max-content) 1fr; gap: var(--space-1) var(--space-3); margin: var(--space-2) 0 0; }
.diff-field {
  font-family: var(--font-mono); font-size: var(--text-eyebrow);
  letter-spacing: .06em; text-transform: uppercase; color: var(--text-muted);
}
.diff-old, .diff-new { margin: 0; min-width: 0; }
.diff-old .tenant-text { color: var(--text-muted); border-left-color: var(--border-strong); }
.diff-new .tenant-text { border-left-color: var(--accent); }

/* A finding is a block, not a table row: it carries a headline, a sentence, evidence and two
   decisions, and none of that fits a column. */
.finding { padding: var(--space-4); border-bottom: 1px solid var(--border-hairline); }
.finding:last-child { border-bottom: 0; }
/* Settled findings recede but are never hidden: a decision someone made is part of the record. */
.finding.settled { background: var(--surface-inset); }
.finding-head {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
}
.finding-head .spacer { flex: 1 1 auto; }
.finding-detail { margin: var(--space-2) 0 0; max-width: 80ch; color: var(--text-secondary); }
.finding-meta {
  display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-4);
  margin-top: var(--space-2); font-size: var(--text-body-sm); color: var(--text-muted);
}
.catalogue-toggle { cursor: pointer; padding: var(--space-3) var(--space-4); }

/* A list of sentences, one per finding. Not a table: these are prose, and a one-column table is a
   list wearing a costume. */
.stack { list-style: none; margin: 0; padding: 0; }
.stack > li {
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--border-hairline);
  color: var(--text-secondary); max-width: 90ch;
}
.stack > li:last-child { border-bottom: 0; }

/* Key/value facts. A definition list because that is what it is. */
.kv { display: grid; grid-template-columns: minmax(9rem, max-content) 1fr; gap: var(--space-1) var(--space-4); margin: 0; }
.kv dt {
  font-family: var(--font-mono); font-size: var(--text-eyebrow);
  letter-spacing: .08em; text-transform: uppercase; color: var(--text-muted);
}
.kv dd { margin: 0; }

/* A status matrix: fixed-width state columns so the eye can run down one, with the identity column
   taking the slack. */
table.matrix th:not(:first-child), table.matrix td:not(:first-child) { width: 6.5rem; }

/* Visually hidden but read aloud. Required wherever colour or shape is the primary channel. */
.sr {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* The system is border-led rather than shadow-led, so honouring a contrast preference is
   two lines and actually works. */
@media (prefers-contrast: more) {
  .card, .pill, .btn, .tiles { border-width: 2px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; }
}

/* A switch that cannot be operated is not rendered as a control at all - see Database.cshtml. It keeps
   the switch's footprint so the rows still line up, but reads as a state rather than an affordance. */
.switch.off {
  display: inline-block; width: 38px; height: 21px; flex: 0 0 auto;
  border-radius: var(--radius-pill); border: 1px dashed var(--border-strong);
  background: var(--surface-inset);
}

/* The theme toggle. Three explicit choices rather than a cycling switch, because "auto" - follow the
   operating system - is a real state a two-way toggle cannot express, and because a control whose
   meaning depends on hidden state is ambiguous to a screen reader. */
.theme { margin-top: var(--space-3); }
.theme fieldset { border: 0; margin: 0; padding: 0; display: flex; gap: 2px; }
.theme button {
  flex: 1 1 auto; appearance: none; cursor: pointer;
  background: none; border: 1px solid transparent; color: var(--rail-ink-2);
  font-family: var(--font-mono); font-size: 10px;
  letter-spacing: var(--tracking-eyebrow-wide); text-transform: uppercase;
  padding: 3px 4px; border-radius: var(--radius-sharp);
  transition: var(--transition-base);
}
.theme button:hover { color: #fff; background: rgba(255,255,255,.07); }
.theme button.on { color: #fff; border-color: rgba(255,255,255,.25); background: rgba(255,255,255,.1); }

/* Field hint: the sentence under a control that says why it is shaped that way. Distinct from a label
   and from an error, so all three can be present at once. */
.field-hint {
  display: block; margin-top: 4px;
  font-size: var(--text-body-sm); color: var(--text-muted); max-width: 78ch;
}
.field.spaced { margin-top: var(--space-4); }
.field input[readonly] { background: var(--surface-inset); color: var(--text-muted); }

/* A retired definition. Kept visible because an answer already given may name it, but clearly past
   tense - and NOT dimmed to the point of looking decorative. */
.tool-row.retired { background: var(--surface-inset); }
.tool-row.retired .ident { text-decoration: line-through; text-decoration-color: var(--text-muted); }

/* Tiles inside a card body, used by the governance confirmation. Spaced from the prose above rather
   than flush, because they are an aside to it rather than the next section. */
.tiles.spaced { margin-top: var(--space-4); }

/* A checkbox and its label on one line, sized to the control rather than to the column. A switch that
   arms a credential reads better as a sentence than as a labelled box, and it must sit in the same
   .filters row as the text inputs without stretching to their width. */
.check { display: flex; align-items: center; gap: var(--space-2); flex: 0 0 auto; }
.check input[type="checkbox"] { width: 1rem; height: 1rem; accent-color: var(--accent); margin: 0; }
.check label { margin: 0; cursor: pointer; }
/* Level with the inputs, by the same offset as a button - not centred, which only looked right while
   the row was bottom-aligned and every field happened to be the same height. */
.filters .check { margin-top: var(--control-offset); }

/* A form section inside a card body, separated from the one above it. Used where a table is followed
   by the form that adds to it. */
.subform { border-top: 1px solid var(--border-hairline); }
.subform h3 {
  font-size: var(--text-body); font-weight: 600; color: var(--text-secondary);
  margin: 0 0 var(--space-3) 0;
}

/* A connection whose system has no adapter yet. Recorded, reviewable, and unreadable - which the row
   must say plainly, because a row that looks provisioned and refuses at call time is the worst of both. */
.pill.planned { border-style: dashed; }

/* Breadcrumb. Above the page title, small and quiet: it says where you are, and the title says what
   you are looking at, so the trail must not compete with it.

   The separator is a ::before on every li except the first, rather than a character in the markup - a
   literal "/" between links is read aloud by a screen reader as "slash" on every step. */
.crumbs { margin-bottom: 6px; }
.crumbs ol { display: flex; flex-wrap: wrap; align-items: center; gap: 0; list-style: none; margin: 0; padding: 0; }
.crumbs li { display: flex; align-items: center; font-size: var(--text-body-sm); color: var(--text-muted); }
.crumbs li + li::before {
  content: "/"; margin: 0 var(--space-2); color: var(--border-strong); speak: never;
}
.crumbs a { color: var(--text-muted); text-decoration: none; }
.crumbs a:hover { color: var(--accent); text-decoration: underline; }
.crumbs [aria-current="page"] { color: var(--text-secondary); }

/* A group heading inside a list of rows - used where a cross-client table is grouped by client, so a
   host admin reads "whose" before "which". */
.grouphead {
  padding: var(--space-3) var(--space-4);
  background: var(--surface-inset); border-bottom: 1px solid var(--border-hairline);
  display: flex; align-items: center; gap: var(--space-2);
}
.grouphead b { font-size: var(--text-body); font-weight: 600; }
.grouphead .spacer { flex: 1 1 auto; }

/* A group heading row inside a table. A th with scope="colgroup" rather than a styled td, so the
   grouping is structural: a screen reader announces it as a heading over the rows beneath instead of
   reading a client's name as if it were a data cell. */
.grouprow {
  background: var(--surface-inset); text-align: left; font-weight: 600;
  padding: var(--space-2) var(--space-4);
}
.grouprow a { color: var(--text-body); }
.grouprow a:hover { color: var(--accent); }
.grouprow .sub2 { margin-left: var(--space-2); font-weight: 400; }

/* Page sections as tabs, attached under the topbar.

   Real links to real URLs, not scroll anchors: the section survives a refresh, a bookmark and the
   POST-redirect after a form, and the back button walks the sections a person actually visited. The
   active tab is rendered by the server, because "which section is this" is something it already knows -
   the alternative is a scroll-spy, which needs JavaScript this console does not have. */
.tabs {
  display: flex; flex-wrap: wrap; gap: var(--space-1);
  padding: 0 var(--space-4);
  border-bottom: 1px solid var(--border-hairline);
  background: var(--surface-card);
}
.tabs a {
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-3) var(--space-3);
  color: var(--text-muted); text-decoration: none;
  /* Reserved on every tab, not added to the active one, so switching tabs does not shift the row. */
  border-bottom: 2px solid transparent;
  transition: var(--transition-base);
}
.tabs a:hover { color: var(--text-body); }
.tabs a.on { color: var(--text-body); border-bottom-color: var(--accent); font-weight: 500; }

/* The count beside a label. Tabular figures so a two-digit tally does not nudge the label, and a
   min-width so 1 and 11 occupy the same box. */
.tabs .tally {
  min-width: 1.5rem; padding: 1px 6px; border-radius: var(--radius-pill);
  background: var(--surface-inset); color: var(--text-muted);
  font-family: var(--font-mono); font-size: var(--text-eyebrow); font-variant-numeric: tabular-nums;
  text-align: center;
}
.tabs .tally.warn { background: var(--wash-amber); color: var(--brand-warning); }
.tabs a.on .tally { color: var(--text-secondary); }

/* A column of checkboxes - a tool picker, as opposed to the single inline .check that sits in a filter
   row. Wraps into columns so a catalogue of a dozen tools is not a dozen lines of scrolling. */
.checks {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
  gap: var(--space-2); margin-bottom: var(--space-3);
}
.checks .check { margin-top: 0; }
.checks .check label { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
