/* ============================================================================
   styles.css — AI Central.

   Built entirely on the custom properties tokens.css defines, so the app inherits
   Geocoding's design system and all 28 themes without duplicating any colour.
   Never hard-code a colour here; if a token is missing, add it to tokens.css in
   BOTH apps so the two do not drift.
   ============================================================================ */

* { box-sizing: border-box; }

html, body {
  margin: 0; padding: 0; height: 100%;
  background: var(--bg); color: var(--txt);
  font-family: Inter, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 14px; -webkit-font-smoothing: antialiased;
}

a { color: var(--ink); text-decoration: none; }
kbd, code, .mono { font-family: "JetBrains Mono", ui-monospace, monospace; }

/* ── Top bar ───────────────────────────────────────────────────────────── */

.topbar {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  height: 52px; padding: 0 14px;
  background: var(--pane); border-bottom: 1px solid var(--rule);
  position: sticky; top: 0; z-index: 40;
}
.tb-left, .tb-right { display: flex; align-items: center; gap: 14px; }
.brand .mk { height: 26px; display: block; }

.tabs { display: flex; gap: 2px; }
.tab {
  padding: 6px 12px; border-radius: 6px; cursor: pointer; border: 1px solid transparent;
  color: var(--txt2); font-weight: 600; font-size: 13px; background: none;
}
.tab:hover { background: var(--hover); color: var(--txt); }
.tab.on { background: var(--sel); color: var(--a-over); border-color: var(--rule2); }

.clock { text-align: right; line-height: 1.1; font-family: "JetBrains Mono", monospace; }
.clock span { font-size: 13px; color: var(--txt); }
.clock small { display: block; font-size: 10px; color: var(--txt3); }

/* Live pulse in the top bar: the platform-wide "an agent is working" signal. */
.livebadge {
  display: flex; align-items: center; gap: 7px;
  padding: 5px 10px; border: 1px solid var(--rule); border-radius: 4px;
  background: var(--pane2); font-size: 12px; color: var(--txt2); font-weight: 600;
}
.livedot { width: 8px; height: 8px; border-radius: 50%; background: var(--txt3); }
.livebadge.live { color: var(--pos); border-color: var(--pos); }
.livebadge.live .livedot { background: var(--pos); animation: pulse 1s ease-in-out infinite; }

@keyframes pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: .35; transform: scale(.8); }
}

/* ── Theme picker (same interaction as Geocoding) ──────────────────────── */

.thpick { position: relative; }
.thbtn {
  display: flex; align-items: center; gap: 7px; cursor: pointer;
  padding: 5px 10px; border: 1px solid var(--rule); border-radius: 6px;
  background: var(--pane2); color: var(--txt2); font-size: 12px; font-weight: 600;
}
.thbtn:hover { background: var(--hover); color: var(--txt); }
.thsw { width: 12px; height: 12px; border-radius: 3px; border: 1px solid var(--rule2); }
.thmenu {
  display: none; position: absolute; right: 0; top: 34px; z-index: 60;
  background: var(--pane); border: 1px solid var(--rule); border-radius: 8px;
  box-shadow: var(--shadow); padding: 6px; max-height: 60vh; overflow-y: auto; min-width: 190px;
}
.thmenu.open { display: block; }
.thmenu button {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 6px 8px; border: 0; border-radius: 5px; cursor: pointer;
  background: none; color: var(--txt2); font-size: 12px; text-align: left;
}
.thmenu button:hover { background: var(--hover); color: var(--txt); }

/* ── Layout ────────────────────────────────────────────────────────────── */

/* Sub-tab bar for a screen that contains other screens (Settings).

   Same pattern as TPL CRM and AutoIQ: a full-bleed band on --head, flat uppercase labels, and a 2px
   accent underline marking the active one. Deliberately NOT the pill treatment the main tab bar uses
   -- the two bars sit directly above one another, and giving them the same shape made it ambiguous
   which level a click would act on. Different shape, same family.

   Hidden with display:none rather than left empty so it takes no vertical space on every other
   screen; an empty band with padding and a border would draw a stray line under the top bar. */
