/* ============================================================
   zenpAI Arbeitsbereich — shell layout: the 68px header, the transcript/rail
   grid, the rail's tab chrome, the identity popover and the off-canvas drawer
   at 1100px and under. Companion to assets/zpa-app.css, which owns every colour, radius,
   hairline and type scale used below; this file adds none of its own.
   No CDN, no web font, no external reference of any kind.

   Where one region of the shell meets another — the header and the body, the
   sidebar and the views — the boundary is drawn with --linie-struktur. --linie
   stays for rhythm inside a region. The transcript and the composer are the
   one pair with no line between them, and .composer-bar says why.
   ============================================================ */

/* The app is a fixed viewport with exactly one scroll container per column;
   the document itself never scrolls. `height:100%` has to be stated on both
   html and body, because a percentage height resolves against the parent's
   *used* height and the initial containing block is the only ancestor with
   one.

   Leptos mounts into <body> (`mount_to_body`), so body IS the shell's root
   element and carries the column layout: header, optional degradation banner,
   then the grid taking whatever is left. A flex column rather than
   `calc(100% - 68px)` on the grid, because the banner's height is not a
   constant — its German sentence wraps to three lines on a 390px phone — and a
   layout that hardcodes it pushes the composer off the bottom of exactly the
   screen where that matters most. */
html, body { height: 100%; }
body { overflow: hidden; display: flex; flex-direction: column; }

/* In-palette scrollbars on the structural line, not the hairline: a thumb the
   reader has to hunt for is a thumb they scroll past. */
.transcript, .rail-panes, .popover, .md pre, .md .table-wrap, .report-dialog-content {
  scrollbar-width: thin;
  scrollbar-color: var(--linie-struktur) transparent;
}

/* ── Header (68px) ────────────────────────────────────────────────── */
/* `min-height` + `flex-wrap`, not a fixed `height`: at 68px the header is one
   row at every width the product is designed for, and on a 320px phone it
   becomes two rather than pushing its last control past the right edge — where
   `body{overflow:hidden}` would make it invisible *and* unreachable. Flex
   decides when that happens; there is no second breakpoint to keep in sync. */
.app-header {
  min-height: var(--app-header-h); flex-shrink: 0;
  border-bottom: 1px solid var(--linie-struktur);
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px 24px;
  padding: 0 22px;
  position: relative; z-index: 60;
  background: var(--kohle);
  transition: background 380ms var(--ease), border-color 380ms var(--ease);
}
.header-brand { display: flex; align-items: center; gap: inherit; min-width: 0 }
.header-controls { display: flex; align-items: center; gap: 12px; margin-left: auto }
/* Structure only: the rank the crumb speaks at is named on the element. */
.crumb {
  border-left: 1px solid var(--linie-struktur);
  padding-left: 18px; margin-left: 2px; white-space: nowrap;
}
.crumb .sep { color: var(--mittelgrau); margin: 0 7px }

/* The two view toggles in the header. Each is pressed while its view is the
   one showing, and the coral says which — the same signal `.gov-nav` gives its
   section buttons. */
.agents-btn.on, .console-btn.on { color: var(--blau) }

/* DE | EN. Two buttons and a separator — not a select: the choice is binary
   and permanently visible. */
.lang-toggle { display: flex; align-items: center; gap: 4px }
.lang-toggle button {
  font-family: var(--sans); font-size: var(--fs-label); font-weight: 600;
  color: var(--text-2); padding: 6px 7px; border-radius: var(--radius);
}
.lang-toggle button:hover { color: var(--blau) }
.lang-toggle button.on { color: var(--graphit); background: var(--tag-bg) }
.lang-toggle .sep { color: var(--mittelgrau) }

.theme-toggle { font-size: 16px; line-height: 1; padding: 8px 9px }

/* Non-blocking degradation banner: `policy_error` from /api/bootstrap. Its
   own ground rather than a red bar — the workspace works, only the "which
   alias resolves local" annotation is missing, and a red bar would claim
   otherwise. */
.degraded {
  display: flex; align-items: center; gap: 8px 14px; flex-wrap: wrap;
  flex-shrink: 0;
  padding: 10px 22px;
  border-bottom: 1px solid var(--linie-struktur);
  background: var(--kohle-card);
}
.degraded .service-de { font-size: var(--fs-ui); color: var(--text-2); line-height: 1.5 }
/* The embedding-drift banner's dismiss control is this bar's only button —
   pinned to the end of the row instead of sitting inline right after the
   sentence, which is where flex would otherwise leave it. */
.degraded .btn--ghost { margin-left: auto }

/* ── The shell's two views ────────────────────────────────────────── */
/* The chat and the governance console are siblings, and the inactive one
   carries `hidden` rather than being unmounted — the same discipline the rail
   applies to its four panes, and for the same reason: a half-written question,
   a loaded page and a typed filter are real state that a swap discards.
   `[hidden]` has to be spelled out per view, because an author `display`
   outranks the UA's own `[hidden]{display:none}`. */
.app-main[hidden], .agents[hidden], .governance[hidden] { display: none }

/* ── The shell body: sidebar | views ──────────────────────────────── */
/* The sidebar is a column of the shell rather than a column of the chat: it is
   beside the console and the agents view too, because reaching one's own
   conversations must not depend on which view happens to be showing. The three
   views share the second column and only one of them is ever not `hidden`, so
   they stack in a flex column that never has more than one item in it. */
.app-body {
  flex: 1; min-height: 0;
  display: grid;
  grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
  /* The containing block for the sidebar once it becomes a drawer — over the
     content and never over the header, whatever the header and the banner add
     up to. */
  position: relative;
}
.app-views { display: flex; flex-direction: column; min-width: 0; min-height: 0 }

/* ── Sidebar ──────────────────────────────────────────────────────── */
.sidebar {
  display: flex; flex-direction: column;
  min-width: 0; min-height: 0;
  border-right: 1px solid var(--linie-struktur);
  background: var(--kohle);
  transition: background 380ms var(--ease), transform 300ms var(--ease);
}
/* The two ways into a conversation, on one row: opening a new one, and finding
   one that exists. The first is the sentence and takes the width; the second is
   a glyph and takes the square left over — a switcher is reached far more often
   by its shortcut than by a press, and a second full-width control would make
   the head of this column read as a menu. */
.sidebar-top { padding: 16px 12px 12px; flex-shrink: 0; display: flex; gap: 8px }
.sidebar-new { flex: 1; min-width: 0; justify-content: center }
/* Square, and exactly as tall as the control beside it: the pair is one row and
   a control half a step shorter than its neighbour reads as an afterthought. */
.sidebar-switcher {
  flex: 0 0 auto; width: 40px; padding: 0;
  font-size: 18px; line-height: 1;
}
/* A thread nobody has asked anything in is not a conversation and has no row,
   so while one is on screen this control is what says where the reader is. It
   takes the same three the current row takes — the full edge, the inner line
   and the heavier weight — because it is standing in for that row and has to
   read as the same statement made one step up the column. */
.sidebar-new.on {
  border-color: var(--blau);
  box-shadow: inset 0 0 0 1px var(--blau);
  font-weight: 700;
}
.sidebar-list { flex: 1; min-height: 0; overflow-y: auto; padding: 0 10px 24px }
/* The list's own headings are the section rank: they name what the rows under
   them are, and a row and the word above it must not read as the same thing. */
.sidebar-list h2 { padding: 18px 4px 8px; margin: 0 }
/* A gap between rows, and nothing drawn in it. Each row states its own bounds,
   so the space between two of them only has to be wide enough that the two
   edges do not read as one. */
.sidebar-list ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px }
.sidebar-list > .empty { padding: 6px 4px 0; font-size: var(--fs-meta) }
.sidebar-failed { padding: 10px 4px 0; font-size: var(--fs-meta); color: var(--blau); line-height: 1.5 }

/* ── One row ──────────────────────────────────────────────────────── */
/* A row is an object: its own fill, its own edge, its own corners. A list read
   at the end of a shift is counted by its boundaries and not by its words, and
   two lines of bare text separated by a margin give the eye nothing to count.
   The fill is the panel step above the sidebar's ground, so a row reads as a
   row before its border is drawn. */
.thread-row {
  position: relative;
  /* The square at the row's end that the control standing there occupies, and
     that the two lines beside it therefore end before. Named once, because the
     control and the room made for it are the same measurement and a row whose
     text runs under its own button reads differently depending on where a
     pointer happens to be. */
  --thread-edge: 28px;
  background: var(--kohle-card);
  border: 1px solid var(--linie);
  border-radius: var(--radius);
  transition: background 160ms var(--ease), border-color 160ms var(--ease);
}
.thread-row:hover, .thread-row:focus-within {
  background: var(--kohle-card-2);
  border-color: var(--linie-struktur);
}
/* The row the reader is in is the most legible row on the list, not the least.
   Selection is carried by the surface, the edge and the weight of the title —
   the same three the source cards use for the card an answer's citation points
   at — so the text stays on the primary rank instead of being tinted down to
   the accent to say something the box beneath it already says. */
.thread-row.current {
  background: var(--kohle-card-2);
  border-color: var(--blau);
  box-shadow: inset 0 0 0 1px var(--blau);
}
.thread-open {
  display: block; width: 100%; text-align: left;
  padding: 9px 11px; min-width: 0;
  padding-right: calc(var(--thread-edge) + 12px);
}
/* The top line: the conversation's name, its mark, and the dot that says
   something arrived in it. The name is the only one of the three that gives
   way — it is a question the service already cut, and cutting it again costs
   the reader nothing they cannot get by opening the row. */