.subtabs { display: none; }
.subtabs.on {
  display: flex; align-items: stretch; gap: 0;
  padding: 0 18px;
  background: var(--head); border-bottom: 1px solid var(--rule);
}
.subtab {
  background: none; border: 0; border-bottom: 2px solid transparent;
  color: var(--txt2); font: inherit; font-size: 11px; font-weight: 700;
  letter-spacing: .4px; text-transform: uppercase;
  padding: 10px 14px; white-space: nowrap; cursor: pointer;
}
.subtab:hover { color: var(--txt); }
.subtab.on { color: var(--amber); border-bottom-color: var(--amber); }

/* The band already supplies the separation above the screen heading. */
.subtabs.on + #view { padding-top: 14px; }

/* ── Dense table ──────────────────────────────────────────────────────────────────────────────────
   The CRM/AutoIQ reading pattern: many rows visible at once, scannable down a column. Used where the
   question is "find the one row I care about" (audit log, users) rather than "read this one thing"
   (an agent card). Tighter padding and a smaller, heavier, uppercase header than the default table,
   because at this density the header has to read as chrome rather than as a first row. */
.dtable { width: 100%; border-collapse: collapse; font-size: 12px; }
.dtable thead th {
  position: sticky; top: 0; z-index: 1; background: var(--head);
  text-align: left; font-size: 9.5px; font-weight: 800; letter-spacing: .7px;
  text-transform: uppercase; color: var(--txt3); white-space: nowrap;
  padding: 7px 10px; border-bottom: 1px solid var(--rule);
}
.dtable tbody td {
  padding: 6px 10px; border-bottom: 1px solid var(--rule);
  color: var(--txt2); vertical-align: middle;
}
.dtable tbody tr:last-child td { border-bottom: 0; }
.dtable tbody tr:hover td { background: var(--hover); }
.dtable tbody tr.clickable { cursor: pointer; }
.dtable td.nowrap, .dtable th.nowrap { white-space: nowrap; }
.dtable td.num { text-align: right; font-family: "JetBrains Mono", monospace; }
/* The one column that may wrap. Everything else stays on one line so rows keep a constant height and
   the eye can track down a column. */
.dtable td.wrap { white-space: normal; }
.dtable .t-mono { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--txt3); }
.dtable .t-strong { color: var(--txt); font-weight: 600; }

#view { padding: 18px; max-width: 1500px; margin: 0 auto; }

.screen-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 16px; margin-bottom: 14px;
}
.screen-head h1 { margin: 0; font-size: 19px; font-weight: 700; color: var(--txt); }
.screen-head .sub { color: var(--txt3); font-size: 12px; }

.grid { display: grid; gap: 12px; }
.grid.cards { grid-template-columns: repeat(auto-fill, minmax(310px, 1fr)); }

.panel {
  background: var(--pane); border: 1px solid var(--rule); border-radius: 10px; padding: 14px;
}

/* ── Agent cards ───────────────────────────────────────────────────────── */

.agent {
  background: var(--pane); border: 1px solid var(--rule); border-radius: 10px;
  padding: 13px 14px; cursor: pointer; position: relative; overflow: hidden;
  transition: border-color .15s, transform .1s;
}
.agent:hover { border-color: var(--rule2); transform: translateY(-1px); }

/* The flashing state. A running agent has to be obvious from across the room, so it gets a
   moving sweep as well as a pulsing dot — colour alone reads as static on a glance. */
.agent.live { border-color: var(--pos); }
.agent.live::after {
  content: ""; position: absolute; inset: 0 auto 0 0; width: 100%; height: 2px;
  background: linear-gradient(90deg, transparent, var(--pos), transparent);
  animation: sweep 1.4s linear infinite;
}
@keyframes sweep {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(100%); }
}

/* ── Mapping review ───────────────────────────────────────────────────────────────────────────────
   Source on the left, destination on the right, an arrow between them: the shape of the decision
   being made, so a reviewer reads a row rather than parsing one.

   Rows still needing a decision are marked and sorted to the top. An unmapped field is the only thing
   on this screen that requires action, and a reviewer who has to hunt for them will approve without
   finding them -- which defeats the point of a human gate. */