.thread-head { display: flex; align-items: center; gap: 8px; min-width: 0 }
.thread-title {
  flex: 1 1 auto; min-width: 0;
  font-size: var(--fs-ui); font-weight: 500; line-height: 1.4; color: var(--graphit);
  /* The title is the reader's own question, cut by the service at a length it
     decides. This is the visual cut only — nothing here shortens the value. */
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.thread-row.current .thread-title { font-weight: 700 }
/* The chip is set so that the line it rides in is exactly as tall as the line
   without it, which is what keeps every row in the list one height. Its colours
   come from the shared `[data-level]` ladder in zpa-app.css, which the banner
   over an answer and the chip on a source card climb too. */
.thread-mark { flex: 0 0 auto; padding: 1px 8px; white-space: nowrap }

/* What is going right now, in one vocabulary for both kinds of row: a
   conversation with a stream in it and a run that has not ended are the same
   fact about this list. The dot is drawn at the accent, sized to the mark
   beside it so the line's height does not move; the word inside it is for a
   screen reader, and the animation is neutralised by the global
   prefers-reduced-motion block. */
.thread-live { flex: 0 0 auto; display: inline-flex; color: var(--blau); line-height: 1 }
.thread-live > span { font-size: 11px; animation: thread-live-pulse 1.8s ease-in-out infinite }
@keyframes thread-live-pulse {
  0%, 100% { opacity: 1 }
  50%      { opacity: .35 }
}

/* The facts under the name, one line and never two: a row that grows a second
   line stands taller than its neighbours and the list stops being countable. */
.thread-meta {
  display: block; margin-top: 3px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Something arrived while the reader was in another thread. The same mark the
   pill carries inside a thread, for the same fact, at the end of the line that
   names the conversation — in the flow, so it is drawn whether or not a pointer
   is resting on the row. */
.thread-row.unseen .thread-head::after {
  content: ""; flex: 0 0 auto;
  width: 6px; height: 6px; border-radius: 50%; background: var(--blau);
}

/* The one control a row carries, at the end of the quietest line it has: the
   upper line carries the mark and the mark is not a thing to stand beside. It
   is drawn whether or not a pointer is resting on the row, because a drawer is
   opened by a finger and a finger has no hover — and because a control the
   browser is not drawing is one `Shift`+`Tab` computes its way straight past.
   Quiet, so a list of conversations still reads as a list of titles.

   Its place is measured from the acts it opens rather than from the row: the
   box below it is empty until it is open and as tall as three acts after, so
   `100%` of that box puts the control on the row's own last line either way. */
.thread-more { position: relative }
.thread-more > summary, .thread-unpin {
  position: absolute; bottom: calc(100% + 3px); right: 4px;
  display: flex; align-items: center; justify-content: center;
  width: var(--thread-edge); height: var(--thread-edge);
  font-size: var(--fs-ui); line-height: 1; color: var(--mittelgrau);
  border-radius: var(--radius-xs); cursor: pointer;
  transition: color 160ms var(--ease);
}
/* A run's row never grows, so its one act is measured from the row itself. */
.thread-unpin { bottom: 3px }
/* A `<summary>` is a list item and draws a marker for it. This one is a control
   at the end of a row and draws its own face and nothing else. */
.thread-more > summary { list-style: none }
.thread-more > summary::-webkit-details-marker { display: none }
.thread-more > summary:hover, .thread-more > summary:focus-visible,
.thread-unpin:hover, .thread-unpin:focus-visible { color: var(--blau) }

/* The acts themselves, inside the row's own bounds and in the flow: the list
   scrolls the way it always did, and the drawer's focus trap finds them where
   it finds every other control it keeps `Tab` behind. */
.thread-actions {
  display: none; flex-direction: column; align-items: stretch;
  padding: 5px; border-top: 1px solid var(--linie);
}
/* Spelled out rather than left to the browser's own `details:not([open])` rule,
   which an author `display` outranks — and whether a control is drawn is the
   same question the focus trap asks before it will step onto it. */
.thread-more[open] > .thread-actions { display: flex }
.thread-actions button {
  padding: 7px 8px; text-align: left;
  font-size: var(--fs-ui); line-height: 1.35; color: var(--graphit);
  border-radius: var(--radius-xs);
  transition: color 160ms var(--ease);
}
.thread-actions button:hover, .thread-actions button:focus-visible { color: var(--blau) }
/* The act that cannot be taken back, set apart from the two that can by the
   same hairline this UI separates anything else with. */
.thread-actions .remove { margin-top: 4px; border-top: 1px solid var(--linie); padding-top: 9px }

/* Renaming and confirming a removal replace the row's contents rather than
   opening beside them, so both are laid out inside the row's own bounds. */
.thread-rename {
  display: block; width: calc(100% - 10px); margin: 5px;
  font-family: var(--sans); font-size: var(--fs-ui); color: var(--graphit);
  background: var(--kohle-card-2); border: 1px solid var(--blau); border-radius: var(--radius);
  padding: 8px 10px;
}
.thread-confirm { padding: 10px 11px 11px }
.thread-confirm-q { font-size: var(--fs-ui); color: var(--graphit); margin: 0 0 5px; line-height: 1.45 }
.thread-confirm .empty { font-size: var(--fs-meta); line-height: 1.5; margin: 0; padding: 0 }
.thread-confirm-row { display: flex; gap: 8px; margin-top: 9px }

/* The name of the conversation on screen. Absent wherever the sidebar itself
   is on screen — there the current row is the answer, and a second one would
   be a second thing to keep true. Shown at the widths where the sidebar is a
   drawer, which is the breakpoint below. */
.thread-bar { display: none }

/* ── Shell grid (minmax(0,1fr) 400px above 1100px) ────────────────── */
.app-main {
  flex: 1; min-height: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--rail-w);
  overflow: hidden;
  /* The containing block for the rail once it becomes a drawer: the drawer
     covers the content area and never the header, whatever the header and
     banner happen to add up to. */
  position: relative;
}

/* min-height:0 / min-width:0 — a flex or grid item refuses to shrink below its
   content, so without these the composer is pushed off the bottom of the
   viewport and the transcript never scrolls. */
.app-col {
  display: flex; flex-direction: column;
  min-width: 0; min-height: 0;
  border-right: 1px solid var(--linie-struktur);
  position: relative;

  /* The transcript's own two edges, each named because a second rule needs
     the same number.

     --transcript-foot: the transcript sets it as padding and the composer's
     fade band is exactly that deep. Read them together — the fade can only
     reach a line of an answer once that line has scrolled into the padding,
     so an answer sitting at the bottom of the transcript is never touched by
     it at any width.

     --transcript-top: the transcript sets it as padding and a turn landed on
     from the switcher's Fundstellen takes the same number as its own
     `scroll-margin-top`, so the anchor stops the same distance from the
     transcript's edge that resting at the top of it already shows — without
     this, `scrollIntoView` snaps the turn flush against the edge the padding
     exists to keep clear.

     Both are restated at every breakpoint that restates the padding. */
  --transcript-foot: 26px;
  --transcript-top: 34px;
}
.transcript {
  flex: 1; min-height: 0;
  overflow-y: auto;
  /* overflow-anchor:none — the browser's own scroll anchoring fights the
     transcript autoscroll. scroll-behavior stays `auto` here; the one smooth
     scroll in the product is the pill's, set per call. */
  overflow-anchor: none;
  scroll-behavior: auto;
  padding: var(--transcript-top) 40px var(--transcript-foot);
  /* Own paint, not a transparent hole onto <body>: the data-theme flip at
     mount is a real value change on the already-painted <body>, so body's own
     `transition: background 380ms` runs, and an element without a background
     of its own shows that transition through as a flat grey rectangle for up
     to 380ms. Freshly inserted elements (.app-header/.rail/.composer-bar) have
     no prior value to transition from and paint their final colour at once,
     which is why only this one needs a background. */
  background: var(--kohle);
  transition: background 380ms var(--ease);
}
.column-inner { max-width: 820px; margin: 0 auto }
/* The band the composer stands in. Its ground is the page's own and it draws no
   line: the container inside it is a filled, bordered, raised card that already
   says it is not the transcript, and a rule above it would say the same thing a
   second time — across the full width of the column, where the card and the
   answer above it are both inset by this band's padding. Two boundaries for one
   division, and the wider of them aligned with neither of the things it
   divides.

   Where the scrolling region ends is ::before's to say: the page colour laid
   back over the foot of the transcript, so an answer taller than the screen
   fades out as it passes under the band rather than being cut through the
   middle of a word. It needs no state to know when to show itself — it is the
   page colour drawn over the page colour, and so nothing at all until there is
   text beneath it — and it is exactly as deep as the transcript's own bottom
   padding, which means the text it reaches is always text on its way out.
   `pointer-events: none` because it lies over the last band of an answer a
   reader may still be selecting. */
.composer-bar {
  position: relative;
  padding: 16px 40px 20px;
  background: var(--kohle);
}
.composer-bar::before {
  content: "";
  position: absolute; left: 0; right: 0; bottom: 100%;
  height: var(--transcript-foot);
  background: linear-gradient(to top, var(--kohle), transparent);
  pointer-events: none;
}

/* ── Composer meta line ───────────────────────────────────────────── */
/* The line under the composer's container, where the meters stand. The search
   rail composes the same shape for its own row of knobs, where the two
   <select>s are the metadata rank with the browser's chrome removed and the row
   reads as one sentence of live truth rather than as a form. */
.composer-meta { display: flex; flex-wrap: wrap; gap: 8px 16px; align-items: center; margin-top: 12px }
.composer-meta .sep { color: var(--mittelgrau) }
.composer-meta select {
  font-family: var(--sans); font-size: var(--fs-meta); font-weight: 500;
  color: var(--text-2);
  background: none; border: 0; padding: 0; cursor: pointer;
  transition: color 200ms var(--ease);
}
.composer-meta select:hover, .composer-meta select:focus-visible { color: var(--blau) }
.composer-meta select:disabled { cursor: not-allowed; opacity: 0.55 }
/* The option list is drawn by the platform, which does not inherit a
   transparent background — name the ground so a light-on-light dropdown is
   not possible in either theme, and keep the platform from lettercasing a
   value the row states verbatim. */
.composer-meta option { background: var(--kohle-card); color: var(--graphit); text-transform: none }

/* The meters stack, so the two readings sit under each other rather than
   running along the line: one grid, one alignment, and the tracks line up
   whatever the numbers do to the width of the sentences. */
.composer-meters { display: flex; flex-direction: column; gap: 5px; min-width: 0 }

/* A conversation a deletion has reached: still readable, closed to further
   questions. The accent, because this is the one thing about the composer's
   state a reader has to notice before they type. */
.composer-closed {
  color: var(--blau); font-size: var(--fs-ui); line-height: 1.5; max-width: 60ch; margin-bottom: 10px;
}

/* ── Transcript body ──────────────────────────────────────────────── */
/* One exchange = one <article>. The hairline between exchanges belongs to
   `.qa-answer` in zpa-app.css, so this wrapper is structure only — except for
   `scroll-margin-top`, which gives a turn landed on from the switcher's
   Fundstellen the same breathing room under the app header that resting at
   the top of the transcript already has, rather than landing flush against
   the container's own top edge. */
.qa { display: block; scroll-margin-top: var(--transcript-top) }
/* A wide table scrolls INSIDE its block instead of widening the column.
   `.md .table-wrap` exists, but pulldown-cmark emits a bare <table> and
   nothing may wrap it after the fact — a settled block's HTML is never
   rewritten. `:has()` puts the overflow on the block that contains a table and
   nowhere else. */
.md-block:has(> table) { overflow-x: auto }

/* The refusal line: knowledge's own sentence under the footer's row of facts.
   A line of its own at the answer's full width, because it is a statement and
   not one more field in the row above it. */
.answer-footer .note {
  display: block; flex-basis: 100%;
  font-family: var(--sans); font-size: var(--fs-ui); font-weight: 400;
  color: var(--text-2); line-height: 1.55;
}

/* ── Rail ─────────────────────────────────────────────────────────── */
.rail {
  display: flex; flex-direction: column;
  min-width: 0; min-height: 0;
  background: var(--kohle);
  transition: background 380ms var(--ease), transform 300ms var(--ease);
}
.rail-tabs { display: flex; border-bottom: 1px solid var(--linie-struktur); flex-shrink: 0 }
/* Three tabs and one pane that is not a tab. The audit trail is opened from an
   answer and takes the whole rail while it stands, so its own head — the way
   back, and the name of what is being read — stands exactly where the tab strip
   stands and the strip steps aside for it. `[hidden]` has to be spelled out for
   both, because an author `display` outranks the UA's own `[hidden]{display:none}`
   and a tab strip that is merely invisible is one a keyboard still walks into. */
.rail-tabs[hidden], .rail-return[hidden] { display: none }
.rail-return {
  display: flex; align-items: center; gap: 12px; flex-shrink: 0;
  padding: 10px 12px 9px;
  border-bottom: 1px solid var(--linie-struktur);
}
.rail-return h2 { margin: 0; min-width: 0 }
.rail-tabs button {
  flex: 1; padding: 18px 6px 15px;
  font-family: var(--sans); font-size: var(--fs-ui); font-weight: 600; letter-spacing: -0.005em;
  color: var(--text-2);
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  transition: color 200ms var(--ease), border-color 200ms var(--ease);
}
.rail-tabs button:hover { color: var(--graphit) }
.rail-tabs button[aria-selected="true"] { color: var(--blau); border-bottom-color: var(--blau); font-weight: 700 }
.rail-panes { flex: 1; min-height: 0; overflow-y: auto; padding: 22px 22px 34px }
/* An empty tab states what will appear and when. A pane that is blank until
   something happens reads as broken. */
.empty { padding: 30px 2px; line-height: 1.6; color: var(--text-2); font-size: var(--fs-ui) }

/* ── Search rail tab ──────────────────────────────────────────────── */
/* Reuses the composer's `.field-group`/`.composer-meta` shapes verbatim, so the
   search form reads as the same sentence of live truth. All this panel adds is
   the vertical rhythm a form sitting above a list of result cards needs. */
.search-panel .composer-meta { margin-bottom: 4px }
.search-results { margin-top: 22px }
.search-results .empty { padding-top: 8px }

/* ── Identity menu ────────────────────────────────────────────────── */
.who { position: relative }
.who > button {
  display: flex; align-items: center; gap: 10px;
  padding: 6px 10px;
  border: 1px solid var(--linie-struktur); border-radius: var(--radius);
  transition: border-color 200ms var(--ease);
}
.who > button:hover, .who > button[aria-expanded="true"] { border-color: var(--blau) }
/* The tag ground and a coral edge, not the marked chip's colours: an identity
   is a fact about who is signed in, and the marked palette says exceptional. */
.who .initials {
  width: 28px; height: 28px; border-radius: var(--radius-xs);
  border: 1px solid var(--blau); background: var(--tag-bg);
  display: grid; place-items: center;
  font-family: var(--sans); font-size: var(--fs-min); font-weight: 700; color: var(--graphit);
  flex-shrink: 0;
}
.who .name { display: flex; flex-direction: column; align-items: flex-start; line-height: 1.2 }
.who .name b { font-size: var(--fs-ui); font-weight: 600 }
/* The floor, because this value stands under a name in a 68px header and the
   line the two share is the header's own. */
.who .name .code { font-size: var(--fs-min); line-height: 1.3 }
.who .caret { color: var(--mittelgrau); font-size: var(--fs-min) }

.popover {
  position: absolute; top: calc(100% + 8px); right: 0; width: 322px;
  background: var(--kohle-card);
  border: 1px solid var(--linie-struktur); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-soft);
  padding: 8px; z-index: 70;
  max-height: min(70vh, 560px); overflow-y: auto;
}
.popover .popover-head { padding: 8px 10px 10px; border-bottom: 1px solid var(--linie); margin-bottom: 6px }
.popover .popover-foot { padding: 10px; border-top: 1px solid var(--linie); margin-top: 6px; line-height: 1.5 }
/* One session, one identity: no row to pick, just the facts the directory
   returned — the same field-label convention `.ledger dt` uses for a request
   row's own field names. */
.popover .identity-facts {
  padding: 4px 10px 8px;
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 7px 14px;
  align-items: baseline;
}
.popover .identity-facts dt {
  font-family: var(--sans); font-size: var(--fs-label); font-weight: 600;
  color: var(--text-2);
}
.popover .identity-facts dd { font-size: var(--fs-ui); overflow-wrap: anywhere }
/* One group per element, wrapped rather than run together: the gap between two
   of them is what a comma would otherwise have to be, and a value carries no
   punctuation that is not its own. */
.popover .identity-groups { display: flex; flex-wrap: wrap; gap: 4px 10px }
.popover-foot .btn--ghost.outline { width: 100% }

/* ── BootstrapGate skeleton ───────────────────────────────────────── */
/* Rules, not a spinner: the anonymous fetch is a fast local check and this is
   visible for one frame on a warm cache. A spinner would imply waiting on
   something slow, which would be a lie about where the time goes. */
.skeleton { padding: 60px 40px; display: flex; flex-direction: column; gap: 14px; max-width: 620px }
.skeleton .bar { height: 2px; border-radius: 1px; background: var(--linie-struktur) }
.skeleton .bar:nth-child(2) { width: 72% }
.skeleton .bar:nth-child(3) { width: 45% }
/* `.empty` carries the padding a rail pane's own empty state needs, and this
   sentence stands on a page rather than in a pane. */