.maptable .arrow { width: 26px; text-align: center; color: var(--txt3); }
.maptable td { vertical-align: middle; }
.maptable select { font-size: 12px; padding: 3px 6px; }
.maptable tr.needs td { background: color-mix(in srgb, var(--warn) 9%, transparent); }
.maptable tr.needs .arrow { color: var(--warn); font-weight: 700; }
.maptable tr.needs td:first-child { box-shadow: inset 2px 0 0 var(--warn); }

/* ── Agent scope panel ────────────────────────────────────────────────────────────────────────────
   Shown at the top of an agent's drawer, above its history. The limit line carries an accent edge
   because "what this will never do" is the sentence people actually need before trusting an agent to
   run unattended against the CRM, and it reads as boilerplate without one. */
.scope-purpose { margin: 0 0 9px; font-size: 13px; color: var(--txt); line-height: 1.5; }
.scope-limit {
  font-size: 12px; color: var(--txt2); line-height: 1.5;
  border-left: 2px solid var(--amber); padding-left: 9px; margin-bottom: 11px;
}
.scope-limit strong { color: var(--txt); }
.scope-io { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.scope-io h5, .scope-objects h5 {
  margin: 0 0 4px; font-size: 9.5px; font-weight: 800; letter-spacing: .7px;
  text-transform: uppercase; color: var(--txt3);
}
.agent-io { margin: 0; padding-left: 15px; font-size: 11.5px; color: var(--txt2); line-height: 1.55; }
.scope-objects { margin-top: 11px; }

/* ── The chain, drawn as a live wire ──────────────────────────────────────────────────────────────
   The screen used to be eleven cards in a grid, which showed WHAT exists and nothing about the order
   they run in -- the single most important fact about this platform. This draws the order, and lights
   the wires while anything is running so the page reads as a system doing work rather than a list.

   Wraps rather than scrolls: eight stages fit on a wide screen and fold to two rows on a laptop, and a
   horizontally-scrolling diagram hides exactly the end of the chain people forget about. */
.flow {
  background: var(--pane); border: 1px solid var(--rule); border-radius: 10px;
  padding: 13px 14px; margin-bottom: 14px;
}
.flow-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 11px; }
.flow-title {
  font-size: 11px; font-weight: 800; letter-spacing: .7px; text-transform: uppercase; color: var(--txt3);
}
.flow-rail { display: flex; align-items: stretch; flex-wrap: wrap; gap: 0; }

.node {
  display: flex; flex-direction: column; gap: 2px; align-items: flex-start;
  min-width: 108px; padding: 8px 11px; cursor: pointer; text-align: left;
  background: var(--pane2); border: 1px solid var(--rule); border-radius: 6px;
  font: inherit; color: var(--txt2);
}
.node:hover { border-color: var(--rule2); background: var(--hover); }
.node-pos {
  font-family: "JetBrains Mono", monospace; font-size: 9px; font-weight: 700; color: var(--txt3);
}
.node-label { font-size: 12.5px; font-weight: 700; color: var(--txt); }
.node-state { font-size: 10px; color: var(--txt3); }

/* State reads from the border and the label, never from fill alone -- a filled node at eight-across
   turns the rail into a colour bar and stops being scannable. */
.node.ok   { border-color: var(--rule2); }
.node.bad  { border-color: var(--neg); }
.node.bad .node-state { color: var(--neg); }
.node.idle { opacity: .72; border-style: dashed; }
.node.live {
  border-color: var(--pos); color: var(--txt);
  box-shadow: 0 0 0 1px var(--pos) inset, 0 0 14px -4px var(--pos);
  animation: nodebeat 1.2s ease-in-out infinite;
}
.node.live .node-state { color: var(--pos); font-weight: 700; }
@keyframes nodebeat {
  0%, 100% { box-shadow: 0 0 0 1px var(--pos) inset, 0 0 14px -4px var(--pos); }
  50%      { box-shadow: 0 0 0 1px var(--pos) inset, 0 0 20px -2px var(--pos); }
}

/* The wire between two stages. Static grey at rest; while anything in the platform is running, a
   pulse travels along it in the direction data actually moves, left to right. */