.skeleton .empty { padding: 0 }

/* ── Login screen ─────────────────────────────────────────────────── */
/* Centred card, the same control idiom the composer uses (a filled field with
   an edge of its own, coral on focus) rather than a boxed dialog — this IS the
   page while nobody has signed in, not an overlay on top of one. */
.login-screen {
  height: 100%; display: flex; align-items: center; justify-content: center;
  padding: 24px;
}
.login-card { width: min(380px, 100%); display: flex; flex-direction: column; gap: 4px }
.login-card .logo { margin-bottom: 18px }
.login-card h1 { font-size: 24px; font-weight: 700; letter-spacing: -0.015em; margin: 0 0 4px }
.login-card > .label-field { margin-bottom: 4px }
/* `.empty` carries the padding a rail pane's own empty state needs, and neither
   the subtitle nor the refusal under the fields is standing in a rail pane. */
.login-card .empty { padding: 0 }
.login-card > .empty { margin-bottom: 22px }
.login-card .rule--blocked { padding: 2px 0 2px 16px }
.login-card form { display: flex; flex-direction: column; gap: 18px }
.login-card button[type="submit"] { margin-top: 6px }

/* ── Responsive (rail is an off-canvas drawer at 1100px and under) ── */
@media (max-width: 1100px) {
  .app-main { grid-template-columns: minmax(0, 1fr) }
  .rail {
    position: absolute; top: 0; right: 0; bottom: 0;
    width: min(var(--rail-w), 92vw);
    border-left: 1px solid var(--linie-struktur);
    transform: translateX(101%);
    z-index: 80;
    box-shadow: var(--shadow-soft);
    /* Out of the tab order while it is off screen — see the sidebar's own
       rule below for why a transform alone is not enough. */
    visibility: hidden;
    transition: background 380ms var(--ease), transform 300ms var(--ease),
                visibility 0s linear 300ms;
  }
  body.rail-open .rail {
    transform: none;
    visibility: visible;
    transition: background 380ms var(--ease), transform 300ms var(--ease), visibility 0s;
  }
  .app-col { border-right: 0; --transcript-foot: 20px; --transcript-top: 26px }
  .transcript { padding: var(--transcript-top) 22px var(--transcript-foot) }
  .composer-bar { padding: 14px 22px 18px }
}
@media (min-width: 1101px) {
  /* The drawer button is the only way into a rail that is always visible
     above this width, so it is not merely hidden — it is absent. */
  .rail-drawer-btn { display: none !important }
}

/* ── Responsive (sidebar is a drawer at 1320px and under) ─────────── */
/* Its own breakpoint, above the rail's: the sidebar and the rail are 268 and
   400 pixels, and the width at which a transcript still has a column worth
   reading between them is not the width at which it still has one beside the
   rail alone. */
@media (max-width: 1320px) {
  .app-body { grid-template-columns: minmax(0, 1fr) }
  .sidebar {
    position: absolute; top: 0; left: 0; bottom: 0;
    width: min(var(--sidebar-w), 86vw);
    transform: translateX(-101%);
    z-index: 80;
    box-shadow: var(--shadow-soft);
    /* Out of the tab order while it is off screen, not merely out of sight. A
       translated drawer keeps every one of its buttons focusable, so a
       keyboard reader tabs from the header through a list they cannot see and
       — `body{overflow:hidden}` — cannot scroll to. `visibility` takes it out
       of the order and is delayed on the way out, so the slide is still
       watched rather than blinking away at its first frame. */
    visibility: hidden;
    transition: background 380ms var(--ease), transform 300ms var(--ease),
                visibility 0s linear 300ms;
  }
  body.sidebar-open .sidebar {
    transform: none;
    visibility: visible;
    transition: background 380ms var(--ease), transform 300ms var(--ease), visibility 0s;
  }
  .thread-bar {
    display: flex; align-items: center; gap: 10px; flex-shrink: 0;
    padding: 11px 22px 10px;
    border-bottom: 1px solid var(--linie-struktur);
    background: var(--kohle);
    transition: background 380ms var(--ease), border-color 380ms var(--ease);
  }
  .thread-bar-title {
    font-size: var(--fs-ui); font-weight: 600; color: var(--graphit);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
  }
  /* The facts beside the title are boxed and hold their width: the title is
     what gives way when the bar runs out of room, because the title is the one
     thing here a reader can also see in the transcript below it. */
  .thread-bar .tag, .thread-bar .chip { flex-shrink: 0 }
}
@media (min-width: 1321px) {
  /* Absent rather than hidden, for the reason the rail's is. */
  .sidebar-drawer-btn { display: none !important }
}
@media (max-width: 720px) {
  /* Everything in the header earns its width again at this size. The crumb and
     the wordmark go — the mark alone is the brand, and the identity square
     without its name is still the identity — so that the four *controls*
     (drawer, identity, DE|EN, theme) all stay reachable rather than being
     clipped by `body{overflow:hidden}` and silently unreachable. */
  .app-header { gap: 8px 10px; padding: 9px 12px }
  .header-controls { gap: 8px }
  .crumb, .who .name, .logo-word { display: none }
  .app-header .chip { padding: 4px 10px }
  /* DE|EN collapses to the language you are *not* in. The pair is a binary
     toggle, so the inactive half carries the whole meaning, and 31px of a
     360px header is worth more elsewhere. */
  .lang-toggle .sep, .lang-toggle button[aria-pressed="true"] { display: none }
  .degraded { padding: 9px 14px }
  .app-col { --transcript-foot: 16px; --transcript-top: 22px }
  .transcript { padding: var(--transcript-top) 16px var(--transcript-foot) }
  .composer-bar { padding: 12px 16px 16px }
  /* A shorter track at the width where the sentence beside it is the thing
     competing for the line. The reading is the sentence; the track is how it
     is felt, and it is still felt at 56px. */
  .composer-meter { grid-template-columns: minmax(0, 1fr) 56px }
  /* The room the keyboard hint holds is the send button's at this width, and
     a hint clipped mid-word is worse than one not drawn. The textarea goes on
     naming it as its description, so it is still said on the way into the
     field — to the readers who are told rather than shown, which on a screen
     this size is who is asking about a keyboard at all. */
  .composer-hint { display: none }
  .popover { width: min(322px, calc(100vw - 28px)); right: -6px }
}
@media (max-width: 600px) {
  .console-btn { display: none }
}