.wire {
  align-self: center; flex: 0 0 26px; height: 2px; margin: 0 2px;
  background: var(--rule2); border-radius: 2px; position: relative; overflow: hidden;
}
.wire.flowing::after {
  content: ""; position: absolute; inset: 0; width: 40%;
  background: linear-gradient(90deg, transparent, var(--pos), transparent);
  animation: wireflow 1.1s linear infinite;
}
@keyframes wireflow {
  0%   { transform: translateX(-120%); }
  100% { transform: translateX(320%); }
}

/* On a narrow screen the rail wraps, and a wire that lands at the start of a new row points at
   nothing. Hidden there rather than left dangling. */
@media (max-width: 820px) {
  .wire { flex-basis: 14px; }
}

@media (prefers-reduced-motion: reduce) {
  .node.live { animation: none; }
  .wire.flowing::after { animation: none; opacity: .55; }
}

/* The orchestrator. It owns the order every other agent runs in, so it gets the lead treatment: full
   width at the top of the grid and an accent edge, rather than sitting as one card among eleven. */
.agent.lead {
  grid-column: 1 / -1;
  border-left: 3px solid var(--amber);
  background: linear-gradient(180deg, var(--head), var(--pane) 70%);
}
.agent.lead .agent-name { font-size: 16px; }

/* Running has to be obvious from across the room, so it is stated three ways: the card sweeps, the
   chip pulses, and the dot beats. Colour alone reads as static on a glance, and colour plus one
   animation was still easy to miss on a grid of eleven cards. */
.rundot {
  width: 7px; height: 7px; border-radius: 50%; background: var(--pos);
  display: inline-block; animation: pulse 1s ease-in-out infinite;
}
.chip.live { animation: livechip 1.6s ease-in-out infinite; }
@keyframes livechip {
  0%, 100% { border-color: var(--pos); box-shadow: 0 0 0 0 transparent; }
  50%      { border-color: var(--pos); box-shadow: 0 0 0 3px color-mix(in srgb, var(--pos) 22%, transparent); }
}

/* A running agent lifts slightly and keeps the sweep. Reduced-motion users get the colour and the
   border only -- the state must still be readable without any animation at all. */
.agent.live { box-shadow: 0 0 0 1px var(--pos) inset; }

@media (prefers-reduced-motion: reduce) {
  .agent.live::after, .rundot, .chip.live { animation: none; }
}

.agent-top { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.agent-name { font-weight: 700; font-size: 14px; color: var(--txt); }
.agent-id { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--txt3); }
.agent-meta { margin-top: 9px; font-size: 12px; color: var(--txt2); line-height: 1.55; }
.agent-last { margin-top: 8px; font-size: 12px; color: var(--txt3); border-top: 1px dashed var(--rule); padding-top: 8px; }

/* --a-over, not --ink. Fourth instance of the same trap: --ink is near-white in the light themes, so
   the stage name on a --sel fill rendered as a pale smudge. */
.stage {
  font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  padding: 2px 7px; border-radius: 4px; background: var(--sel); color: var(--a-over);
}

/* ── Chips / status ──────────────────────────────────────────────────────
   Rectangular, not pill-shaped. Every other edge in this UI is a 4-10px radius -- tables, panels,
   cards, inputs -- and fully-round chips read as a different design language pasted in, which was
   most obvious in the audit log where a column of them sits against square table cells. */

.chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 700;
  border: 1px solid var(--rule); color: var(--txt2); background: var(--pane2);
  white-space: nowrap;
}
.chip.ok       { color: var(--pos);  border-color: var(--pos); }
.chip.bad      { color: var(--neg);  border-color: var(--neg); }
.chip.warn     { color: var(--warn); border-color: var(--warn); }
.chip.muted    { color: var(--txt3); }
.chip.live     { color: var(--pos);  border-color: var(--pos); }

/* ── Tables ────────────────────────────────────────────────────────────── */

.tablewrap { overflow-x: auto; border: 1px solid var(--rule); border-radius: 10px; background: var(--pane); }
table { border-collapse: collapse; width: 100%; font-size: 13px; }
thead th {
  position: sticky; top: 0; background: var(--head); z-index: 1;
  text-align: left; font-size: 11px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--txt3);
  padding: 9px 11px; border-bottom: 1px solid var(--rule);
}
tbody td { padding: 9px 11px; border-bottom: 1px solid var(--rule); color: var(--txt2); vertical-align: top; }
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover td { background: var(--hover); }
td.num { text-align: right; font-family: "JetBrains Mono", monospace; }