/* ── Agents ───────────────────────────────────────────────────────── */
/* The catalog and the reader's own runs. One scroll container like the
   console, because this view is a document too — a run reads top to bottom,
   from its mark through its output to the calls it made. The run list is a
   column beside it rather than a rail: it is the reader's own set of objects,
   not a description of the pane it sits next to, and it stays visible while
   the pane changes under it. */
.agents {
  flex: 1; min-height: 0;
  overflow-y: auto;
  padding: 26px 34px 60px;
}
.agents-inner {
  max-width: 1080px; margin: 0 auto;
  display: flex; flex-direction: column; gap: 26px;
}
/* The cards and the pane they open. The left column is wide enough for a card
   to lead with a sentence about what its agent does rather than with a name cut
   at the third word — the whole point of the redesign — and the pane keeps the
   rest, because a README and a worked example are the longest prose on this
   screen. */
.agents-cols {
  display: grid; grid-template-columns: minmax(0, 340px) minmax(0, 1fr); gap: 26px;
  align-items: start;
}
/* The launch surface, in the order a person walks it: the shelf they keep in
   front of them, and the catalog they stocked it from — one column, because
   the second is where an empty first one sends them. */
.agents-list { display: flex; flex-direction: column; gap: 10px; min-width: 0 }
.agents-list h3 {
  font-family: var(--sans); font-size: var(--fs-ui); font-weight: 700;
  margin-top: 10px;
}
.agents-list h3:first-child { margin-top: 0 }
.agents-pane { display: flex; flex-direction: column; gap: 18px; min-width: 0 }
.agent-pane { display: flex; flex-direction: column; gap: 14px; min-width: 0 }
.agent-pane h4 {
  font-family: var(--sans); font-size: var(--fs-ui); font-weight: 700; margin-top: 6px;
}
/* Every bounded number a run stands under, each as its own sentence. A list
   and not a table: they are statements about the person's work, not cells. */
.agent-limits { display: flex; flex-direction: column; gap: 4px; padding-left: 18px }
.agent-limits li { font-size: var(--fs-meta); color: var(--text-2); line-height: 1.5 }

/* A chip is a box around one fact, never a bar across the panel. Standing
   alone as a direct child of a card's column it would otherwise stretch to the
   card's full width, because that is what a flex item does. */
.agents .gov-card > .tag { align-self: flex-start }

/* A card leads with what the agent does. The whole upper half is the control
   that opens its pane, so a pointer anywhere on the name, the description or
   the way it works reaches the same place. */
.agent-card { gap: 0 }
.agent-card.current { border-color: var(--blau) }
.agent-card-open {
  display: flex; flex-direction: column; align-items: flex-start; gap: 6px;
  width: 100%; text-align: left;
}
.agent-card-what {
  font-size: var(--fs-ui); color: var(--text-2); line-height: 1.55; overflow-wrap: anywhere;
}
.agent-card .field-group { margin-top: 12px; flex-wrap: wrap; gap: 8px }
/* A card whose agent cannot be started right now keeps its place and says so,
   quietly: the person put it there, and a card that shouted would be a fault
   report about somebody else's registry. */
.agent-card--away { opacity: .82 }

.agent-card .field { margin-top: 14px }
.agent-card textarea {
  width: 100%; resize: vertical;
  font-family: var(--sans); font-size: 15px; line-height: 1.55;
}
/* What is installed and which build of it — and, over a run, how it went. One
   line of prose running the two together reads as a single token. */
.agent-card-head,
.run-head-name {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px 10px;
}
/* A name is at most two hundred characters and may carry no space, so it is
   the one string on these surfaces that has to be allowed to break inside
   itself rather than push the card open. Both ranks: a card names its agent in
   an h4 and the pane names it in an h3. */
.agent-card-head h3, .agent-card-head h4, .run-head-name h3 { overflow-wrap: anywhere }
/* The banner carries its own margin for the transcript, where it stands in
   normal flow under a phase line. A card is a flex column that spaces its own
   children, so here that margin is a second gap on one side of one item and
   the column stops reading as evenly spaced. */
.run-head .banner { margin-bottom: 0 }
.doc-field .field-group { margin-top: 8px }
.doc-row {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--linie);
}
.doc-row:last-child { border-bottom: 0 }
.doc-line { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; min-width: 0 }
.doc-line-name { font-size: var(--fs-ui); font-weight: 600; overflow-wrap: anywhere }
/* The ladder is one ladder: the level beside a document a run is about to be
   launched with wears the same chip the banner gives that word, so a reader
   who has learned it once has learned it everywhere. `.tag[data-level]`
   carries it — see the tag vocabulary in zpa-app.css. */

/* What identifies a run, one press away from the panel that says how it went.
   The same door the document panel draws over its own record: what a person
   opened the run for is on the surface, what an auditor matches it by is
   inside, and nothing is further than one press. */
.run-record { margin-top: 6px }
.run-record > summary,
.run-entry-why > summary {
  font-family: var(--sans); font-size: var(--fs-label); font-weight: 600;
  color: var(--blau); cursor: pointer;
}
.run-record-body { display: flex; flex-direction: column; gap: 12px; margin-top: 12px }
/* An identifier stands under its own name rather than beside it: a
   71-character digest and a German field name side by side leave each other
   three characters. The act beside the value is the whole of it, in the
   clipboard — the handle is for the eye and is never the identity. */
.run-identity { display: flex; flex-direction: column; gap: 4px; min-width: 0 }
.run-identity-value {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap; min-width: 0;
}
/* What an artifact is, before what is in it: the level it carries on the
   shared ladder, and how large it is. */
.run-artifact-head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 12px }
/* The output is a file, so `pre` and not prose — reflowing it would change what
   it is. It takes the value treatment every body of its own takes in this
   product and adds only the ceiling a long output needs. */
.run-artifact { max-height: 60vh; overflow-y: auto }
.run-entry {
  padding: 14px 16px; margin-top: 10px;
  background: var(--kohle-card);
  border: 1px solid var(--linie); border-radius: var(--radius-lg);
}
.run-entry--act { border-style: dashed; background: none }
/* When the call was made and which request it was. Both are quiet: what the
   call did is in the ledger under them. */
.run-entry-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px 14px; flex-wrap: wrap; margin-bottom: 10px;
  font-family: var(--sans); font-size: var(--fs-meta); color: var(--text-2);
}
/* The instant's two halves are one item of that row. Loose, the head would
   divide them from each other, because dividing what it holds across the row
   is the whole of what the head does. */
.run-entry-time { min-width: 0 }
.run-entry-id { display: inline-flex; align-items: baseline; gap: 8px; min-width: 0 }
/* The policy engine's own expression waits behind the same door the routing
   line over an answer puts it behind: it is the engine's syntax, not a fact a
   reader weighs. */
.run-entry-why { margin-top: 10px }
.run-entry-why .code { display: block; margin-top: 6px }
/* A request the chain never answered says so at the end of its own entry, clear
   of the disclosure above it. */
.run-entry-note { margin-top: 10px }
@media (max-width: 900px) {
  .agents-cols { grid-template-columns: minmax(0, 1fr) }
}
@media (max-width: 720px) {
  .agents { padding: 20px 16px 44px }
}

/* ── Governance console ───────────────────────────────────────────── */
/* One scroll container, like `.transcript`, because this view is a document
   and not a grid: the cards, the section nav and the tables all read top to
   bottom, and a second scroller inside a table would hide the footnote that
   explains the numbers above it. */
.governance {
  flex: 1; min-height: 0;
  overflow-y: auto;
  padding: 26px 34px 60px;
}
.governance-inner {
  max-width: 1080px; margin: 0 auto;
  display: flex; flex-direction: column; gap: 26px;
}
.governance-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
}
.governance-title-group { display: flex; flex-direction: column; gap: 4px; min-width: 0 }
.governance-head h2 { font-size: 24px; font-weight: 700; letter-spacing: -0.02em }
.governance-subline { color: var(--text-2); font-size: var(--fs-ui); line-height: 1.5 }
@media (max-width: 720px) {
  .governance { padding: 20px 16px 44px }
}

/* ── the mask, over a running conversation ──────────────────────────────────
   A door beside the open one and never a gate in front of it: the composer
   stays where it is, and the sheet covers the transcript rather than replacing
   it. Anchored to the viewport, because the transcript scrolls underneath and
   a sheet that scrolled with it would leave the fields half off screen. */
.mask-overlay {
  position: fixed; inset: 0; z-index: 40;
  display: flex; align-items: flex-end; justify-content: center;
  background: color-mix(in srgb, var(--kohle-tief) 72%, transparent);
  padding: 24px 16px;
}
.mask-sheet {
  width: min(560px, 100%); max-height: 78vh; overflow-y: auto;
  display: flex; flex-direction: column; gap: 12px;
  padding: 18px 20px 20px;
  background: var(--kohle-card);
  border: 1px solid var(--linie-struktur); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
}

/* One field the whole mask stopped on, under the control it is about — never
   over the form, because a person fixes the field they are looking at. */
.field-refused { margin-top: 6px; font-size: var(--fs-meta); color: var(--laut); line-height: 1.5 }

/* The seam between two sessions of one conversation: a quiet rule with the
   version the new one runs under. It states a boundary and does not announce
   one — the turns on either side of it are the subject. */
.session-seam {
  display: flex; align-items: center; gap: 12px;
  margin: 22px 0 6px;
  font-size: var(--fs-meta); color: var(--text-3);
}
.session-seam::before, .session-seam::after {
  content: ""; flex: 1 1 auto; height: 1px; background: var(--linie);
}

/* A mask turn, as its labelled fields. Two columns where there is room and one
   where there is not, so a long value wraps under its own label rather than
   pushing the label off the line. */
.mask-turn {
  display: grid; grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  gap: 4px 12px; margin-top: 8px;
}
.mask-turn dt { font-size: var(--fs-meta); color: var(--text-3) }
.mask-turn dd { font-size: var(--fs-meta); color: var(--text-2); overflow-wrap: anywhere }
@media (max-width: 560px) {
  .mask-turn { grid-template-columns: minmax(0, 1fr) }
}

/* The draft a turn produced, inline in the transcript. Set apart by a rule and
   a label rather than by a panel of its own: each new one supersedes the
   reader's attention naturally, because it is simply the newest thing here. */
.turn-draft { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--linie) }
.turn-draft .mono-label { display: block; margin-bottom: 6px }