/* ── Buttons and forms ─────────────────────────────────────────────────── */

.btn {
  padding: 6px 12px; border-radius: 6px; cursor: pointer; font-size: 12px; font-weight: 600;
  border: 1px solid var(--rule); background: var(--pane2); color: var(--txt);
}
.btn:hover { background: var(--hover); border-color: var(--rule2); }
/* Coloured from --a-over, NOT --ink — the same fix already applied to .authbtn.go, which this class
   was missed by. --ink means "text that contrasts with an ACCENT FILL" and is therefore light in the
   light themes; on the --sel fill used here that rendered #f7f8fa on #d6e0ea, a contrast ratio of
   about 1.2:1. The button was there, sized and clickable, and all but invisible — which reads as a
   disabled control rather than the primary action. --a-over is defined to contrast with the pane in
   every one of the 28 themes. */
.btn.primary { background: var(--sel); color: var(--a-over); border-color: var(--a-over); }
.btn:disabled { opacity: .45; cursor: not-allowed; }
.btn.sm { padding: 3px 9px; font-size: 11px; }

input[type=text], input[type=password], input[type=number], select {
  padding: 6px 9px; border-radius: 6px; font-size: 13px;
  border: 1px solid var(--rule); background: var(--bg2); color: var(--txt);
  font-family: inherit; width: 100%;
}
input:focus, select:focus { outline: none; border-color: var(--ink); }
label.fld { display: block; margin-bottom: 11px; }
label.fld .lbl { display: block; font-size: 11px; font-weight: 700; color: var(--txt3); margin-bottom: 4px; text-transform: uppercase; letter-spacing: .04em; }
label.fld .hint { display: block; font-size: 11px; color: var(--txt3); margin-top: 4px; line-height: 1.45; }

/* ── Activity feed ─────────────────────────────────────────────────────── */

.act { border-bottom: 1px solid var(--rule); padding: 10px 12px; }
.act:last-child { border-bottom: 0; }
.act-head { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.act-when { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--txt3); }
.act-sum { margin-top: 5px; color: var(--txt); font-size: 13px; }
.act-actor { font-size: 11px; color: var(--txt3); }
.act-more { margin-top: 7px; }
.act-more summary { cursor: pointer; font-size: 11px; color: var(--txt3); }
.act-more summary:hover { color: var(--ink); }
.diff { display: grid; grid-template-columns: 1fr 1fr; gap: 9px; margin-top: 8px; }
.diff pre {
  margin: 0; padding: 8px; border-radius: 6px; overflow-x: auto;
  background: var(--bg2); border: 1px solid var(--rule);
  font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--txt2);
}
.diff .before { border-left: 2px solid var(--neg); }
.diff .after  { border-left: 2px solid var(--pos); }
.diff h5 { margin: 0 0 4px; font-size: 10px; color: var(--txt3); text-transform: uppercase; letter-spacing: .05em; }

/* ── Detail drawer ─────────────────────────────────────────────────────── */

.detailwin {
  display: none; position: fixed; top: 52px; right: 0; bottom: 0; width: min(660px, 94vw);
  background: var(--pane); border-left: 1px solid var(--rule); box-shadow: var(--shadow);
  z-index: 50; overflow-y: auto;
}
.detailwin.open { display: block; }
.dw-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 13px 15px; border-bottom: 1px solid var(--rule);
  position: sticky; top: 0; background: var(--pane); z-index: 2;
}
.dw-head h2 { margin: 0; font-size: 15px; color: var(--txt); }
.dw-body { padding: 13px 15px; }

/* ── Toasts ────────────────────────────────────────────────────────────── */

#toasts { position: fixed; bottom: 18px; right: 18px; z-index: 90; display: flex; flex-direction: column; gap: 8px; }
.toast {
  padding: 9px 13px; border-radius: 7px; font-size: 13px;
  background: var(--pane); border: 1px solid var(--rule); border-left-width: 3px;
  color: var(--txt); box-shadow: var(--shadow); max-width: 430px;
}

/* ── Boot splash ───────────────────────────────────────────────────────── */

#boot {
  position: fixed; inset: 0; z-index: 100; background: var(--bg);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 9px;
  transition: opacity .3s;
}
#boot.gone { opacity: 0; pointer-events: none; }
#boot .bmk { height: 46px; }
#boot .bt { font-size: 17px; font-weight: 800; letter-spacing: .16em; color: var(--txt); }
#boot .bs { font-size: 12px; color: var(--txt3); }
#boot .bootlog { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--txt3); margin-top: 7px; }

/* Budget screen. A spend figure and a fill bar, because "are we near the limit" should be readable
   without parsing two numbers and dividing them. */
.bigfig {
  font-family: "JetBrains Mono", monospace;
  font-size: 30px; font-weight: 700; line-height: 1.1;
  color: var(--txt); margin: 6px 0 3px;
  font-variant-numeric: tabular-nums;
}
.meter {
  height: 7px; border-radius: 999px; background: var(--rule);
  overflow: hidden; margin: 10px 0 7px;
}
.meter > span { display: block; height: 100%; background: var(--pos); transition: width .3s ease; }
.meter.warn > span { background: var(--warn); }
.meter.bad  > span { background: var(--neg); }

.empty { padding: 34px; text-align: center; color: var(--txt3); font-size: 13px; }
.muted { color: var(--txt3); }
.right { text-align: right; }
.row { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.spacer { flex: 1; }

/* Signed-in user. Reads from /api/me so the role shown is the one actually driving permissions,
   not something the front end guessed. */
.whoami {
  font-size: 12px; color: var(--txt2); padding: 5px 10px;
  border: 1px solid var(--rule); border-radius: 6px; background: var(--pane2);
  max-width: 240px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.whoami b { color: var(--txt); font-weight: 700; }

/* Sign in / out. Matches .whoami's height and border so the header reads as one row rather than a
   button bolted next to a label. Empty and invisible when sign-in is off — see renderAuthControl.

   Coloured from --txt and --a-over, NOT --ink. That token means "text that contrasts with an accent
   fill" and is therefore LIGHT in the light themes: in coolgray both --ink and --pane2 are #f7f8fa, so
   an --ink button on a --pane2 background rendered white-on-white with a white border. The button was
   there, sized and clickable, and completely invisible — which looked exactly like the app failing to
   load. --txt and --a-over are defined to contrast with the pane in every theme. */
.authctl:empty { display: none; }
.authbtn {
  font: inherit; font-size: 12px; cursor: pointer;
  color: var(--txt); background: var(--pane2);
  padding: 5px 12px; border: 1px solid var(--rule); border-radius: 6px;
}
.authbtn:hover { background: var(--hover); border-color: var(--rule2); }
/* The way in, when there is no other way in — carries the accent so it reads as the next action. */
.authbtn.go { color: var(--a-over); border-color: var(--a-over); font-weight: 600; }
.authbtn.go:hover { background: var(--sel); }
/* Sign-in configured but unavailable. Warn-coloured rather than error-coloured: nothing is broken in
   the data, the browser simply cannot reach the sign-in library. */
.authwarn {
  font-size: 12px; color: var(--warn); background: var(--pane2);
  padding: 5px 10px; border: 1px solid var(--warn); border-radius: 6px; white-space: nowrap;
}
input[type=checkbox] { width: 15px; height: 15px; accent-color: var(--ink); cursor: pointer; }
input[type=checkbox]:disabled { cursor: not-allowed; opacity: .45; }

/* ── Users & Roles ────────────────────────────────────────────────────────────────────────────────
   Ported from Geocoding's radar/users.js so the identity screen reads the same in both applications:
   the same segmented control, the same filter row, the same permissions modal. Somebody who
   administers one should not have to learn the other. */

.filters { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; margin-bottom: 12px; }
.filters input[type=text] { min-width: 240px; }

/* Segmented control: one choice out of a few, always visible. Preferred over a <select> here
   because the options are the screen's main axis and hiding them behind a click would bury them. */
.seg { display: inline-flex; border: 1px solid var(--rule); border-radius: 7px; overflow: hidden; }
.seg button {
  padding: 5px 11px; border: 0; background: var(--pane); cursor: pointer;
  color: var(--txt2); font: inherit; font-size: 12px; font-weight: 600;
  border-right: 1px solid var(--rule);
}
.seg button:last-child { border-right: 0; }
.seg button:hover { background: var(--hover); color: var(--txt); }
.seg button.on { background: var(--sel); color: var(--ink); }

/* Row-level action. Smaller and quieter than .btn: these repeat on every row, and at .btn weight a
   40-row table reads as a wall of buttons. */
.miniact {
  padding: 3px 9px; border: 1px solid var(--rule); border-radius: 6px; background: var(--pane2);
  color: var(--txt2); font: inherit; font-size: 11px; font-weight: 600; cursor: pointer;
}
.miniact:hover { background: var(--hover); border-color: var(--rule2); color: var(--txt); }
.miniact.go { color: var(--a-over); border-color: var(--a-over); }
.miniact:disabled { opacity: .45; cursor: not-allowed; }

/* How much of the catalog a role holds. A bar rather than a number because the useful reading is
   "nearly everything" vs "barely anything", which a bare "9 of 14" makes you compute. */
.permbar { height: 5px; border-radius: 3px; background: var(--rule); overflow: hidden; }
/* --a-over, not --ink, for the third time on this screen: --ink is near-white in the light themes,
   so the filled portion vanished against its own track and a role holding every permission looked
   identical to one holding none. */
.permbar > span { display: block; height: 100%; background: var(--a-over); }

/* Modal. Roles get one instead of the old permission matrix: the catalog is expected to grow, and a
   grid of every permission against every role stops being readable as soon as either axis does. */
.modal-ov {
  position: fixed; inset: 0; z-index: 60; display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, .45); padding: 24px;
}
.modal-card {
  background: var(--pane); border: 1px solid var(--rule2); border-radius: 12px;
  width: min(660px, 100%); max-height: 88vh; display: flex; flex-direction: column;
  box-shadow: 0 18px 48px rgba(0, 0, 0, .3);
}
.modal-head { padding: 14px 16px; border-bottom: 1px solid var(--rule); }
.modal-head h2 { margin: 0; font-size: 15px; font-weight: 700; color: var(--txt); }
.modal-head .sub { color: var(--txt3); font-size: 12px; margin-top: 3px; }
.modal-body { padding: 14px 16px; overflow-y: auto; }
.modal-acts {
  padding: 11px 16px; border-top: 1px solid var(--rule);
  display: flex; gap: 9px; justify-content: flex-end; align-items: center;
}
.modal-err { color: var(--neg); font-size: 12px; margin-right: auto; }

/* Permission list inside the modal: group heading, then one row per permission with its plain
   description and its key. The description is the point — "credentials.manage" alone tells an
   approver nothing about what they are handing over. */
.permgroup {
  display: flex; align-items: center; justify-content: space-between; gap: 9px;
  padding: 9px 2px 5px; font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .05em; color: var(--txt3);
}
.permrow {
  display: flex; gap: 10px; align-items: flex-start; padding: 7px 8px; border-radius: 7px;
  cursor: pointer;
}
.permrow:hover { background: var(--hover); }
.permrow input { margin-top: 2px; flex: none; }
.permlabel { display: block; font-size: 13px; color: var(--txt); font-weight: 600; }
.permdesc { display: block; font-size: 11px; color: var(--txt3); margin-top: 2px; }
.permkey {
  display: block; font-family: "JetBrains Mono", monospace; font-size: 10px;
  color: var(--txt3); margin-top: 2px;
}

/* ── Audit log actor badges ───────────────────────────────────────────────────────────────────────
   Who acted, colour-coded by kind. An agent running on a schedule and a person clicking a button are
   different in a way that matters when reading an audit trail, and both used to render as plain text.
   Coloured from --cyan / --amber / --txt3, which are defined in every theme, rather than from --ink,
   which is light in the light themes and would render white-on-white. */
.actor-badge {
  font-family: var(--mono); font-size: 11px; font-weight: 700; letter-spacing: .02em;
  padding: 2px 8px; border-radius: 4px; border: 1px solid currentColor; white-space: nowrap;
}
.actor-badge.act-agent  { color: var(--cyan); }
.actor-badge.act-user   { color: var(--amber); }
.actor-badge.act-system { color: var(--txt3); }
