/* v2 additions — Findings cards, expandable entity rows, scope toggle, document history */

/* TopBar user info */
.topbar-user {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-left: 8px;
  border-left: 1px solid var(--rule);
}
.topbar-user-email {
  font-size: 12px;
  color: var(--ink-light);
  max-width: 160px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.topbar-role-badge {
  padding: 2px 7px;
  border-radius: 10px;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  flex-shrink: 0;
}
.topbar-role-admin  { background: var(--badge-info-bg); color: var(--badge-info-fg); }
.topbar-role-viewer { background: var(--badge-success-bg); color: var(--badge-success-fg); }
.topbar-logout-btn {
  display: flex;
  align-items: center;
  background: none;
  border: none;
  cursor: pointer;
  color: var(--ink-light);
  padding: 4px;
  border-radius: 4px;
}
.topbar-logout-btn:hover { background: var(--surface-2); color: var(--badge-danger-fg); }
.topbar-admin-btn { color: var(--badge-info-fg) !important; }

/* ── Tab, chip and module bar: one look, four surfaces ───────────
   Translated line for line from the accepted design's inline styles
   (Variante-J-Themenuebersicht.dc.html: `tabStyle` 1718, `kpis[].style` 2722,
   `ovChipStyle` 2566, `modBarStyle` 1995). They stand up here rather than beside
   any one user because four unrelated surfaces draw them -- the lease page's two
   lists, the panel's two views, the queue's status filter and the band's filter
   chip -- and each of them would otherwise draw its own.

   `.lease-tab`, `.ap-tab`, `.iq-chip` and `.ov-chip` stand as names: the
   browser tests reach for them, and a page still needs a hook that says WHICH
   tab band this is. What they do not carry is an appearance of their own. */

/* 32px, one hairline, no radius: the strip a module hangs under its head for
   whatever only that module has. Declared once, here: the dossier's head bar
   and `.ev-bar` use it. */
.mod-bar {
  display: flex; align-items: center; flex-wrap: nowrap; gap: 6px;
  flex: 0 0 auto; box-sizing: border-box;
  height: 32px; padding: 0 6px 0 10px;
  min-width: 0; overflow: hidden;
  background: var(--surface-raised);
  border-bottom: 1px solid var(--sky);
}

/* A tab is text with a line under it, not a box. A box reads as a button, and
   a row of buttons does not say that one of them is the view you are
   looking at. Depth comes out of the badge for the same reason: only the state
   fills, everything else is an outline or nothing at all. */
.tab {
  display: inline-flex; align-items: center; gap: 6px; min-width: 0;
  padding: 0 10px;
  border: 0; background: transparent; cursor: pointer;
  font: 400 13px/18px var(--font-sans);
  color: var(--ink-light);
  white-space: nowrap;
}
.tab:hover { color: var(--ink); }
.tab .material-icons { font-size: 17px; color: var(--ink-lighter); }
/* `box-shadow` rather than `border-bottom`, so switching tabs moves no text: an
   inset shadow costs no layout. */
.tab.is-on {
  color: var(--ink); font-weight: 500;
  box-shadow: inset 0 -2px 0 var(--blue);
}
.tab.is-on .material-icons { color: var(--blue-darker); }
.tab-label { overflow: hidden; text-overflow: ellipsis; }
/* The count is the quieter half of the label: it says how much, the word says of
   what. Its own `font` shorthand, so the weight of an active tab stops here. */
.tab-count {
  font: 400 11px/16px var(--font-sans);
  color: var(--ink-lightest); font-variant-numeric: tabular-nums;
}
.tab.is-on .tab-count { color: var(--ink-light); }

/* A chip is a filter you can see the state of: outline off, filled on. */
.chip {
  display: inline-flex; align-items: baseline; gap: 4px;
  padding: 2px 8px; border-radius: 12px; cursor: pointer;
  border: 1px solid var(--sky-dark);
  background: var(--surface-raised);
  color: var(--ink-light);
  font: 400 11px/16px var(--font-sans);
  white-space: nowrap;
}
.chip:hover { border-color: var(--blue-lighter); }
/* After `:hover` on purpose -- same specificity, so a chip that is on keeps its
   blue edge while the pointer is over it. */
.chip.is-on {
  border-color: var(--blue);
  background: var(--blue-lightest);
  color: var(--blue-darker);
}
.chip-num {
  font: 500 12px/16px var(--font-sans); color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.chip.is-on .chip-num { color: var(--blue-darker); }

/* Findings layer */
.findings-head {
  display: flex; align-items: center; justify-content: space-between;
  margin: 8px 0 12px;
}
.findings-head h2 {
  font: 500 16px/24px var(--font-sans);
  margin: 0; color: var(--ink); letter-spacing: 0.15px;
}
/* The list count, in the toolbar and not a heading band of its own. */
.toolbar-count { font: 400 12px/16px var(--font-sans); color: var(--ink-lighter); }

/* Active filter chip (sticky context) */
.filter-context {
  display: flex; align-items: center; gap: 12px;
  background: var(--blue-lightest);
  border: 1px solid var(--blue-lighter);
  border-radius: 4px;
  padding: 10px 14px;
  margin-bottom: 12px;
}
.filter-context .material-icons { color: var(--blue); }
.filter-context-text {
  flex: 1;
  font: 400 13px/18px var(--font-sans);
  color: var(--ink);
}
.filter-context-text strong { color: var(--blue-darker); }
.filter-context-clear {
  font: 500 12px/16px var(--font-sans);
  letter-spacing: 1.25px;
  text-transform: uppercase;
  color: var(--blue);
  background: transparent; border: 0; cursor: pointer;
  display: inline-flex; align-items: center; gap: 4px;
}
.filter-context-clear .material-icons { font-size: 16px; }

/* Asset register: technical-facility category chips.
   All categories share one neutral blue — category is not a severity signal,
   so per-category hues would collide with the criticality colour coding. */
.tf-cat-chip {
  display: inline-block; padding: 3px 10px; border-radius: 12px;
  font: 600 11px/1.4 var(--font-sans);
  background: #DBEAFE; color: #1E40AF;
}

/* Lifecycle chips — used by all v2 entity pages via <LifecycleChip>. */
.lc-chip {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 8px; border-radius: 10px;
  font: 600 11px/1.4 var(--font-sans);
  background: #E5E7EB; color: #374151;
  white-space: nowrap;
}
.lc-chip.lc-compact { padding: 2px 6px; }
.lc-chip-label { letter-spacing: 0.2px; }
.lc-chip-link {
  border: 0; background: transparent; padding: 0 0 0 4px; margin-left: 2px;
  font: 600 11px/1.4 var(--font-sans);
  color: inherit; opacity: 0.75; cursor: pointer;
  border-left: 1px solid currentColor;
}
.lc-chip-link:hover { opacity: 1; text-decoration: underline; }
.lc-active     { background: #D1FAE5; color: #065F46; }
.lc-scheduled  { background: #DBEAFE; color: #1E40AF; }
.lc-expiring   { background: #FEF3C7; color: #92400E; }
.lc-expired    { background: #FEE2E2; color: #991B1B; }
.lc-terminated { background: #E5E7EB; color: #374151; }
.lc-replaced   { background: #EDE9FE; color: #5B21B6; }
.lc-draft      { background: #F3F4F6; color: #6B7280; }
.lc-unknown    { background: #F3F4F6; color: #6B7280; }

/* Disabled buttons: greyed-out + non-interactive (used by validated state). */
.btn:disabled,
.btn[disabled] {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

/* ──────────────────────────────────────────────────────────
   Entity table (design/v2-table.jsx)
   A dense grid with vertical column rules and no zebra striping:
   alignment carries the reading, so stripes only add noise.
   ────────────────────────────────────────────────────────── */
/* `flex-basis: 0`, not `auto`. On `auto` the base size is the content height —
   the whole table, scrolled or not — and the page splits its free space in
   proportion to base sizes. Every row the filter adds or drops then resizes
   .page-table-head above, and the toolbar jumps. The pane is a viewport: it takes
   the space that is left, and the row count says nothing about how much that is.

   The floor is written here rather than on .et-pane because this is the element
   the page lays out: on a short window with the findings strip open there is less
   space than this, and the head has to be the one that yields and scrolls. */
.et-wrap {
  display: flex; flex-direction: column;
  flex: 1 1 0; min-height: 244px;
}

/* `.mod-bar` in the markup carries the shape; these are the three things a
   toolbar needs on top of it. The overflow is the important one: `.mod-bar`
   clips, and the column popover has to hang out of the bar. */
.et-toolbar {
  overflow: visible; position: relative; z-index: 30;
  border: 1px solid var(--sky);
  border-radius: 8px 8px 0 0;
}
/* Nothing in the bar shrinks except the search, which takes whatever is left.
   Same move as `.ev-search`: the 36px field of the page head is too tall for a
   32px strip, so it is taken down to 26px here rather than everywhere. */
.et-toolbar > * { flex: 0 0 auto; }
.et-toolbar .search-input {
  flex: 1 1 60px; min-width: 60px;
  height: 26px; padding: 0 8px;
}
.et-toolbar .search-input input { min-width: 0; font: 400 12px/18px var(--font-sans); }
/* The foot of the panel. `--sky-lighter` and one line on top, so it reads as a
   rule under the list rather than as a fifteenth row of it. */
.et-sumbar {
  display: flex; align-items: center; gap: 12px;
  flex: 0 0 auto; height: 30px; padding: 0 12px;
  background: var(--sky-lighter);
  border: 1px solid var(--sky); border-top: 0;
  border-radius: 0 0 8px 8px;
}
.et-sum-gap { flex: 1; }
.et-count {
  font: 700 10px/14px var(--font-sans); letter-spacing: 1.25px;
  text-transform: uppercase; color: var(--ink-lighter);
  white-space: nowrap;
}
.et-sum-val { font: 500 12px/16px var(--font-sans); color: var(--ink); white-space: nowrap; }

/* The unfold switch (design/v2-leases.jsx). Outline off, filled on — the same
   sentence `.chip` makes, in the toolbar's smaller type. Not a `.chip`: this one
   is a state of the LIST and not a filter on it, and it carries a word and a
   glyph rather than a count. */
.et-unfold, .et-diffs, .et-layout-btn {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 9px 3px 7px;
  border: 1px solid var(--sky); border-radius: 4px;
  background: var(--surface-raised);
  color: var(--ink-light);
  font: 500 11px/16px var(--font-sans);
  white-space: nowrap; cursor: pointer;
}
.et-unfold .material-icons, .et-diffs .material-icons,
.et-layout-btn .material-icons { font-size: 16px; }
.et-unfold:hover, .et-diffs:hover, .et-layout-btn:hover { color: var(--ink); }
.et-diffs:disabled { opacity: .5; cursor: default; }
.et-unfold.is-on, .et-diffs.is-on, .et-layout-btn.is-on {
  border-color: var(--blue);
  background: var(--blue-lightest);
  color: var(--blue-darker);
}
/* In a narrow pane the long view name ("Mietverträge und Mieteinheiten") and
   the deviations switch would push the column picker and the menu out of the
   bar: these two give way, their word clipped to an ellipsis, the glyph kept. */
.et-toolbar .et-unfold, .et-toolbar .et-diffs { flex: 0 1 auto; min-width: 0; }
.et-unfold > :last-child, .et-diffs > :last-child {
  min-width: 0; overflow: hidden; text-overflow: ellipsis;
}

/* Same reason as .et-wrap: a basis of `auto` would size this from the table it
   scrolls. It fills the wrap, and the wrap carries the floor. Its top edge is
   the toolbar's bottom edge — one panel, one border. */
.et-pane {
  flex: 1 1 0; min-height: 0;
  overflow: auto;
  background: var(--surface-raised);
  border: 1px solid var(--sky);
  border-top: 0; border-bottom: 0;
}

/* border-collapse must stay `separate`: a collapsed border is owned by the
   table, not the cell, and scrolls away from a sticky cell. */
.et { border-collapse: separate; border-spacing: 0; table-layout: fixed; }

/* The column rule carries only as much contrast as counting columns needs. Its
   own token, because eight rules read it and a table whose lines disagree with
   each other reads as two tables. */
.et {
  --et-rule: color-mix(in srgb, var(--sky) 55%, transparent);
}

.et th, .et td {
  position: relative;
  padding: 0 10px 0 24px;
  border-bottom: 1px solid var(--sky);
  border-right: 1px solid var(--et-rule);
  overflow: hidden;
  white-space: nowrap;
  /* Values line up at the right edge and their dots at a fixed left one, so
     both read down the column. The cell that NAMES the row is the exception:
     a name reads from the left. */
  text-align: right;
  vertical-align: middle;
}
.et tr > *:last-child { border-right-color: transparent; }
.et .is-entity { text-align: left; }
.et tbody td {
  height: 30px;
  font: 400 13px/18px var(--font-sans);
  color: var(--ink);
}

/* One line per block, and it is drawn on top: a row's own bottom edge is blank,
   so the rule between two rows is one line and never two. A child row is inside
   a block rather than beside one, and is parted by the lighter column rule. */
.et tbody td { border-top: 1px solid var(--sky); border-bottom-color: transparent; }
.et tbody .is-child td {
  border-top: 0;
  border-bottom-color: var(--et-rule);
}
/* The row that opens a block carries the weight; what hangs under it does not. */
.et tbody .et-row:not(.is-child):not(.is-unit) td { font-weight: 500; }

/* The head sits on the same raised ground as every other column head in the
   app, and parts from the body with one line rather than with a filled band. */
.et thead th {
  position: sticky; top: 0; z-index: 2;
  height: 32px;
  background: var(--surface-raised);
  font: 500 10px/16px var(--font-sans);
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--ink-lighter);
  border-right-color: transparent;
  border-bottom: 1px solid var(--sky-dark);
  cursor: pointer;
  user-select: none;
}
.et thead th:hover { color: var(--ink); }

/* A sticky cell is transparent by default, so every frozen and header cell
   needs an explicit background or the panned columns show through it. */
.et .et-col-frozen { position: sticky; z-index: 1; background: var(--surface-raised); }
.et thead .et-col-frozen { z-index: 3; background: var(--surface-raised); }
.et .et-freeze-edge { border-right: 1px solid var(--sky-dark); }

/* The dot leaves the text flow for a fixed left edge, and loses its halo with
   it: a column of filled discs reads as a column of alarms. The 24px of left
   padding above is the room it needs. */
.et .et-cell > .vdot {
  position: absolute; left: 9px; top: 50%; transform: translateY(-50%);
  width: 6px; height: 6px; margin: 0;
  box-shadow: none;
}
.et .et-cell > .vdot:hover { transform: translateY(-50%) scale(1.4); }
.et .et-cell > .vdot.active {
  transform: translateY(-50%) scale(1.5);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--blue) 35%, transparent);
}

.et-cell-inner {
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: middle;
}
.et .is-right { text-align: right; font-variant-numeric: tabular-nums; }
.et .is-mono .et-cell-inner { font-family: var(--font-mono); font-size: 12px; }

.et-entity:hover .et-cell { background: var(--sky-lighter); }
.et-cell { cursor: pointer; }

/* Hierarchy without new colours: the parent carries the weight and opens its
   block with a line, the children under it are lighter and quieter. */
.et-row.is-child .et-cell {
  font: 400 12px/16px var(--font-sans);
  color: var(--ink-light);
}

/* The fold button, in the entity cell beside the name. Its own click, so the
   cell around it still opens the dossier. */
.et-fold {
  display: inline-flex; align-items: center; gap: 1px; vertical-align: middle;
  border: 0; background: transparent; padding: 0; margin-right: 3px;
  color: var(--ink-lighter); cursor: pointer;
}
.et-fold:hover { color: var(--ink); }
.et-fold .material-icons { font-size: 16px; }
.et-fold-n { font: 400 10px/14px var(--font-sans); }

/* The group header holds the only chevron left, beside its label. */
.et-chevron {
  font-size: 18px; color: var(--ink-lighter);
  transition: transform .2s; vertical-align: middle;
  margin-right: 4px;
}

/* Flag state rides on the frozen leading cell, so it stays on screen at any
   pan position — a left border on the row would scroll out of view. The cell is
   addressed by position, not by column key, because the analyst can hide the ID
   column and the marker then moves to whatever leads the row. */
.et-row.is-flagged .et-cell:first-child { box-shadow: inset 3px 0 0 var(--critical); }
.et-row.is-warning .et-cell:first-child { box-shadow: inset 3px 0 0 var(--warning); }

.et-row.is-replaced .et-cell,
.et-row.is-expired .et-cell,
.et-row.is-terminated .et-cell { opacity: .65; }
.et-row.is-replaced .et-name .et-cell-inner,
.et-row.is-terminated .et-name .et-cell-inner {
  text-decoration: line-through;
  text-decoration-color: rgba(0, 0, 0, 0.35);
}

/* The Bef. column's number — how many findings are open on the contract, and
   the glyph of the worst.

   No target of its own. The whole cell is the click, like every other cell in
   the table, and what it opens is the attribute that finding is about
   (`attrFor` in design/v2-leases.jsx). A button inside the cell would put a
   second destination on one square and teach a gesture this table does not
   otherwise have.

   The glyph carries the severity, as a dot in the finding hue at the strength
   design/chrome.jsx `SEV_TONE` gives it — no red. A column of red icons reads as
   one alarm rather than as a ranking. The dot carries no digit, which is what
   this cell needs: the number beside it is the count of findings, and a second
   number inside the glyph made the reader work out which was which. */
.et .is-ins { text-align: center; padding-left: 6px; padding-right: 6px; }
.et-insight {
  display: inline-flex; align-items: center; gap: 2px;
  vertical-align: middle;
}
.et-insight .sev-glyph { vertical-align: middle; }
.et-insight-n {
  font: 500 12px/16px var(--font-sans);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

.et-sort { font-size: 14px; color: var(--blue); vertical-align: middle; }

.et-resize {
  position: absolute; top: 0; right: 0;
  width: 6px; height: 100%;
  cursor: col-resize;
  user-select: none; touch-action: none;
}
.et-resize:hover { background: var(--blue-lighter); }

.et-group-row td {
  background: var(--sky-light);
  height: 30px;
  cursor: pointer;
  font: 500 12px/18px var(--font-sans);
  color: var(--ink);
}
.et-group-row .et-col-frozen { background: var(--sky-light); }
/* A group heading is a name, and a name reads from the left. */
.et .et-group-label { text-align: left; }
.et-group-row.is-collapsed .et-chevron { transform: rotate(-90deg); }
.et-group-count { color: var(--ink-lighter); font-weight: 400; margin-left: 8px; }

.et-empty {
  padding: 24px 16px;
  text-align: center;
  color: var(--ink-lighter);
  white-space: normal;
  border-right: 0;
}

/* Its popup is right-aligned on the picker, and the picker sits at the right end
   of a bar that does not wrap — so the popup always opens over the table and
   never over the rail, where its checkboxes stopped taking clicks. */
.et-picker { position: relative; }
.et-picker-pop {
  position: absolute; right: 0; top: calc(100% + 4px); z-index: 20;
  min-width: 220px; max-height: 320px; overflow: auto;
  padding: 8px;
  background: var(--surface-raised);
  border: 1px solid var(--sky);
  border-radius: 6px;
  box-shadow: var(--depth-02);
}
.et-picker-row {
  display: flex; align-items: center; gap: 8px;
  padding: 4px 6px; border-radius: 4px;
  font: 400 13px/18px var(--font-sans);
  cursor: pointer;
}
.et-picker-row:hover { background: var(--sky-lighter); }
/* The named sets, over the checkboxes and parted from them by a line: they
   answer a question, the checkboxes answer "and one more column". */
.et-presets {
  display: flex; flex-wrap: wrap; gap: 4px;
  padding: 0 2px 8px; margin-bottom: 6px;
  border-bottom: 1px solid var(--sky);
}
.et-preset {
  padding: 3px 9px; border-radius: 4px;
  border: 1px solid var(--sky); background: var(--surface-raised);
  font: 500 11px/16px var(--font-sans); color: var(--ink-light);
  white-space: nowrap; cursor: pointer;
}
.et-preset:hover {
  border-color: var(--blue); background: var(--blue-lightest); color: var(--blue-darker);
}
.et-picker-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.et-picker-reset { margin-top: 6px; }

/* The dot menu at the right end of the toolbar. It borrows
   `.et-picker-pop` for the popover — one popover shape in the bar, not two — and
   adds only what an ENTRY is: the full width of the pop, one line, and an icon
   that stays quieter than its word. Wider than the column pop because an entry
   carries a sentence and not a column name.

   Its own anchor class rather than a second `.et-picker`: three tests reach the
   column picker as `.et-picker button`, and a second element of that class in
   the same bar makes the selector match two buttons. */
.et-menu { position: relative; }
.et-menu-pop {
  min-width: 236px; padding: 4px;
  display: flex; flex-direction: column; gap: 1px;
}
.et-menu > button[aria-expanded="true"] { background: var(--sky-light); }
.et-menu-item {
  display: flex; align-items: center; gap: 8px;
  width: 100%; text-align: left;
  padding: 7px 8px; border: 0; border-radius: 4px;
  background: transparent; cursor: pointer;
  font: 400 12px/18px var(--font-sans); color: var(--ink);
}
.et-menu-item:hover { background: var(--sky-lighter); }
.et-menu-item .material-icons { font-size: 17px; color: var(--ink-lighter); }

/* ──────────────────────────────────────────────────────────
   The comparison import (design/v2-table.jsx)

   The bar over the table names the file and what it found; the second line in a
   cell names the deviation on that value; the chip names a row that only one of
   the two lists knows. Three surfaces, one colour: `--critical` says "these two
   lists disagree" and nothing else on the table uses it.
   ────────────────────────────────────────────────────────── */
.et-cmp-bar {
  display: flex; align-items: center; gap: 8px;
  flex: 0 0 auto; height: 28px; padding: 0 6px 0 10px;
  box-sizing: border-box; min-width: 0;
  background: var(--sky-lighter);
  border-left: 1px solid var(--sky); border-right: 1px solid var(--sky);
  border-bottom: 1px solid var(--sky);
}
.et-cmp-bar .material-icons { font-size: 15px; color: var(--blue-darker); flex: 0 0 auto; }
.et-cmp-file {
  font: 500 11px/16px var(--font-sans); color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  flex: 0 1 auto; min-width: 40px;
}
.et-cmp-day {
  font: 400 11px/16px var(--font-sans); color: var(--ink-light);
  white-space: nowrap; flex: 0 0 auto;
}
.et-cmp-meta {
  font: 400 11px/16px var(--font-sans); color: var(--ink-lighter);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1 1 auto;
}

/* The placeholder is the point: every value cell carries the line, and only the
   cells with something to say colour it. Without the empty ones the rows with a
   deviation would be taller than the rows without one. */
.et-delta {
  display: block;
  font: 500 10px/13px var(--font-sans);
  color: transparent;
  white-space: nowrap; overflow: hidden;
}
.et-delta.is-on { color: var(--critical); }
/* The day of the field is not the as-of date of the list. */
.et-cmp-note {
  font: 500 11px/16px var(--font-sans); color: var(--blue-darker);
  white-space: nowrap; flex: 0 0 auto;
}

/* "nur Datenraum" — an outline chip rather than a filled one,
   because it marks a row that is missing somewhere and not a state it is in. */
.et-cmp-chip {
  display: inline-block; margin-left: 5px; vertical-align: 1px;
  font: 500 9px/13px var(--font-sans); letter-spacing: .2px;
  color: var(--critical); border: 1px solid var(--critical);
  border-radius: 3px; padding: 0 3px; white-space: nowrap;
}
/* "beendet" and "Zuschnitt" are no disagreement of the two lists, so
   they take the quiet ink rather than `--critical`. */
.et-cmp-chip.is-ended, .et-cmp-chip.is-cut {
  color: var(--ink-lighter); border-color: var(--ink-lighter);
}

/* The switch of the body, table or record view (design/v2-table.jsx): two
   buttons in the look of `.et-unfold`, joined into one. */
.et-layout { display: inline-flex; }
.et-layout-btn { padding: 3px 6px; }
.et-layout-btn:first-child { border-radius: 4px 0 0 4px; }
.et-layout-btn:last-child { border-radius: 0 4px 4px 0; margin-left: -1px; }

/* The record view (design/entity-record.jsx): one row at a time, a line per
   field. The head and the column "Feld" stand while the pane scrolls; the pane
   scrolls sideways when the lettings take more width than it has. */
.et-rec-head {
  position: sticky; top: 0; left: 0; z-index: 4;
  display: flex; align-items: center; gap: 2px;
  height: 36px; box-sizing: border-box; padding: 0 10px 0 4px;
  background: var(--sky-lighter);
  border-bottom: 1px solid var(--sky);
}
.et-rec-prev, .et-rec-next { width: 28px; height: 28px; }
.et-rec-prev:disabled, .et-rec-next:disabled { opacity: .4; cursor: default; }
.et-rec-pos {
  font: 500 12px/16px var(--font-sans); color: var(--ink-light);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.et-rec-name {
  flex: 1 1 auto; min-width: 0; margin-left: 8px;
  font: 500 13px/18px var(--font-sans); color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.et-rec-group { color: var(--ink-lighter); font-weight: 400; margin-right: 6px; }
/* At least the pane's width: one value column takes what the field leaves. */
.et.et-rec-grid { table-layout: auto; min-width: 100%; }
.et-rec-grid thead th { top: 36px; height: auto; padding-top: 6px; padding-bottom: 6px; }
.et-rec-grid td { max-width: 110px; }
.et-rec-grid td.is-main { font-weight: 500; }
.et .et-rec-field {
  position: sticky; left: 0; z-index: 1;
  width: 110px; max-width: 110px; padding-left: 10px;
  text-align: left;
  background: var(--surface-raised);
  border-right: 1px solid var(--sky-dark);
}
.et tbody .et-rec-field {
  font: 400 12px/16px var(--font-sans); color: var(--ink-light);
  border-top: 1px solid var(--sky);
}
.et thead .et-rec-field { z-index: 3; }
.et-rec-note {
  display: block;
  font: 400 11px/14px var(--font-sans); letter-spacing: 0;
  text-transform: none; color: var(--ink-light);
}
.et-cmp-sums {
  flex: 0 0 auto; padding: 2px 10px;
  font: 400 11px/16px var(--font-sans); color: var(--ink-lighter);
  border-left: 1px solid var(--sky); border-right: 1px solid var(--sky);
  border-bottom: 1px solid var(--sky);
}
.et-cmp-sums > summary { cursor: pointer; }
.et-cmp-sum { padding-left: 14px; }
.et-cmp-sum-what { color: var(--ink); margin-right: 6px; }

/* The errors of an import (design/abgleich-view.jsx). */
.ci-errors {
  display: flex; flex-direction: column; gap: 3px;
  margin-top: 12px; padding: 8px 10px; max-height: 160px; overflow: auto;
  border: 1px solid var(--critical); border-radius: 4px;
}
.ci-errors-head {
  font: 500 11px/16px var(--font-sans); color: var(--critical);
}
.ci-error { font: 400 11px/16px var(--font-sans); color: var(--ink-light); }

/* ──────────────────────────────────────────────────────────
   Selected cell (design/v2-table.jsx)
   A click on a cell fills the right column, so the table
   has to say which cell the panel describes: a border on the cell, a tint on its
   row. Both survive a sort, because they are keyed on the row and the attribute
   rather than on a position.
   ────────────────────────────────────────────────────────── */
.et-row.is-selected .et-cell { background: var(--blue-lightest); }
/* A bar on the leading cell, the same shape the flag marker uses — so a row
   that is both keeps the flag, which says something about the data, over the
   selection, which says only where the analyst is standing. */
.et-row.is-selected:not(.is-flagged):not(.is-warning) .et-cell:first-child {
  box-shadow: inset 3px 0 0 var(--blue);
}
/* Inset, not a real border: a border would widen the cell and shift every
   value to its right by a pixel on selection. */
.et-cell.is-active { box-shadow: inset 0 0 0 2px var(--blue); }
.et-row.is-selected .et-cell.is-active { box-shadow: inset 0 0 0 2px var(--blue); }

/* ──────────────────────────────────────────────────────────
   Attribute source panel (design/attr-panel.jsx)
   ────────────────────────────────────────────────────────── */
.ap { display: flex; flex-direction: column; height: 100%; min-height: 0; overflow: hidden; }

.ap-head {
  min-height: 40px;
  padding: 4px 8px 4px 14px;
  border-bottom: 1px solid var(--sky);
  background: var(--sky-light);
  display: flex; align-items: center; gap: 8px;
  flex-shrink: 0;
}
.ap-head-title {
  margin: 0;
  font: 700 10px/16px var(--font-sans);
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--ink-lighter);
}

/* The two views of the panel. `.tab` draws them; this only stretches the band to
   the head's full height, which is what puts the active tab's line on the head's
   own bottom edge instead of somewhere in the middle of it. */
.ap-tabs {
  display: flex; align-items: stretch; align-self: stretch; gap: 2px;
  margin: -4px 0;
}

.ap-ent-head {
  display: flex; align-items: center; gap: 6px;
  width: 100%; min-width: 0;
  padding: 3px 4px;
  border: 0; border-radius: 4px;
  background: transparent; cursor: pointer;
  text-align: left;
}
.ap-ent-head:hover { background: var(--sky-lighter); }
.ap-ent-head.is-open { background: var(--sky-light); }
.ap-ent-name {
  font: 500 14px/20px var(--font-sans); color: var(--ink);
  flex: 0 1 auto; min-width: 60px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ap-ent-icon { font-size: 18px; color: var(--ink-lighter); margin-left: auto; }

.ap-body { flex: 1 1 auto; overflow-y: auto; min-height: 0; }

.ap-level { padding: 10px 14px; border-bottom: 1px solid var(--sky); }
.ap-level-head {
  display: flex; align-items: center; gap: 6px;
  margin-bottom: 6px;
  font: 700 10px/16px var(--font-sans);
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--ink-lighter);
}
.ap-level-num {
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--sky); color: var(--ink-light);
  font: 700 10px/16px var(--font-sans); letter-spacing: 0;
}
/* The head is set in spaced capitals; a count and a button in it are not. Both
   properties inherit, so "5 Eingaben" arrived as spread-out capitals wrapped
   over two lines. */
.ap-level-head .ap-muted,
.ap-level-head button {
  text-transform: none; letter-spacing: 0; white-space: nowrap;
}

.ap-muted { font: 400 12px/17px var(--font-sans); color: var(--ink-lighter); }

.ap-ins { margin-top: 8px; display: flex; flex-direction: column; gap: 6px; }
.ap-ins-row { display: flex; gap: 8px; align-items: flex-start; }
.ap-ins-icon { font-size: 16px; color: var(--blue); flex-shrink: 0; }
.ap-ins-body { display: flex; flex-direction: column; min-width: 0; }
.ap-ins-label { font: 500 12px/17px var(--font-sans); color: var(--ink); }

/* Block 5, the focused value. `.ap-attr-line` is the whole block and not one row
   in it: six browser tests ask "did the click reach an attribute" by counting it,
   and one reads its text to tell two attributes apart. */
.ap-attr-line { display: flex; flex-direction: column; }
/* Small caps rather than `text-transform: uppercase`, and the reason is not
   taste: the eyebrow carries the FIELD'S NAME, and thirteen browser tests read
   it to say which attribute the dossier is on. `text-transform` changes the text
   a reader — and `innerText` — gets back, so it would turn every one of those
   into a claim about capitalisation. */
.ap-attr-label {
  font: 700 10px/14px var(--font-sans);
  letter-spacing: 1.5px; font-variant-caps: all-small-caps;
  color: var(--ink-lighter);
}
.ap-attr-value {
  font: 500 22px/30px var(--font-sans); color: var(--ink);
  min-width: 0; overflow-wrap: anywhere;
}
.ap-attr-state { display: flex; align-items: center; gap: 8px; margin: 2px 0 8px; }
.ap-attr-rule {
  margin-top: 4px;
  font: 400 11px/18px var(--font-sans); color: var(--ink-lighter);
}
.ap-attr-note {
  display: flex; align-items: flex-start; gap: 6px;
  margin-top: 8px; padding: 6px 8px;
  border: 1px dashed var(--ink-lighter); border-radius: 4px;
  background: var(--sky-lighter);
  font: 400 11px/16px var(--font-sans); color: var(--ink);
}
.ap-attr-note .material-icons { font-size: 15px; color: var(--ink-light); }

/* The action row, its own band under block 6. `flex-wrap` because three buttons
   with German labels do not fit one 349px pane, and a row that clipped the third
   would hide the gesture that leaves the dossier. */
.ap-actions { display: flex; flex-wrap: wrap; gap: 6px; }
.ap-note {
  margin-top: 6px;
  font: 400 11px/16px var(--font-sans);
  color: var(--ink-lighter);
}

/* One row per entry, one kind per row. The rows of block 6 are nested — a node
   per envelope, its older versions under it — and every row has this class. */
.ap-entry {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 8px; margin: 0 -8px;
  border-radius: 4px;
  cursor: pointer;
}
.ap-entry:hover { background: var(--sky-lighter); }
.ap-entry.is-active { background: var(--sky-light); box-shadow: inset 2px 0 0 var(--blue); }
.ap-entry-icon { font-size: 16px; color: var(--ink-lighter); flex-shrink: 0; }
.ap-entry-body { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.ap-entry-label {
  font: 500 12px/17px var(--font-sans); color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ap-entry-value {
  font: 400 11px/16px var(--font-sans); color: var(--ink-lighter);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ap-entry-nokey { flex-shrink: 0; white-space: nowrap; }
.ap-entry-top { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
/* The figure stands on every node, so the two strings in this row compete
   for the same width at every entry and not only at a named rule input. The
   figure is short and keeps its size; the document's name takes what is left and
   is cut with an ellipsis — the other way round the name would shrink to one
   letter. */
.ap-entry-fig {
  flex: 0 0 auto; min-width: 0; max-width: 50%;
  font: 500 13px/18px var(--font-sans); color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ap-entry-top .ap-entry-label { flex: 1 1 auto; min-width: 0; }
.ap-entry-meta {
  font: 400 11px/16px var(--font-sans); color: var(--ink-lightest);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The date says which day it is in words, so it is its own element and not a
   piece of a joined string. It opens the same source the row does — the row's
   own click — and the pointer says so. */
.ap-entry-date, .ap-entry-fig { cursor: pointer; }
/* A SUPERSEDED READING is struck through — what it says is not what the envelope
   says any more. It stays confirmable all the same: the quote behind it is still
   a quote a person can check. The strike sits on the value and never on the
   document's name: the reading is superseded, the paper is not.

   An AGREEING quote never gets this class. It says the same thing the node says,
   and a line through it would report a contradiction that is not there. */
.ap-entry.is-older .ap-entry-fig,
.ap-entry.is-older .ap-entry-value { text-decoration: line-through; }
.ap-entry.is-older .ap-entry-label { font-weight: 400; color: var(--ink-light); }

/* Block 6, the derivation as a tree. The rail on the left is what says the nodes
   belong to the rule above them; the dashed ones say a folded entry belongs to
   its node. Two folds, and each names itself: the analyst has to be able to tell
   a second document that CONFIRMS the value from one it REPLACED. */
.ap-node { display: flex; flex-direction: column; padding-left: 6px; }
.ap-node + .ap-node { border-top: 1px solid var(--sky-lighter); }
.ap-node-support,
.ap-node-hist {
  margin-left: 24px; padding-left: 6px;
  border-left: 1px dashed var(--sky-dark);
}
.ap-node-head {
  margin: 4px 0 2px;
  font: 700 10px/14px var(--font-sans);
  letter-spacing: 1.25px; text-transform: uppercase;
  color: var(--ink-lighter);
}
/* The dot and the button of the card sit at the top of it, beside the name — the
   card is a row that grew, not a second kind of entry. */
.ap-rule {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto;
  align-items: start; gap: 0 8px;
  margin: 0 0 8px; padding: 8px 10px;
  border: 1px solid var(--sky); border-radius: 4px;
  background: var(--sky-lighter);
}
.ap-rule-body { display: flex; flex-direction: column; min-width: 0; }
.ap-rule-formula {
  margin-top: 6px; padding: 5px 8px;
  border: 1px solid var(--sky); border-radius: 4px;
  background: var(--surface-raised);
  font: 400 11px/16px var(--font-mono);
  color: var(--ink); text-wrap: pretty;
}
/* The summands of a sum, between the rule card and the quotes behind them. Built
   like `.ap-result` and placed at the other end of the list: the one says what
   went in, the other what came out. It wraps, because a contract with sixteen
   units still shows three numbers and a count and German words are long. */
.ap-sum {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 8px;
  margin: 0 0 8px; padding: 7px 10px;
  border-radius: 4px; background: var(--sky-lighter);
}
.ap-sum-head {
  font: 700 10px/14px var(--font-sans);
  letter-spacing: 1.25px; text-transform: uppercase;
  color: var(--ink-lighter);
}
.ap-sum-value {
  min-width: 0;
  font: 400 11px/16px var(--font-mono); color: var(--ink); text-wrap: pretty;
}
.ap-result {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 8px;
  margin-top: 8px; padding: 7px 10px;
  border-radius: 4px; background: var(--sky-light);
}
.ap-result-head {
  font: 700 10px/14px var(--font-sans);
  letter-spacing: 1.25px; text-transform: uppercase;
  color: var(--ink-lighter);
}
.ap-result-value { font: 500 14px/20px var(--font-sans); color: var(--ink); }

/* The rent path of a letting (design/rent-path.jsx). Sections in the order
   the rent is built, the evidence of each field under it as ordinary nodes. */
.rp-cond-head {
  margin: 4px 0 6px; font: 700 10px/14px var(--font-sans);
  letter-spacing: 1.25px; text-transform: uppercase; color: var(--ink-lighter);
}
.rp-sec { padding: 6px 0; border-bottom: 1px solid var(--border-default); }
.rp-sec-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
  font: 500 12px/17px var(--font-sans); color: var(--ink);
}
.rp-sec-value { font: 500 13px/18px var(--font-mono); color: var(--ink); white-space: nowrap; }
.rp-field, .rp-read { padding: 3px 0 3px 10px; }
.rp-read { padding-left: 20px; }
.rp-field-top { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.rp-field-label { font: 400 12px/17px var(--font-sans); color: var(--ink-light); }
.rp-field-value { font: 500 12px/17px var(--font-sans); color: var(--ink); text-align: right; }
.rp-none, .rp-note, .rp-sub, .rp-why, .rp-source {
  font: 400 11px/16px var(--font-sans); color: var(--ink-lighter);
}
.rp-none { font-style: italic; }
.rp-proof { margin-left: 8px; }
.rp-steps { width: 100%; border-collapse: collapse; margin: 4px 0; }
.rp-steps th {
  text-align: right; padding: 2px 4px; font: 500 10px/14px var(--font-sans);
  color: var(--ink-lighter); border-bottom: 1px solid var(--border-default);
}
.rp-steps td {
  text-align: right; vertical-align: top; padding: 3px 3px; white-space: nowrap;
  font: 400 11px/16px var(--font-sans); font-variant-numeric: tabular-nums; color: var(--ink);
}
.rp-steps td.rp-why { text-align: left; white-space: normal; padding-top: 0; }
.rp-steps th:first-child, .rp-steps td:first-child { text-align: left; }
.rp-step.is-nostep td { color: var(--ink-lighter); }
.rp-step + .rp-step tr:first-child td { border-top: 1px solid var(--border-default); }
.rp-why { color: var(--blue); }
/* Staffel: the step at the as-of date marked, later steps grey. */
.rp-stage + .rp-stage tr:first-child td { border-top: 1px solid var(--border-default); }
.rp-stage.is-current tr:first-child td { font-weight: 600; background: var(--sky-light); }
.rp-stage.is-later td { color: var(--ink-lighter); }
.rp-stages { table-layout: fixed; }
.rp-steps td.rp-stage-paper {
  text-align: left; white-space: normal; overflow: hidden; padding-top: 0; font-weight: 400;
}
.rp-result {
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
  margin-top: 8px; padding: 7px 10px; border-radius: 4px; background: var(--sky-light);
  font: 500 12px/17px var(--font-sans); color: var(--ink);
}
.rp-foot { margin-top: 8px; text-align: right; font-size: 11px; }
/* The rent of a contract, one line per letting. The line is the link. */
.rp-letting {
  display: grid; grid-template-columns: 1fr auto; gap: 0 8px; width: 100%;
  padding: 4px 6px 4px 10px; border: 0; border-radius: 4px; background: none;
  text-align: left; cursor: pointer; font: 400 12px/17px var(--font-sans); color: var(--ink);
}
.rp-letting:hover { background: var(--sky-light); }
.rp-letting-unit { color: var(--blue); text-decoration: underline; overflow-wrap: anywhere; }
.rp-letting-amount { font: 500 12px/17px var(--font-mono); white-space: nowrap; text-align: right; }
.rp-letting-reason { grid-column: 1 / -1; color: var(--ink-light); }

/* One block per document class. The rows inside are .ap-entry unchanged. */
.ap-docgroup + .ap-docgroup { margin-top: 10px; }
.ap-docgroup-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
  margin-bottom: 4px;
  font: 700 10px/16px var(--font-sans);
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--ink-lighter);
}

/* ── Block 2: the findings line ────────────────────────
   ONE LINE, NOT CARDS. Cards would repeat the queue sentence for sentence
   and carry its three actions; what the dossier owes the analyst is
   the count, the money and the way over, and that fits in the 32px of a
   `.mod-bar`. The whole line is the button, like `.ap-ent-head`.
   Full-bleed under the head, with the same rule under it the head has. */
.ap-findings-link {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 8px;
  height: 32px; padding: 0 14px;
  border: 0; border-bottom: 1px solid var(--sky);
  background: transparent; cursor: pointer;
  text-align: left;
}
.ap-findings-link:hover { background: var(--sky-lighter); }
.ap-findings-link-text {
  flex: 1 1 auto; min-width: 0;
  font: 500 13px/20px var(--font-sans); color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The money is the second reading, not the headline: it says how much the count
   is worth, and a contract whose findings carry no impact shows no sum at all. */
.ap-findings-link-sum {
  flex: 0 0 auto;
  font: 400 12px/20px var(--font-sans); color: var(--ink-light);
  white-space: nowrap;
}
.ap-findings-link-go { font-size: 16px; color: var(--ink-lighter); flex: 0 0 auto; }

/* ── Block 3: the finding head ───────────────────────────────
   The caption over the attribute tabs: which finding they belong to. Fixed with
   the link line above it, same height, same hairline — the two read as one
   two-line statement about the contract, the count first and the worst sentence
   under it. Deliberately NOT a button: it decides nothing, so it gets no hover
   and no pointer. */
.ap-inst-head {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 8px;
  height: 32px; padding: 0 14px;
  border-bottom: 1px solid var(--sky);
  background: var(--sky-lighter);
}
.ap-inst-headline {
  flex: 1 1 auto; min-width: 0;
  font: 400 12px/20px var(--font-sans); color: var(--ink-light);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ── Block 4: the attribute card and its tabs ────────────────
   `.ap-attr-blocks` is the wrapper the three attribute blocks always sit in and
   it styles nothing at all; `.ap-card` is added only in finding mode, where
   there is something to switch between. A frame around blocks that never change
   under one another would be decoration.

   The frame is the whole point: press a tab and the value, the derivation and
   the buttons all change together, so they have to look like one body. The
   field list under it stays outside, because it does not. */
.ap-card {
  margin: 10px 8px;
  border: 1px solid var(--sky);
  border-radius: 6px;
  background: var(--surface-raised);
  overflow: hidden;
}
/* The blocks keep their own dividers, except the last one: a hairline right
   above the card's own bottom edge is two lines 1px apart. */
.ap-card > .ap-level:last-child { border-bottom: 0; }

/* The tab strip on the card's top edge. `align-items: stretch` for the same
   reason `.ap-tabs` has it: it puts the active tab's underline on the strip's
   own bottom edge rather than somewhere in the middle of it. The buttons are
   `.tab`, the house's own — this adds the strip, not a second tab appearance. */
.ap-ftabs {
  display: flex; align-items: stretch; gap: 2px;
  height: 32px; padding: 0 4px;
  border-bottom: 1px solid var(--sky);
  background: var(--sky-lighter);
  overflow-x: auto;
}
/* A field the rule read but this contract does not carry. Drawn, so the reader
   sees what the finding rests on; dead, because there is no level 2 to open. */
.ap-ftab:disabled {
  color: var(--ink-lightest); cursor: default;
}
.ap-ftab:disabled:hover { color: var(--ink-lightest); }

/* ── Block 7: the field list ────────────────────────────────
   Full-bleed rows, so the list reads as an index of the contract rather than as
   a fourth level of the derivation. Values are deliberately absent: they are in
   the table, and a second copy is a second thing to keep in step. */
.ap-fields { padding-left: 0; padding-right: 0; }
.ap-fields .ap-level-head { padding: 0 14px; }
.ap-field {
  display: flex; align-items: center; gap: 10px;
  padding: 6px 14px;
  border-bottom: 1px solid var(--sky-light);
  cursor: pointer;
}
.ap-field:hover { background: var(--sky-lighter); }
.ap-field.is-active { background: var(--sky-light); }
.ap-field.is-active .ap-field-label { font-weight: 500; }
.ap-field-label {
  font: 400 12px/18px var(--font-sans); color: var(--ink);
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ap-field-hint {
  font: 400 11px/16px var(--font-sans); color: var(--ink-lightest);
  white-space: nowrap;
}

.ap-selected {
  flex-shrink: 0;
  padding: 6px 14px;
  border-top: 1px solid var(--sky);
  background: var(--sky-lighter);
  display: flex; align-items: baseline; gap: 8px;
  overflow: hidden;
}
.ap-selected-label {
  font: 700 10px/16px var(--font-sans);
  letter-spacing: 1.5px; text-transform: uppercase;
  color: var(--ink-lighter);
  flex-shrink: 0;
}
.ap-selected-name {
  font: 500 12px/17px var(--font-sans); color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ── The dictionary page (design/v2-dictionary.jsx) ──────────
   One row per attribute in a .data-table, with a group header per registry
   file and a detail block that opens on click. The prose is long — an
   attribute description and the gif-IDA description of its field — so it
   never sits in a cell. */
.dict-group td {
  background: var(--sky-lighter);
  border-top: 1px solid var(--sky-dark);
}
.dict-group-label { font: 700 12px/18px var(--font-sans); color: var(--ink); }
.dict-group-name,
.dict-group-prefix,
.dict-group-ida,
.dict-group-ext { margin-left: 10px; font-size: 11px; }
.dict-group-name { color: var(--ink-light); }
.dict-group-ida { color: var(--blue-darker); }
.dict-group-ext { color: var(--badge-purple-fg); }
.dict-group-count { margin-left: 10px; font-size: 11px; }

.dict-sub { font-size: 11px; margin-top: 2px; }
.dict-unit { margin-left: 6px; font-size: 11px; }

.dict-detail-row td { background: var(--surface-sunken); }
.dict-detail { display: flex; flex-direction: column; gap: 10px; padding: 4px 0 6px; }
.dict-prose { max-width: 90ch; }
.dict-prose-label,
.dict-fact-label {
  font: 700 10px/16px var(--font-sans);
  letter-spacing: 1.5px; text-transform: uppercase;
  color: var(--ink-lighter);
}
.dict-prose p { margin: 2px 0 0; font: 400 13px/19px var(--font-sans); color: var(--ink); }

/* The values of a vocabulary of ours: our key, our label, and the gif-IDA value it
   narrows to. Three columns so the narrowing lines up down the list -- eleven of
   the thirteen service types collapse onto NOT_SPECIFIED, and that is the point. */
.dict-values { margin: 4px 0 0; padding: 0; list-style: none; }
.dict-values li {
  display: grid; grid-template-columns: 200px 1fr 160px;
  gap: 8px; align-items: baseline; padding: 1px 0;
}
.dict-values code { font: 400 12px/18px var(--font-mono); color: var(--ink); }
.dict-values span { font: 400 12px/18px var(--font-sans); color: var(--ink-light); }
.dict-narrow { color: var(--ink-lighter) !important; }

.dict-facts {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 8px 16px;
}
.dict-fact-value { font: 400 12px/17px var(--font-sans); color: var(--ink); }

/* The Themenbereich over its entities in the dictionary, and the ways
   into the Herleitung from a column head of the lease table and from the
   dossier. The head's link shows on hover and on focus, so the label keeps its
   width. It sits in the head's empty left padding, the room of a row's dot, so
   it covers neither the label nor the sort arrow. */
.dict-area td {
  padding-top: 14px;
  font: 700 12px/18px var(--font-sans); color: var(--ink);
  border-bottom: 1px solid var(--sky-dark);
}
.et-th-lineage {
  position: absolute; top: 8px; left: 5px;
  padding: 0; border: 0; background: var(--surface-raised);
  color: var(--blue); cursor: pointer; opacity: 0;
}
.et-th-lineage .material-icons { font-size: 14px; vertical-align: top; }
.et thead th:hover .et-th-lineage,
.et-th-lineage:focus-visible { opacity: 1; }
.ap-lineage { display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: baseline; }

/* ── Herleitung: the flow page ───────────────────────────────────────────
   Two panes. The picker is a fixed rail so the boxes never reflow while the
   analyst walks the list; the drawing scrolls sideways because a six-stage flow
   is wider than the workspace and squeezing it would make the boxes unreadable.
   Every colour is a token, so the dark theme needs nothing of its own. */
/* `.page` already has a definite height and scrolls; without these three rules
   the 207-row picker stretches it to 6400px and the whole page scrolls instead
   of the list. The two panes must scroll inside their own boxes, or the boxes
   of the drawing walk off the top while an analyst looks for an attribute. */
.lin-page { display: flex; flex-direction: column; overflow: hidden; }
.lin-split {
  flex: 1 1 auto; min-height: 0;
  display: grid; grid-template-columns: 260px 1fr; gap: 16px;
}

.lin-picker { display: flex; flex-direction: column; gap: 8px; min-height: 0; }
.lin-search {
  padding: 6px 10px; border: 1px solid var(--border-default);
  border-radius: var(--radius-sm); background: var(--surface-raised);
  color: var(--text-primary); font: 400 13px/19px var(--font-sans);
}
.lin-filters { display: flex; gap: 4px; }
.lin-filter {
  flex: 1; padding: 4px 8px; cursor: pointer;
  border: 1px solid var(--border-default); border-radius: var(--radius-sm);
  background: var(--surface-raised); color: var(--text-secondary);
  font: 400 12px/18px var(--font-sans);
}
.lin-filter.is-on { background: var(--blue); border-color: var(--blue); color: var(--text-on-accent); }

.lin-list { overflow: auto; min-height: 0; }
.lin-group-head {
  display: flex; justify-content: space-between; align-items: baseline;
  padding: 8px 4px 4px; color: var(--text-secondary);
  font: 500 11px/16px var(--font-sans); text-transform: uppercase; letter-spacing: .06em;
}
.lin-group-count { color: var(--text-tertiary); letter-spacing: 0; }
.lin-item {
  display: flex; justify-content: space-between; align-items: baseline; gap: 8px;
  width: 100%; padding: 4px 6px; cursor: pointer; text-align: left;
  border: 0; border-radius: var(--radius-sm); background: transparent;
  color: var(--text-primary); font: 400 13px/19px var(--font-sans);
}
.lin-item:hover { background: var(--surface-hover); }
.lin-item.is-on { background: var(--blue); color: var(--text-on-accent); }
.lin-item-producer { font: 400 11px/16px var(--font-mono); color: var(--text-tertiary); }
.lin-item.is-on .lin-item-producer { color: var(--text-on-accent); }

/* Back and forward, the filter by area, the area heads. */
.lin-picker-top { display: flex; gap: 4px; align-items: center; }
.lin-picker-top .lin-search { flex: 1 1 auto; min-width: 0; }
.lin-hist {
  display: inline-flex; padding: 4px; cursor: pointer;
  border: 1px solid var(--border-default); border-radius: var(--radius-sm);
  background: var(--surface-raised); color: var(--text-secondary);
}
.lin-hist:disabled { opacity: .4; cursor: default; }
.lin-hist .material-icons { font-size: 18px; }
.lin-area {
  padding: 5px 8px; border: 1px solid var(--border-default);
  border-radius: var(--radius-sm); background: var(--surface-raised);
  color: var(--text-primary); font: 400 13px/19px var(--font-sans);
}
.lin-section-head {
  padding: 12px 4px 4px; border-bottom: 1px solid var(--border-default);
  color: var(--text-primary); font: 600 12px/18px var(--font-sans);
}

.lin-detail { min-width: 0; overflow: auto; display: flex; flex-direction: column; gap: 12px; }
.lin-head { display: flex; flex-direction: column; gap: 4px; }
.lin-title { margin: 0; font: 500 18px/26px var(--font-sans); color: var(--text-primary); }
.lin-addr { font: 400 12px/18px var(--font-mono); color: var(--text-tertiary); }
.lin-prose {
  margin: 2px 0 0; max-width: 78ch;
  font: 400 13px/19px var(--font-sans); color: var(--text-secondary);
}
.lin-prose.is-clamped {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden;
}
.lin-more {
  padding: 0; border: 0; background: none; cursor: pointer;
  font: 400 12px/18px var(--font-sans); color: var(--blue);
}
.lin-warn { flex-basis: 100%; margin: 0; font: 400 12px/18px var(--font-sans); color: var(--warning); }
.lin-loading {
  flex-basis: 100%; margin: 0; font: 400 12px/18px var(--font-sans); color: var(--text-secondary);
}
.lin-depth {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  font: 400 13px/19px var(--font-sans); color: var(--text-secondary);
}
.lin-day { font: 400 13px/19px var(--font-sans); color: var(--text-secondary); }

.lin-scroll { overflow-x: auto; padding-bottom: 8px; }
.lin-flow { position: relative; }
.lin-cols { position: relative; height: 20px; }
.lin-col-head {
  position: absolute; top: 0;
  font: 500 11px/16px var(--font-sans); text-transform: uppercase; letter-spacing: .06em;
  color: var(--text-tertiary);
}
.lin-canvas { position: relative; }
.lin-edges { position: absolute; left: 0; top: 0; overflow: visible; }
.lin-edge { fill: none; stroke: var(--rule); stroke-width: 1.5; }
/* A dashed edge judges the value; a solid one produced it. The one distinction
   OpenLineage's column facet draws, and the one filter the two audiences need. */
.lin-edge-indirect { stroke-dasharray: 3 4; }

.lin-box {
  position: absolute; box-sizing: border-box; padding: 6px 8px;
  border: 1px solid var(--border-default); border-radius: var(--radius-sm);
  background: var(--surface-raised); box-shadow: var(--shadow-d1);
}
.lin-box-attribute { border-color: var(--blue); border-width: 2px; }
.lin-box-gate { border-style: dashed; }
.lin-box-signal { border-style: dashed; }
.lin-box.is-walkable { cursor: pointer; }
.lin-box.is-walkable:hover { border-color: var(--blue); }
.lin-box-head { display: flex; flex-direction: column; gap: 1px; margin-bottom: 4px; }
.lin-box-kind {
  font: 500 10px/14px var(--font-sans); text-transform: uppercase; letter-spacing: .06em;
  color: var(--text-tertiary);
}
.lin-box-name {
  font: 400 12px/16px var(--font-mono); color: var(--text-primary);
  overflow-wrap: anywhere;
}
/* A value that does not fit beside its key takes the next line, whole, and
   does not break at a dot: "2.0.0" beside "Prompt-Version laut Konfiguration"
   read "2.0." on one line and "0" on the next. */
.lin-fact {
  display: flex; justify-content: space-between; gap: 6px; align-items: baseline;
  flex-wrap: wrap;
}
.lin-fact-key { font: 400 11px/17px var(--font-sans); color: var(--text-tertiary); }
/* No clamp. The page measures what it drew and lays out again, so a value may
   take as many lines as it needs -- and a truncated merge strategy or formula
   is exactly the half-answer this page exists to remove. The full text is in
   the title attribute as well, for the values a hover reaches faster. */
.lin-fact-val {
  font: 400 11px/17px var(--font-sans); color: var(--text-secondary);
  text-align: right; overflow-wrap: anywhere; margin-left: auto;
}
/* A technical fact is dimmer than a plain one: the deep view adds detail, it
   does not change what the page is about. */
.lin-fact.is-dev .lin-fact-key,
.lin-fact.is-dev .lin-fact-val { color: var(--text-tertiary); }

/* The formula panel under the flow: the code of a rule, read by the route.
   Docstring open, source folded behind <details>. Neither the
   flow nor the panel may shrink: .lin-detail is a flex column that scrolls, and
   a shrinking .lin-scroll folds the drawing to its column heads. */
.lin-detail > .lin-scroll, .lin-formula { flex-shrink: 0; }
.lin-box-hidden { border-style: dotted; }
.lin-box.is-open { box-shadow: 0 0 0 2px var(--blue); }
.lin-box-open { margin-top: 4px; font: 500 11px/17px var(--font-sans); color: var(--blue); }
.lin-formula {
  display: flex; flex-direction: column; gap: 8px; max-width: 100ch;
  padding: 12px; border: 1px solid var(--border-default); border-radius: var(--radius-sm);
  background: var(--surface-raised);
}
.lin-formula-title { margin: 0; font: 500 15px/22px var(--font-sans); color: var(--text-primary); }
.lin-formula-sub {
  margin: 8px 0 0; font: 500 11px/16px var(--font-sans); text-transform: uppercase;
  letter-spacing: .06em; color: var(--text-tertiary);
}
.lin-formula-expr { font: 400 13px/19px var(--font-mono); color: var(--text-primary); }
.lin-code {
  display: flex; flex-direction: column; gap: 4px;
  padding-top: 6px; border-top: 1px solid var(--rule);
}
.lin-code-head {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px;
  font: 500 13px/19px var(--font-sans); color: var(--text-primary);
}
.lin-code-where { font: 400 11px/17px var(--font-sans); color: var(--text-tertiary); }
.lin-code-where.mono { font-family: var(--font-mono); }
.lin-code-none { margin: 0; font: 400 12px/18px var(--font-sans); color: var(--text-tertiary); }
.lin-doc {
  margin: 0; white-space: pre-wrap; font: 400 13px/19px var(--font-sans);
  color: var(--text-secondary);
}
.lin-source summary { cursor: pointer; font: 400 12px/18px var(--font-sans); color: var(--blue); }
.lin-source pre {
  margin: 4px 0 0; padding: 8px; overflow-x: auto; border-radius: var(--radius-sm);
  background: var(--surface-sunken);
  font: 400 12px/17px var(--font-mono); color: var(--text-primary);
}

/* The head of the attribute: title and address on one line, and under
   them description, note, values of our own vocabulary and the gif-IDA line. */
.lin-head-title { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; }
.lin-head-key { font: 500 12px/18px var(--font-sans); color: var(--text-tertiary); }
.lin-note { color: var(--text-tertiary); }
.lin-values { max-width: 78ch; }
.lin-values ul {
  margin: 2px 0 0; padding: 0; list-style: none;
  display: flex; flex-wrap: wrap; gap: 4px 12px;
  font: 400 12px/18px var(--font-sans); color: var(--text-secondary);
}
.lin-values li { display: inline-flex; gap: 6px; align-items: baseline; }
.lin-ida {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px;
  font: 400 12px/18px var(--font-sans); color: var(--text-secondary);
}
.lin-link {
  padding: 0; border: 0; background: none; cursor: pointer;
  font: 500 12px/18px var(--font-sans); color: var(--blue);
}
.lin-link:hover { text-decoration: underline; }

/* The check panel: a count per verdict, and the signals that cannot judge
   here folded under their name. */
.lin-count {
  padding: 0 6px; border-radius: var(--radius-sm); background: var(--surface-sunken);
  font: 400 12px/18px var(--font-mono); color: var(--text-secondary);
}
.lin-check-signal { display: flex; flex-direction: column; gap: 4px; }
.lin-check-not summary { font-family: var(--font-mono); }

/* The entity view under the flow: drawn like the panels, but its own
   class, because it stands beside an open panel. One block per reference, and
   the attribute on screen marked in the list of weighted ones. */
.lin-entity {
  flex-shrink: 0; max-width: 100ch;
  padding: 12px; border: 1px solid var(--border-default); border-radius: var(--radius-sm);
  background: var(--surface-raised);
}
.lin-entity > summary { cursor: pointer; }
.lin-entity-body { display: flex; flex-direction: column; gap: 8px; margin-top: 8px; }
.lin-entity-ref { gap: 6px; }
.lin-entity-here .lin-link { color: var(--text-primary); }

/* ── Insights queue (design/insight-queue.jsx) ─────────────────
   One row per finding instance, bundled by contract. Behind
   cap.insight_queue; the entity table is what the page shows without it.

   Tokens only. Severity has one colour of its own, `--finding`, and it appears
   on nothing but the glyph: the row itself stays uncoloured, so a column of
   rows reads as a ranking of dots rather than as a wall of alarm (chrome.jsx
   `SEV_TONE` says why). The other colour in here is the selection blue, and it
   is the same blue and the same inset the entity table marks a selected row
   with — one gesture, one appearance, whichever surface it happened on.

   The grid drops columns by the width of THIS element, not of the window --
   for the queue row alone. The entity table hides nothing: a table is read
   across, a queue
   row is read down, and the queue row has to survive a 520px pane. Making it a
   query container is what lets one declaration serve the full-width page and the
   three-column workspace. See .iq-grid below for the measurement. */
.iq {
  display: flex; flex-direction: column; border-top: 1px solid var(--sky);
  container-type: inline-size;
  /* The height of the box it stands in, not the height of its rows. Whatever
     holds the queue clips rather than scrolls -- `.ws3-body` in the workspace,
     `.page.page-table` on the full-width page -- so a queue sized by its content
     loses every row past the fold. The entity table takes the same two
     declarations, on `.et-pane`, for the same reason. */
  flex: 1 1 0; min-height: 0;
}

/* The scrolling half: the column head and the groups. The bands above it -- the
   status chips, the sort bar, the filters, the orphan note -- stay put, because
   they steer what is in here.

   `min-height: 0` is the one that does the work: without it a flex item's floor
   is the height of its content, and `overflow-y` then has nothing to scroll. */
.iq-scroll {
  flex: 1 1 0; min-height: 0;
  overflow-y: auto;
}

/* Seven columns, and the head shares them with every row — one declaration, so a
   header cannot drift out of line with the values under it. The finding column
   takes twice the field column and both are fractional: it holds two lines of
   prose, and the facts to its right are short and fixed.

   MEASURED, NOT GUESSED. At 1440 with the dossier open the queue is 599px wide.
   The state chip as a column of its own, like the design has it, would leave
   the headline 24px — unreadable. So the chip stands in the finding column (it
   is part of what the row says about the finding, not a fact beside it) and the
   three figure columns carry no slack. That gives the prose about 135px.

   The field column may collapse to 60px before the prose gives up its last: the
   row's title carries the field name, and nothing carries the reason.

   IN THE THREE-COLUMN WORKSPACE the work pane at the base fractions is 520px,
   and the finding column would fall to 45px there — the headline 7px, one
   character. The fixed columns and the gaps take 473px of
   the 500px inside the pane, so there is no slack left to rebalance. The row
   needs about 620px. The measurement and the ways out are in
   planning/2026-09-08-queue-zeile-schmale-spalte.md; the container query below
   is the way taken. */
/* The last column is the action cluster, 96px: three controls need 80px, and
   the 16px on top keep the deferral legible
   rather than clipped to its first character. The design gives the same cluster
   132px (line 108) and carries a sixth data column we do not: what pays for the
   widening here is that column's absence. */
.iq-grid {
  display: grid;
  grid-template-columns: 17px 2fr minmax(60px, 1fr) 80px 78px 94px 96px;
  gap: 8px;
  align-items: start;
}
.iq-grid > * { min-width: 0; }

/* Below 720px of pane the field and the trust columns go, and the finding gets
   their width. Which two is not a preference: the row's own title carries the
   field name, and the trust word is repeated by the state chip that already
   rides inside the finding column. Nothing else on the row carries the reason.

   ONE breakpoint, where the design has two (Variante-J lines 110 to 117). The
   design drops "Aufwand" at 880px first, and we have no such column: it has
   no source. Our seven columns therefore
   already ARE the design's 880px state, and its 720px state is the one below.

   The hidden children take no grid cell, so the five that remain fall on the
   five tracks: glyph, finding, impact, due, actions.

   MEASURED in the work pane at the base fractions, which is 497px inside: the
   fixed tracks and the gaps take 223px rather than 413px, so 274px of fraction
   is left and the finding column lands at 171px. Before the query it was 45px.

   The 160px floor is the design's figure (line 115) and it does NOT bind here --
   it binds when the analyst drags the pane below about 460px, and then it comes
   out of the impact column, which is a formatted number and survives on 62px.
   Declared rather than left to the fractions, because 45px was exactly what
   happens when nothing floors the column that carries the prose. */
@container (max-width: 720px) {
  .iq-grid {
    grid-template-columns: 17px minmax(160px, 2fr) minmax(0, 1fr) 78px 96px;
  }
  .iq-grid > :nth-child(3),
  .iq-grid > :nth-child(6) { display: none !important; }
}

.iq-kpi {
  display: flex; flex-wrap: wrap; gap: 5px;
  padding: 7px 10px;
  background: var(--surface-raised);
  border-bottom: 1px solid var(--sky);
}

/* The queue's own search. Its own class rather than the shared `.search-input`
   for the same reason `.ev-search` has one — two fields under one selector make
   every test that fills "the search field" ambiguous. The paint is `.ev-search`'s
   down to the pixel, because both sit in a narrow band and a field that looks
   different reads as a different gesture.
   It takes the whole first row of the band: the chips wrap under it and stay
   readable as a row of counts. */
.iq-search {
  display: inline-flex; align-items: center; gap: 8px;
  flex: 1 0 100%; min-width: 80px;
  height: 26px;
  padding: 0 8px;
  background: var(--surface-raised);
  border: 1px solid var(--sky);
  border-radius: 4px;
}
.iq-search input {
  flex: 1; border: 0; outline: none; min-width: 0;
  font: 400 12px/18px var(--font-sans);
  color: var(--ink);
  background: transparent;
}
.iq-search .material-icons { font-size: 18px; color: var(--ink-lighter); }
.iq-search:focus-within {
  border-color: var(--blue);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--blue) 10%, transparent);
}

/* The sort control, the filter toggle and the result count. Wraps rather than
   overflowing: this band is narrow. */
.iq-bar {
  display: flex; align-items: center; flex-wrap: wrap; gap: 4px 6px;
  padding: 5px 6px 5px 10px;
  background: var(--surface-raised);
  border-bottom: 1px solid var(--sky);
}
.iq-count {
  font: 400 11px/16px var(--font-sans);
  color: var(--ink-lightest);
  white-space: nowrap;
  padding-right: 4px;
}
.iq-filter-btn.has-filters { color: var(--blue-darker); }
.iq-filter-btn[aria-expanded="true"] { background: var(--sky-light); }

.iq-filters {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
  padding: 7px 10px;
  background: var(--sky-lighter);
  border-bottom: 1px solid var(--sky);
}
.iq-select, .iq-date {
  font: 400 11px/16px var(--font-sans);
  padding: 3px 5px;
  border: 1px solid var(--sky-dark);
  border-radius: 4px;
  background: var(--surface-raised);
  color: var(--ink);
}
.iq-due-label { font: 400 11px/16px var(--font-sans); color: var(--ink-light); }

/* Uppercase and small, the same voice as the entity table's header, so the two
   lists on this page read as one page. */
.iq-head {
  padding: 5px 8px 5px 10px;
  background: var(--surface-raised);
  border-bottom: 1px solid var(--sky-dark);
  font: 700 10px/14px var(--font-sans);
  letter-spacing: 1.25px;
  text-transform: uppercase;
  color: var(--ink-lightest);
  position: sticky; top: 0; z-index: 1;
}
.iq-num { text-align: right; }

.iq-group-head {
  display: flex; align-items: center; gap: 6px;
  padding: 9px 10px 3px;
  cursor: pointer;
}
.iq-group + .iq-group .iq-group-head {
  border-top: 1px solid var(--sky);
  margin-top: 8px;
}
.iq-group-chev { font-size: 15px; color: var(--ink-lightest); }
.iq-group-label {
  font: 500 11px/16px var(--font-sans); color: var(--ink-light);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* "3 Befunde · 2 offen · 1 erledigt" -- long enough to need a clip of its own.
   At 1440 with the dossier open the queue is 599px, and the tenant name beside
   this clips first; when even that is not enough, the tail of the sub-label goes
   rather than the head running under the sort figure. */
.iq-group-count {
  font: 400 11px/16px var(--font-sans); color: var(--ink-lightest); white-space: nowrap;
  min-width: 0; overflow: hidden; text-overflow: ellipsis;
}
/* Ruled off from the count, because it answers a different question: how many
   findings versus how they rank under the current sort. */
.iq-group-agg {
  font: 400 11px/16px var(--font-sans); color: var(--ink-lightest); white-space: nowrap;
  border-left: 1px solid var(--sky); padding-left: 8px;
}

.iq-row {
  padding: 8px 8px 9px 10px;
  cursor: pointer;
  border-bottom: 1px solid var(--sky-light);
}
.iq-row:hover { background: var(--sky-lighter); }
/* The same mark the entity table puts on a selected row. */
.iq-row.is-active {
  background: var(--blue-lightest);
  box-shadow: inset 3px 0 0 var(--blue);
}

/* One line each, clipped. A headline that wrapped to three lines would push the
   next finding off the screen, and the whole value of a queue is seeing the
   next one. The reason line carries the same rule and the row's title carries
   the field, so nothing is only available at full width. */
.iq-main { min-width: 0; }
.iq-headline {
  font: 500 12px/18px var(--font-sans); color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.iq-why {
  font: 400 11px/16px var(--font-sans); color: var(--ink-lighter);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.iq-field {
  font: 400 12px/18px var(--font-sans); color: var(--ink-light);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Figures right-aligned and tabular, so the digits line up down the column and
   two amounts can be compared without reading them. */
.iq-impact, .iq-due {
  font: 500 12px/18px var(--font-sans); color: var(--ink);
  text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap;
}
.iq-due { font-weight: 400; color: var(--ink-light); }

/* Trust is a word, not a dot: the dots on this page already mean per-attribute
   validation status, and a fourth kind of dot would invite the reader to
   reconcile it with the three in the table.

   Green and red here, and they do not collide with the severity dot beside
   them: severity owns one hue, `--finding`, and trust owns the two the
   validation dots already use. So the word reads the same as the dot it is
   derived from, and no reader has to ask whether a green word ranks below a
   purple dot. Both tokens invert in the dark theme, so neither is a
   light-theme-only decision. */
.iq-trust {
  font: 400 11px/18px var(--font-sans);
  color: var(--ink-lighter);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Ruled off from the deadline beside it. Both columns are short text with one
   right-aligned and one left, so without this "31.07.2027 Prüfung offen" reads
   as a single value — and the header pair "FRIST VERTRAUEN" reads as one word. */
.iq-trust, .iq-col-trust { padding-left: 10px; }
.iq-trust.is-actionable { color: var(--success-darker); }
.iq-trust.is-blocked { color: var(--critical-darker); }

.iq-empty {
  margin: 12px 10px;
  padding: 16px 12px;
  border: 1px dashed var(--sky-dark);
  border-radius: 6px;
  font: 400 12px/18px var(--font-sans);
  color: var(--ink-lighter);
  text-align: center;
}

/* ── The decision state ───────────────────────────────────────
   A finding is a piece of work, not an alarm: it can be seen, deferred to a day,
   or closed with a reason. What follows is that state on the row and the menu
   that sets it.

   The chips carry the design's `CHIP_TONE`, as tokens. Note what they are NOT:
   red. Severity owns colour on this page (the glyph), and a state chip
   competing with it would make every deferred warning look worse than an
   undecided critical. Blue for "your deferral came due" is the one loud one,
   because it is the only state the system chose rather than the analyst. */
.iq-item.is-open { background: var(--sky-lighter); }

/* Dismissed work stays legible and stops asking for attention. Not hidden: the
   "Erledigt" chip is a filter, and an analyst checking what was closed has to be
   able to read it. */
.iq-row.is-done { opacity: .72; }

/* Inline after the headline, not in a column of its own -- the measurement above
   says why. `flex-shrink: 0` so the chip keeps its date and the headline is the
   one that clips: a chip reading "WV 15.11." is a fact, a headline missing its
   last word is still a sentence. */
.iq-head-line { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.iq-head-line .iq-headline { min-width: 0; }
.iq-chip-state {
  flex: 0 0 auto;
  font: 500 10px/15px var(--font-sans);
  letter-spacing: .4px;
  padding: 1px 6px;
  border-radius: 10px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  border: 1px solid transparent;
}
.chip-snoozed {
  background: var(--sky-light); color: var(--ink-light); border-color: var(--sky-dark);
}
.chip-due {
  background: var(--blue-lightest); color: var(--blue-darker); border-color: var(--blue);
}
.chip-done {
  background: var(--success-lightest); color: var(--success-darker);
  border-color: var(--success-lightest);
}

/* The action cluster. Right-aligned and allowed to clip rather than to widen:
   the column is 96px and the menu button is the one that must never be cut, so
   it is last in the DOM and the flex row shrinks from the left. A deferred
   finding carries four controls and is the one case that still clips — and what
   goes is the deferral button, which is the least use on a finding somebody has
   already deferred. */
.iq-actions {
  display: flex; align-items: center; justify-content: flex-end;
  gap: 1px; overflow: hidden;
}
.iq-actions .icon-btn { width: 26px; height: 26px; flex: 0 0 auto; }
.iq-actions .material-icons { font-size: 18px; }
.iq-snooze7 {
  padding: 2px 4px; flex: 0 1 auto; min-width: 0;
  font: 400 11px/16px var(--font-sans);
}
.iq-reopen { color: var(--ink-lighter); }
/* The row's one primary button, and the only place a finding closes in a single
   click. 24px rather than the 28px `.btn-icon.btn-sm` gives it, because the
   cluster is 62px wide and holds three controls: the deferral, this, and the
   menu. */
.iq-done { width: 24px; height: 24px; padding: 0; flex: 0 0 auto; }
.iq-done .material-icons { font-size: 16px; }

/* The menu, inline under its row rather than in a popover: it holds a date, a
   text field and three buttons, and a popover over a scrolling list would leave
   the row it belongs to somewhere else on the screen. */
.iq-menu {
  display: flex; flex-direction: column; gap: 8px;
  padding: 9px 10px 11px;
  background: var(--sky-lighter);
  border-bottom: 1px solid var(--sky);
}
.iq-menu-row { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; }
.iq-menu-label {
  font: 400 11px/16px var(--font-sans); color: var(--ink-light); width: 78px;
}
.iq-preset {
  font: 400 11px/16px var(--font-sans);
  padding: 3px 9px; border-radius: 12px; cursor: pointer; white-space: nowrap;
  border: 1px solid var(--sky-dark);
  background: var(--surface-raised); color: var(--ink-light);
}
.iq-preset:hover { border-color: var(--blue-lighter); }
.iq-note {
  font: 400 11px/16px var(--font-sans);
  padding: 4px 6px; min-width: 0;
  border: 1px solid var(--sky-dark); border-radius: 4px;
  background: var(--surface-raised); color: var(--ink);
}
.iq-menu-history {
  font: 400 10px/15px var(--font-sans); color: var(--ink-lightest); text-wrap: pretty;
}
/* A refusal the server sent. Red here and nowhere else in this block, because it
   is the one thing on the row that is actually wrong. */
.iq-menu-err {
  font: 400 11px/16px var(--font-sans);
  color: var(--critical-darker);
  background: var(--critical-lightest);
  padding: 4px 6px; border-radius: 4px;
}

/* The tab Abgleich (design/abgleich-view.jsx): the queue's frame with eight
   columns of its own -- contract/tenant, field, data room, list, gap, kind,
   status, the button. Below 720px of pane the kind goes and the columns narrow
   so that the button stays in view in the work pane of the workspace (about
   500px); above, the rows keep 760px and the list scrolls sideways. */
.ab-grid {
  display: grid; gap: 8px; align-items: baseline;
  grid-template-columns: minmax(130px, 2fr) minmax(80px, 1.2fr) 92px 92px 92px minmax(70px, 1fr) 84px 30px;
}
.ab-grid > * { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ab-scroll { overflow-x: auto; }
.ab-scroll > * { min-width: 760px; }
@container (max-width: 720px) {
  .ab-grid {
    gap: 6px;
    grid-template-columns: minmax(80px, 2fr) minmax(56px, 1.2fr) 76px 76px 76px 62px 28px;
  }
  .ab-grid > :nth-child(6) { display: none !important; }
  .ab-scroll > * { min-width: 0; }
}
.ab-num { text-align: right; font-variant-numeric: tabular-nums; }
.ab-own { font-weight: 600; }
.ab-cell { font: 400 12px/18px var(--font-sans); color: var(--ink); }
.ab-muted { color: var(--ink-lighter); }
.ab-sub { display: block; font: 400 11px/16px var(--font-sans); color: var(--ink-lighter); }
.ab-vanished { font-style: italic; }
.ab-key { opacity: .6; margin-right: 2px; }
.ab-chip { font-size: 11px; line-height: 16px; padding: 1px 7px; min-height: 0; height: auto; }
.ab-done { margin-left: 8px; font-weight: 600; }
.ab-note { width: 100%; box-sizing: border-box; resize: vertical; }
.ab-sum {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font: 400 11px/16px var(--font-sans); color: var(--ink-lighter);
}
.ab-headbox { background: var(--sky-lighter); border-bottom: 1px solid var(--sky); }
.ab-head { flex-wrap: wrap; border-bottom: 0; }
.ab-head-icon { font-size: 16px; color: var(--ink-lighter); }
.ab-head-text {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font: 400 12px/18px var(--font-sans); color: var(--ink);
}
.ab-days { padding: 0 10px 6px; font: 400 11px/16px var(--font-sans); color: var(--ink-light); }
.ab-day-off { display: block; font-weight: 500; color: var(--blue-darker); }
.ab-sheet-top { display: flex; align-items: center; gap: 6px; }
.ab-in-table .material-icons { font-size: 15px; }
.ab-history { padding: 0 10px 6px; font: 400 11px/16px var(--font-sans); color: var(--ink-lighter); }
.ab-history summary { cursor: pointer; }
.ab-upload { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.ab-day { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; width: 100%; }
.ab-day label { display: flex; align-items: center; gap: 6px; }
.ab-file { font: 500 12px/18px var(--font-sans); color: var(--ink); }
.ab-day-hint { width: 100%; }
.ab-cut { padding: 12px; overflow: auto; height: 100%; box-sizing: border-box; }
.ab-cut-head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-bottom: 4px; }
.ab-cut-head strong { font: 500 14px/20px var(--font-sans); color: var(--ink); }
.ab-cut-head .ab-sub { display: inline; }
.ab-cut-scroll { overflow-x: auto; margin-top: 8px; }
.ab-cut-table { border-collapse: collapse; }
.ab-cut-table th, .ab-cut-table td {
  padding: 4px 5px; border-bottom: 1px solid var(--sky-light); text-align: left;
  vertical-align: top; font: 400 11px/16px var(--font-sans); white-space: nowrap;
}
.ab-cut-text { max-width: 96px; overflow: hidden; text-overflow: ellipsis; }
.ab-cut-table th { font: 700 10px/14px var(--font-sans); text-transform: uppercase; color: var(--ink-lightest); }
.ab-cut-table .ab-num { text-align: right; }
.ab-cut-ref { background: var(--sky-lighter); }
.ab-cut-part { cursor: pointer; }
.ab-cut-part:hover { background: var(--sky-lighter); }
.ab-cut-sum td { border-top: 2px solid var(--sky-dark); }
.ab-cut-bad { color: var(--red, crimson); font-weight: 600; }
.ab-cut-checks-head { position: static; padding: 4px 0; margin-top: 14px; }
.ab-cut-check { display: flex; gap: 8px; font: 400 12px/20px var(--font-sans); color: var(--ink); }
.ab-cut-mark { width: 16px; color: var(--ink-lightest); }
.ab-cut-mark.is-ok { color: var(--green, green); }
.ab-cut-mark.is-bad { color: var(--red, crimson); }
.ab-cut-check-label { flex: 1; }
.ab-rule { padding: 12px; overflow: auto; height: 100%; box-sizing: border-box; }
.ab-rule-title { display: block; font: 500 14px/20px var(--font-sans); color: var(--ink); }
.ab-rule-part { position: static; padding: 4px 0; margin-top: 10px; }
.ab-rule-case { display: flex; gap: 8px; padding: 3px 0; cursor: pointer; font: 400 12px/18px var(--font-sans); color: var(--ink); }
.ab-rule-case:hover { background: var(--sky-lighter); }
.ab-rule-who { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ab-start { padding: 32px 24px; text-align: center; }
.ab-start h3 { margin: 8px 0 4px; font: 500 16px/22px var(--font-sans); color: var(--ink); }
.ab-start-icon { font-size: 40px; color: var(--ink-lightest); }
.ab-start-actions {
  display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; align-items: center;
  margin-top: 16px;
}

/* Housekeeping, and muted so it reads as such: stored decisions whose finding no
   longer exists. It is above the list because it explains a number the analyst
   may otherwise not be able to account for, and quiet because it is not work. */
.iq-orphans {
  display: flex; align-items: center; gap: 6px;
  padding: 5px 10px;
  background: var(--surface-raised);
  border-bottom: 1px solid var(--sky);
  font: 400 11px/16px var(--font-sans);
  color: var(--ink-lightest);
}
.iq-orphans .material-icons { font-size: 15px; }

/* ── The lease page's two lists ────────────────────────────────
   Buttons, not links: they switch what is below them rather than navigating, and
   there is no URL for either. Absent entirely without cap.insight_queue — one
   tab is not a choice.

   Between the split band and the list on purpose. The band divides the header
   from the content, and these belong to the content they switch.

   `.tab` draws the buttons themselves. The height is here because the line under
   an active tab needs an edge to sit on, and on the full-width page there is no
   head around this band to borrow one from. */
.lease-tabs {
  display: flex; align-items: stretch; gap: 2px;
  flex: 0 0 auto;
  height: 34px;
  margin-bottom: 8px;
}

/* The field "Stichtag" in the head of the lease page (design/v2-leases.jsx). */
.lease-day {
  display: inline-flex; align-items: center; gap: 6px;
  font: 500 11px/16px var(--font-sans); color: var(--ink-light);
}
/* The paint of `.search-input` (architrave-viewer.css): raised ground, sky
   edge, blue focus with a ring. */
.lease-day input {
  height: 28px; padding: 0 8px;
  font: 400 12px/18px var(--font-sans); color: var(--ink);
  background: var(--surface-raised);
  border: 1px solid var(--sky); border-radius: 4px;
  outline: none;
}
.lease-day input:hover { border-color: var(--sky-dark); }
.lease-day input:focus { border-color: var(--blue); box-shadow: 0 0 0 3px rgba(0,145,255,0.10); }

/* ── The band of the comparison mode (design/compare-band.jsx) ─
   A row of its own between the top bar and the workspace, on every page. */
.app.has-compare { grid-template-rows: 64px auto 1fr; }
.app.has-compare .workspace { grid-row: 3; }
.cmp-band {
  grid-column: 2; grid-row: 2;
  display: flex; align-items: center; flex-wrap: wrap; gap: 12px;
  padding: 6px 16px;
  background: var(--blue-lightest); border-bottom: 1px solid var(--sky);
  font: 400 12px/18px var(--font-sans); color: var(--ink);
  position: relative; z-index: 20;
}
.cmp-band > .material-icons { font-size: 18px; color: var(--blue-darker); }
.cmp-band-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmp-band-end { margin-left: auto; display: inline-flex; align-items: center; gap: 4px; }
.cmp-band-end .material-icons { font-size: 16px; }

/* The same on the lease page, as a pill beside the field "Stichtag": one row
   instead of a band, so the head keeps its height. Only the file name gives up
   its width; the as-of date and "Beenden" are never cut. */
.cmp-pill {
  display: inline-flex; align-items: center; gap: 6px;
  flex: 0 1 auto; min-width: 0; max-width: 560px;
  height: 28px; padding: 0 3px 0 10px;
  border: 1px solid var(--blue-lighter); border-radius: 14px;
  background: var(--blue-lightest); color: var(--blue-darker);
  font: 400 12px/18px var(--font-sans);
}
.cmp-pill > .material-icons { font-size: 16px; flex: 0 0 auto; }
/* A flex row, so the text around the name stays whole and only the name
   (the <strong>) shrinks; `pre` keeps the spaces at the edges of the parts. */
.cmp-pill-text { display: flex; min-width: 0; white-space: pre; }
.cmp-pill-text strong { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.cmp-pill-end {
  flex: 0 0 auto; margin-left: 4px;
  display: inline-flex; align-items: center; gap: 2px;
  height: 22px; padding: 0 8px 0 6px;
  border: 0; border-radius: 11px; cursor: pointer;
  background: transparent; color: var(--blue-darker);
  font: 500 12px/18px var(--font-sans);
}
.cmp-pill-end:hover { background: var(--surface-raised); }
.cmp-pill-end .material-icons { font-size: 14px; }

/* ── The three-column workspace (design/workspace.jsx) ─────
   Behind cap.workspace3. Every number here comes from the accepted design,
   Variante-J-Themenuebersicht.dc.html; the ones a component has to compute with
   live in design/pane-layout.js instead and are named there.

   `.ws3` sits on the same element as `.page.page-table`, so the page header
   above the grid keeps the gutters, the scrolling and the height cap every table
   page has. The grid is the flex child that takes what is left. */
.ws3-grid {
  flex: 1 1 0; min-height: 0;
  display: grid;
  padding: 10px;
  /* Edge to edge: the panes are cards on the page's own ground, so the gutters
     that hold the header's text belong to the header and not to them. Exactly
     .page's padding — one pixel more and .page-table's `overflow: hidden` clips
     the grid, because that clips to the padding box. */
  margin: 0 -32px -24px;
  background: var(--sky-lighter);
}

/* Each pane is its own card on that ground — visibly a layer somebody can
   close, rather than a column built into the page. */
.ws3-pane {
  display: flex; flex-direction: column;
  min-width: 0; min-height: 0;
  overflow: hidden;
  background: var(--surface-raised);
  border: 1px solid var(--sky);
  border-radius: 8px;
  box-shadow: var(--depth-01);
}

/* One head for every pane: the same two buttons in the same place. What differs
   is only what stands on the left — an icon and a name, or the page's tabs. */
.ws3-head {
  display: flex; align-items: center; flex-wrap: nowrap; gap: 8px;
  flex: 0 0 auto;
  height: 34px;
  padding: 0 6px 0 10px;
  min-width: 0; overflow: hidden;
  background: var(--sky-light);
  border-bottom: 1px solid var(--sky-dark);
  border-radius: 7px 7px 0 0;
}
.ws3-head-icon { font-size: 17px; color: var(--ink-lighter); flex: 0 0 auto; }
/* `min-width` is the floor the 148px in design/pane-layout.js protects: below it
   the name of the pane is the first thing the head crushes, and the name is what
   says which pane you are looking at. */
.ws3-head-title {
  font: 500 13px/18px var(--font-sans); color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  flex: 0 1 auto; min-width: 48px;
}
.ws3-head-gap { flex: 1 1 auto; }
/* The tabs are the head here, so they carry neither the margin that separated
   them from the list below nor a second line of their own. */
.ws3-head .lease-tabs { height: 100%; margin: 0; min-width: 0; overflow: hidden; }

/* The same box .rc-bottom is, for the same reason: the panel and the source view
   size themselves against a flex column that does not scroll. */
.ws3-body {
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column;
  overflow: hidden;
}
/* The work pane holds a page's own content and needs the breathing room a page
   would have given it; the dossier and the evidence bring their own padding. */
.ws3-pane[data-ws3-pane="work"] .ws3-body { padding: 8px 10px 10px; }

/* Between two cards lies ground, not a line — the grip appears on hover. */
.ws3-split {
  cursor: col-resize;
  position: relative;
  touch-action: none;
  user-select: none;
}
.ws3-split::after {
  content: "";
  position: absolute; top: 50%; left: 1px; right: 1px; height: 44px;
  transform: translateY(-50%);
  background: repeating-linear-gradient(to bottom, var(--sky-dark) 0 2px, transparent 2px 5px);
}
.ws3-split:hover::after {
  background: repeating-linear-gradient(to bottom, var(--blue) 0 2px, transparent 2px 5px);
}
/* While a pane is maximised there is no boundary to move, and the handle says so
   rather than swallowing the drag silently. */
.ws3-split.is-locked { cursor: default; }
.ws3-split.is-locked::after { opacity: .35; }

/* A closed pane: 36px wide, its label upright, and the whole strip is the click
   that brings it back. */
.ws3-strip {
  flex: 1;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  padding: 10px 0;
  cursor: pointer;
}
.ws3-strip .material-icons { font-size: 18px; color: var(--ink-lighter); }
.ws3-strip-label {
  writing-mode: vertical-rl;
  font: 500 12px/16px var(--font-sans);
  color: var(--ink-light);
  letter-spacing: .3px;
}
.ws3-pane.is-strip:hover { border-color: var(--blue-lighter); }

/* ── The evidence head (design/evidence-head.jsx) ──────────
   Behind cap.workspace3, because it exists only in the workspace's third pane.
   Every number here is the accepted design's own,
   Variante-J-Themenuebersicht.dc.html lines 1195 to 1258. */

.ev {
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column;
  overflow: hidden;
}

/* The two bars above the value head are `.mod-bar` (line 59), the way the
   dossier's head bar is: the element carries `mod-bar ev-bar`, and `.ev-bar`
   stays only as the name the tests count the bars by. It declares nothing of
   its own. */
.ev-doc-title {
  flex: 1 1 auto; min-width: 0;
  font: 500 14px/20px var(--font-sans); color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Tabular figures, so "2 / 4" does not shift its neighbours on the step from 9
   to 10 — the two arrow buttons sit either side of it. */
.ev-meta {
  flex: 0 0 auto;
  font: 400 11px/16px var(--font-sans); color: var(--ink-lighter);
  font-variant-numeric: tabular-nums;
  min-width: 42px; text-align: center;
}
.ev-bar .icon-btn { flex: 0 0 auto; }

/* Its own class rather than the shared `.search-input`: the page above already
   has one of those, and two inputs answering to one selector make every test
   that fills "the search field" ambiguous. The PAINT is `.search-input`'s
   (architrave-viewer.css:650) — raised ground, 18px glyph, blue focus with a
   ring — and only the box is smaller, because this one lives in a 32px bar
   instead of a page header. Two search fields that look different read as two
   different gestures. */
.ev-search {
  display: inline-flex; align-items: center; gap: 8px;
  flex: 1 1 auto; min-width: 80px;
  height: 26px;
  padding: 0 8px;
  background: var(--surface-raised);
  border: 1px solid var(--sky);
  border-radius: 4px;
}
.ev-search input {
  flex: 1; border: 0; outline: none; min-width: 0;
  font: 400 12px/18px var(--font-sans);
  color: var(--ink);
  background: transparent;
}
.ev-search .material-icons { font-size: 18px; color: var(--ink-lighter); }
.ev-search:focus-within {
  border-color: var(--blue);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--blue) 10%, transparent);
}

/* The clear cross of a search field. `.text-btn` had no rule anywhere, so the
   one in the stock list (`v2-leases.jsx`) rendered as a raw button with a
   border and a grey ground. One rule for all three fields, so a cross is the
   same gesture wherever it stands. */
.search-input .text-btn,
.ev-search .text-btn,
.iq-search .text-btn {
  flex: 0 0 auto;
  border: 0; background: transparent; padding: 0;
  display: inline-flex; align-items: center;
  color: var(--ink-lighter); cursor: pointer;
}
.search-input .text-btn:hover,
.ev-search .text-btn:hover,
.iq-search .text-btn:hover { color: var(--ink); }

/* The value head. Raised off the document below it, because it is a statement
   about the field and not part of the paper.

   THREE LINES AT MOST — label with badge, value, source — and the last drops
   out when it is empty. No actions: those are the dossier's. */
.ev-head {
  flex: 0 0 auto;
  padding: 8px 12px;
  border-bottom: 1px solid var(--sky);
  background: var(--sky-lighter);
}
.ev-head-top { display: flex; align-items: center; gap: 8px; min-width: 0; }
.ev-head-gap { flex: 1 1 auto; }
.ev-label {
  font: 700 10px/14px var(--font-sans);
  letter-spacing: 1.5px; text-transform: uppercase;
  color: var(--ink-lighter);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ev-head-top .badge { flex: 0 0 auto; }
.ev-value-line {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px;
  margin: 1px 0 2px;
}
/* 22px, the size the design gives the focused value (line 907 and line 1217).
   The head is the one place on the screen that answers "which figure are we
   deciding about", and at 18px it reads as a label. The stacked arrangement
   stays (see `.ev-head` above): the head grows 6px,
   and `test_the_evidence_head_puts_three_bars_over_the_document` holds the
   document to 45 % of the pane either way. */
.ev-value {
  font: 500 22px/30px var(--font-sans); color: var(--ink);
  overflow-wrap: anywhere;
}
/* The value the document actually carries, struck through beside the corrected
   one. It stays legible: it is the figure the analyst has to be able to check
   the correction against. */
.ev-raw {
  font: 400 13px/20px var(--font-sans); color: var(--ink-lighter);
  text-decoration: line-through;
}
.ev-source { font: 400 11px/16px var(--font-sans); color: var(--ink-lighter); }

/* ── The note form (design/note-form.jsx) ──────────────────────
   In the DOSSIER, not in the head above — the head has four lines of the pane's
   349px and five more fields would push the document out of it. Dashed and
   untinted like the note's own dot and badge: it records an open place rather
   than a finding, and a blue-bordered box would read as one more write action
   beside the two above it. Design lines 995 to 1032. */
.nf {
  margin: 6px 16px 12px;
  padding: 10px 12px;
  border: 1px dashed var(--ink-lighter);
  border-radius: 4px;
  background: var(--sky-lighter);
  display: flex; flex-direction: column; gap: 8px;
}
.nf-title { font: 500 12px/16px var(--font-sans); color: var(--ink); }
.nf-grid {
  display: grid;
  grid-template-columns: 96px minmax(0, 1fr);
  gap: 6px 8px;
  align-items: center;
}
.nf-grid label { font: 400 11px/16px var(--font-sans); color: var(--ink-light); }
.nf-grid input, .nf-select {
  font: 400 12px/18px var(--font-sans);
  padding: 4px 6px;
  border: 1px solid var(--sky-dark);
  border-radius: 4px;
  background: var(--surface-raised);
  color: var(--ink);
  min-width: 0;
}
.nf-grid input:focus, .nf-select:focus { outline: none; border-color: var(--blue); }
/* The value and its unit are one field: the unit is a label, not an input, and
   putting it in the grid's own column would move every label out of line. */
.nf-value { display: flex; align-items: center; gap: 6px; min-width: 0; }
.nf-value .nf-input { flex: 1 1 auto; font-variant-numeric: tabular-nums; }
.nf-input.is-error { border-color: var(--critical); }
.nf-unit {
  font: 500 12px/18px var(--font-sans);
  color: var(--ink-light);
  white-space: nowrap;
}
/* Indented to the field column, so the sentence sits under what it is about. */
.nf-hint {
  font: 400 11px/16px var(--font-sans);
  color: var(--ink-lightest);
  padding-left: 104px;
}
.nf-hint.is-error { color: var(--critical-darker); padding-left: 104px; }
.nf-foot-note { font: 400 11px/16px var(--font-sans); color: var(--ink-lighter); }
.nf-actions { display: flex; flex-wrap: wrap; gap: 6px; }

/* The source view keeps the rest, and scrolls inside it. */
.ev-body {
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column;
  overflow: hidden;
}

/* Every hit of the document search is marked; the one the bar is counting to
   carries the accent. Without the second tone "3 von 12" names no particular
   place on the screen. */
.doc-body mark.is-hit {
  background: var(--blue);
  color: var(--text-on-accent);
}

/* THE QUOTE AS A BLOCK, the way the design draws it (line 1263): a stripe down
   the left and a tinted ground, so the analyst finds the cited place on the page
   before reading a word of it. A single marked run in a wall of OCR text does
   not carry that far.

   The type stays mono 13px from `.doc-body`. The design sets the quote in sans
   17px (line 1264), which it can afford because its document is four grey bars
   and one sentence; here the block sits inside the real page, and a second face
   in the middle of it would break the text flow the analyst is scanning.

   The `mark` inside goes blue too. `--warning` is the colour of the search
   hit's neighbours, and a quote in the same yellow inside a blue block would
   claim to be a search result. The two never meet: design/chrome.jsx draws the
   quote OR the hits, never both, so this rule and `mark.is-hit` never apply to
   one element. */
.doc-quote {
  display: block;
  padding: 14px 18px;
  border-left: 3px solid var(--blue);
  background: var(--blue-lightest);
}
.doc-body .doc-quote mark {
  background: color-mix(in srgb, var(--blue) 22%, transparent);
}

/* THE MARKDOWN PREVIEW (design/doc-markdown.js). The text switch over the body
   is a `.mod-bar` of two `.tab`s and needs nothing of its own.

   The body keeps its padding and its scroll, and drops what makes it raw text:
   `pre-wrap` would turn every newline marked left between two tags into a blank
   line, and the mono face would set a table in a typewriter. Its pieces stay
   plain — a hairline per cell, headings one step apart — because the page is a
   third party's document, not a page of this app. */
.doc-body.is-md {
  white-space: normal;
  font: 400 14px/22px var(--font-sans);
}
.doc-body.is-md h1, .doc-body.is-md h2, .doc-body.is-md h3, .doc-body.is-md h4 {
  margin: 16px 0 8px; color: var(--ink);
}
.doc-body.is-md h1 { font: 600 18px/26px var(--font-sans); }
.doc-body.is-md h2 { font: 600 16px/24px var(--font-sans); }
.doc-body.is-md h3, .doc-body.is-md h4 { font: 600 14px/22px var(--font-sans); }
.doc-body.is-md p { margin: 0 0 10px; }
.doc-body.is-md ul, .doc-body.is-md ol { margin: 0 0 10px; padding-left: 22px; }
.doc-body.is-md table { border-collapse: collapse; margin: 0 0 12px; }
.doc-body.is-md th, .doc-body.is-md td {
  border: 1px solid var(--sky-dark);
  padding: 4px 8px;
  text-align: left; vertical-align: top;
}
.doc-body.is-md th { background: var(--sky-light); color: var(--ink); font-weight: 600; }
.doc-body.is-md pre { white-space: pre-wrap; font: 400 13px/20px var(--font-mono); }
/* The stripe lands on the block that holds the mark: a cell stays a cell and an
   item an item, where `display: block` from `.doc-quote` would pull a cell out
   of its row. */
.doc-body.is-md .doc-quote { display: revert; }

/* ── The topic band (design/topic-overview.jsx) ────────────
   Behind cap.topic_overview. Every number here comes from the accepted design,
   Variante-J-Themenuebersicht.dc.html lines 331 to 411; the ones a component has
   to compute with live in design/topic-overview-model.js and are named there.

   A flex child of `.page.page-table`, above `.page-table-head` and therefore
   outside the block that scrolls: the band is the page's own header now, and a
   header that can scroll out of reach is a control nobody can get back to. Both
   layouts render it from the same `head` slot, so it looks and behaves alike
   with and without cap.workspace3. */
.ov {
  flex: 0 0 auto;
  /* Edge to edge AND up to the top bar, cancelling .page's gutters exactly —
     one pixel more and .page-table's `overflow: hidden` clips it. The top gutter
     goes too: the band is this page's header, and 24px of page ground between
     it and the top bar only pushed the work down. Flush, the top bar's own
     bottom hairline becomes the line between the asset navigation above and the
     topic below — one line, not two, which is why the band adds no border here.
     Only this page cancels the gutter; every other table page keeps its
     PageHead and needs the space. */
  margin: -24px -32px 0;
  background: var(--surface-raised);
  border-bottom: 1px solid var(--sky);
}

.ov-head {
  display: flex; align-items: center; gap: 14px;
  min-height: 48px; padding: 4px 10px 4px 16px;
  min-width: 0; flex-wrap: wrap;
}
.ov-head-gap { flex: 1 1 auto; }

/* The page title and the switch are one surface. Collapsed it carries a ground,
   so the row still reads as a control rather than as a heading somebody clicked
   by accident. */
.ov-title {
  display: flex; align-items: center; gap: 6px;
  border: 0; border-radius: 4px; padding: 3px 8px;
  background: transparent; cursor: pointer; min-width: 0;
}
.ov:not(.is-open) .ov-title { background: var(--sky-lighter); }
.ov-title:hover { background: var(--sky-light); }
.ov-title-text {
  font: 400 20px/26px var(--font-sans); color: var(--ink); white-space: nowrap;
}
.ov-caret { font-size: 22px; color: var(--ink-light); }

/* The subline, beside the title rather than under it: the band is one row, and
   a page that hands in none loses nothing. It gives up its width first — the
   figures and the controls beside it are what the row is for. */
.ov-subtitle {
  font: 400 12px/16px var(--font-sans); color: var(--ink-lighter);
  min-width: 0; flex: 0 1 auto;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ── Collapsed: four chips in place of five cards ───────── */
.ov-summary { display: flex; align-items: center; flex-wrap: wrap; gap: 5px; min-width: 0; }
.ov-sum {
  display: inline-flex; align-items: baseline; gap: 4px;
  font: 400 11px/16px var(--font-sans); color: var(--ink-light);
  padding: 2px 8px; border-radius: 12px; white-space: nowrap;
  border: 1px solid var(--sky-dark); background: var(--surface-raised);
  cursor: pointer;
}
.ov-sum.is-dead { cursor: default; opacity: .6; }
.ov-sum.is-on {
  border-color: var(--blue); background: var(--blue-lightest); color: var(--blue-darker);
}
.ov-sum-num {
  font: 500 12px/16px var(--font-sans); color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.ov-sum.is-on .ov-sum-num { color: var(--blue-darker); }

/* ── Expanded: the five cards ───────────────────────────── */
/* The second cap is not in the design and is the price of a 900px-tall window:
   the band is a HEADER, and a header that takes 495 of 836 pixels leaves the
   findings strip and the gap notes under it with nothing: at 1440×900 without
   the cap `.page-table-head` comes out 0px tall. The panel scrolls instead.

   30vh rather than 40: when the cards wrap onto a second row, 40vh makes the
   band 415px of the 836px page and leaves `.et-wrap` 213px against its own
   244px floor — the table comes out clipped and the drag on `.page-hsplit` has
   nothing left to give it. At 30vh the band is 318px and the table keeps
   310px. The second row arrives by scrolling at a narrower window. */
.ov-panel {
  background: var(--sky-lighter);
  border-top: 1px solid var(--sky);
  padding: 10px 24px 12px;
  max-height: min(440px, 30vh); overflow-y: auto;
}
/* A CARD IS THE SAME CARD AT EVERY WINDOW WIDTH, AND THE BAND IS ONE ROW AT
   1440. That is the rule; the track width is only what it takes to keep it. At
   1440 with the rail open (236px) the panel has 1156px inside its padding.

   184px: six cards of 184 and five gaps of 10 are 1154px. The design's own
   width, 252px, is drawn for 1920px. A card in a second row would arrive under the panel's own scroll
   cap, and a card an analyst has to scroll to find is a card they do not read.

   `auto-fill` keeps the empty tracks empty, and `justify-content: start` puts
   them all on the right. `auto-fit` with `1fr` would do the opposite: at every
   width where five cards fit and the sixth does not, the sixth alone would take
   the whole second row, and the drag on `.ov-resize` would move that break as
   it moved the height. Six shapes that change on a resize are six things to re-read.

   The band scrolls instead. `.ov-panel` above already caps its height, so a
   second row of cards at a narrow window arrives under the scrollbar rather
   than by pushing the table off the screen.

   The row height is the drag's business and arrives as an inline style — fixed,
   not a floor: a row that may grow makes the second row a different height
   from the first, which is the same wobble by another route. A card whose
   content is taller than the row scrolls inside itself, so nothing is lost and
   the drag keeps its whole range. Several need it at the default 180px, which
   is why this is not `hidden`. */
.ov-grid {
  display: grid; gap: 10px; align-items: stretch;
  grid-template-columns: repeat(auto-fill, 184px);
  justify-content: start;
}
.ov-card {
  display: flex; flex-direction: column; min-width: 0; overflow-y: auto;
  background: var(--surface-raised);
  border: 1px solid var(--sky);
  border-radius: 4px;
  padding: 9px 11px 10px;
}
.ov-card.is-skeleton { background: var(--sky-light); border-style: dashed; }
.ov-card-head { display: flex; align-items: center; gap: 6px; min-width: 0; }
.ov-card-icon { font-size: 15px; color: var(--ink-lightest); }
.ov-card-title {
  font: 700 10px/14px var(--font-sans); letter-spacing: 1.25px;
  text-transform: uppercase; color: var(--ink-lighter);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ov-hero { display: flex; align-items: baseline; gap: 5px; margin-top: 5px; min-width: 0; }
/* A hero that is a filter: the same figure, drawn as a button.
   The reset is what a <button> needs to keep looking like the <div> beside it;
   the hover and the `is-on` outline are the ones .ov-row already uses, so a
   card's headline answers to a click the way its lines do. */
button.ov-hero {
  width: 100%; text-align: left; font: inherit; color: inherit;
  border: 0; border-radius: 3px; background: transparent;
  padding: 0 5px; margin-left: -5px; cursor: pointer;
}
button.ov-hero.is-dead { cursor: default; }
button.ov-hero:hover { background: var(--sky-lighter); }
button.ov-hero.is-on { background: var(--blue-lightest); }
button.ov-hero.is-on .ov-hero-num { color: var(--blue-darker); }
.ov-hero-num {
  font: 300 26px/28px var(--font-sans); color: var(--ink);
  font-variant-numeric: tabular-nums;
}
/* A euro figure is four times as long as a count and would break the card at
   220px; the design drops it two steps rather than letting it wrap. */
.ov-hero-num.is-small { font-size: 22px; line-height: 26px; }
.ov-hero-unit {
  font: 400 11px/15px var(--font-sans); color: var(--ink-light);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Pushed to the bottom so the feet line up however tall their cards grew. */
.ov-foot {
  margin-top: auto; padding-top: 7px;
  font: 400 10px/14px var(--font-sans); color: var(--ink-lightest);
  text-wrap: pretty;
}

/* A bar: what it splits, how much of it, and one segment per part. */
.ov-bar { margin-top: 9px; }
.ov-bar-head {
  display: flex; align-items: baseline; gap: 6px; margin-bottom: 4px; min-width: 0;
}
.ov-bar-gap { flex: 1 1 auto; }
.ov-bar-label {
  font: 400 10px/14px var(--font-sans); color: var(--ink-lighter);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ov-bar-value {
  font: 500 10px/14px var(--font-sans); color: var(--ink-light); white-space: nowrap;
}
.ov-bar-track { display: flex; gap: 2px; }
.ov-seg {
  flex-basis: 0; min-width: 4px; height: 8px; padding: 0;
  border: 0; border-radius: 2px; cursor: pointer;
}
.ov-seg.is-dead { cursor: default; }
.ov-seg.is-on { outline: 2px solid var(--blue-darker); outline-offset: 1px; }

/* The expiry profile. One column per calendar year that HAS an expiry, scaled
   by the share of the rent running out in it — a year nothing ends in has no
   column, so every column here is a button. */
.ov-cols { display: flex; align-items: flex-end; gap: 3px; margin-top: 10px; }
.ov-col {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column; justify-content: flex-end; align-items: stretch;
  gap: 3px; border: 0; background: transparent; padding: 0; cursor: pointer;
}
.ov-col.is-dead { cursor: default; }
.ov-col-bar { display: block; border-radius: 2px 2px 0 0; background: var(--blue); }
.ov-col.is-on .ov-col-bar { background: var(--blue-darker); }
.ov-col-label {
  font: 400 9px/12px var(--font-sans); color: var(--ink-lightest);
  text-align: center; white-space: nowrap;
}

/* A line under the chart: a dot, a name, a figure. */
.ov-row {
  display: flex; align-items: center; gap: 7px; width: 100%; text-align: left;
  border: 0; border-top: 1px solid var(--sky-lighter); border-radius: 3px;
  background: transparent; padding: 4px 5px; margin-top: 2px; cursor: pointer;
}
.ov-row.is-dead { cursor: default; }
.ov-row:hover { background: var(--sky-lighter); }
.ov-row.is-on { background: var(--blue-lightest); }
.ov-dot { width: 6px; height: 6px; border-radius: 999px; flex: 0 0 auto; }
.ov-row-label {
  flex: 1 1 auto; min-width: 0;
  font: 400 11px/15px var(--font-sans); color: var(--ink-light);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ov-row.is-on .ov-row-label { color: var(--blue-darker); }
.ov-row-value {
  font: 500 11px/15px var(--font-sans); color: var(--ink);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* Loading, empty, failed — three sentences, never five cards of zeros. */
.ov-note {
  display: flex; align-items: center; gap: 8px;
  font: 400 12px/18px var(--font-sans); color: var(--ink-light);
  padding: 6px 2px;
}
.ov-note-error .material-icons { font-size: 18px; color: var(--ink); }

/* The bottom edge sets how tall a card may be. Same grip as .rc-hsplit, so
   one drag affordance reads alike across the app. */
.ov-resize {
  height: 6px; cursor: row-resize; touch-action: none; user-select: none;
  background: var(--sky);
}
.ov-resize:hover { background: var(--blue-lighter); }

/* ── The chip a filtered list carries ───────────────────── */
/* On exactly one surface: the queue's KPI bar for an insight filter, a row of
   its own above the table for a contract filter.

   A `.chip` that is always on -- a filter chip only exists while a filter lies,
   so there is no off state to draw. What it adds is room for the x on its right,
   a ceiling on a long topic name, `center` instead of the baseline the chip uses
   (the x is an icon and would hang below a baseline it shares with text), and the
   default cursor, because the x is the only part of it that clicks. */
.ov-chip {
  align-items: center; font-weight: 500; cursor: default;
  padding-right: 4px; max-width: 260px; min-width: 0;
}
.ov-chip-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ov-chip-count { color: var(--ink-lighter); white-space: nowrap; }
.ov-chip-x {
  display: inline-flex; align-items: center; border: 0; padding: 0;
  background: transparent; color: var(--blue-darker); cursor: pointer;
}
.ov-chip-x .material-icons { font-size: 14px; }

/* The row the chip stands in above the contract table. It exists only while a
   filter does — an empty bar would be a permanent reminder of a control nobody
   used. */
.ov-chip-bar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  flex: 0 0 auto; padding: 7px 10px;
  border-bottom: 1px solid var(--sky);
  background: var(--surface-raised);
}
.ov-chip-bar .ov-chip-count { font-weight: 400; }

/* ──────────────────────────────────────────────────────────
   Quellenregister
   ──────────────────────────────────────────────────────────
   Four blocks, one per pane: the band, the list, the dossier and the stand
   table (planning/2026-09-15-bauplan-quellenregister.md). */

/* page.sources.band */
/* The band is design/topic-overview.jsx's, and only its cards are this page's.
   This wrapper draws NO box (`display: contents`), so `.ov` stays the flex child
   of `.page.page-table` that its negative margins were written for; it exists
   purely to give these rules something to hang off that the lease band does not
   carry. design/topic-overview.jsx writes `.ov` and `.ov-grid` unconditionally,
   which is why the class goes on the outside rather than onto `.ov-grid`. */
.ov-band--sources { display: contents; }

/* Four cards in a row at the desktop width the design draws for, and a
   card that stays readable when the panel narrows. `auto-fit` with `1fr` rather
   than the lease band's fixed 220px track, because there are four cards and not
   five: four can share the width evenly at every window, so the wobble that
   made the lease grid fixed (a fifth card alone on a second row) cannot arise.

   `grid-auto-rows` stays the drag's business and arrives as an inline style;
   design/sources-band.jsx starts it at 120px rather than the lease band's 180,
   because these cards carry a label, a figure and a foot and nothing else. */
.ov-band--sources .ov-grid {
  grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
}
.ov-band--sources .ov-card {
  border-radius: 8px;
  padding: 10px 12px;
}

/* A card that filters is a button, so it needs the reset a `<div>` did not.
   `.is-dead` is a card whose set is empty: still a card, no longer a control. */
.srcb-card {
  font: inherit; text-align: left; color: inherit;
  cursor: pointer; appearance: none;
}
.srcb-card:hover { border-color: var(--sky-dark); }
.srcb-card.is-on { border-color: var(--blue); }
.srcb-card.is-on .ov-card-title { color: var(--blue-darker); }
.srcb-card.is-dead { cursor: default; }
.srcb-card.is-dead:hover { border-color: var(--sky); }

/* The caret TURNS, it is not swapped. design/topic-overview.jsx renders
   `expand_less` open and `expand_more` collapsed, and the two are the same glyph
   mirrored — so rotating the open one by 180° lands on the closed one's shape,
   and the collapsed one by -90° on the design's left-pointing caret. The result
   is the prototype's single turning caret, out of CSS alone. */
.ov-band--sources .ov-caret {
  transition: transform 150ms ease-out;
}
.ov-band--sources .ov.is-open .ov-caret { transform: rotate(180deg); }
.ov-band--sources .ov:not(.is-open) .ov-caret { transform: rotate(-90deg); }
/* page.sources.list */
/* The toolbar's own two controls. `.select` is 36px and `.filter-chip` 32px,
   both sized for a page head; the table's toolbar is a 32px strip and takes them
   down the way `.et-toolbar .search-input` already takes the search field down.
   Their own classes rather than `.et-toolbar .select`, because that rule would
   shrink the maintenance register's toolbar too — another page. */
.srcl-select {
  height: 26px; width: 110px;
  padding: 0 22px 0 8px;
  background-position: right 7px center;
  font: 400 12px/18px var(--font-sans);
}

/* SEVEN CONTROLS IN 507 PIXELS. The work pane is five elevenths of the
   workspace — measured 507px at 1440 with the rail open — and the search, two
   selects, two chips and the table's own two menus do not fit that at their
   natural widths. `.et-toolbar > *` pins every child at `0 0 auto`, so the row
   would overflow: the search would collapse onto its 60px basis, its clear
   button fall out of the field, and the category select land on top of it.

   So the two selects give way first, down to a width that still shows
   "Alle Kategorien" clipped rather than gone, and the search keeps a floor wide
   enough for its own icon, field and clear button. Both rules are scoped to this
   page's classes: the maintenance register's toolbar shares `.select` and
   `.filter-chip` and is not this page's to resize. */
.et-toolbar .srcl-select { flex: 0 1 auto; min-width: 72px; }
.et-toolbar .srcl-search { flex: 1 1 96px; min-width: 96px; }
.srcl-chip {
  height: 26px; padding: 0 10px;
  font: 400 12px/16px var(--font-sans);
}

/* Icon and word in one cell, and the icon carries the meaning: a lock is read
   before it is read (Funktionsinventar 24). Blue for the customer source, ink
   for Architrave's — the same pair the dossier badge uses. */
.srcl-origin { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
.srcl-origin .material-icons { font-size: 14px; color: var(--ink-lighter); }
.srcl-origin.is-own .material-icons { color: var(--blue); }

/* The status dot asserts nothing a click could change, so it loses `.vdot`'s
   pointer and its hover growth. Its right margin goes too: the gap is the
   flexbox's now, and a margin on top of it would put the word off centre. */
.srcl-status { display: inline-flex; align-items: center; gap: 7px; min-width: 0; }
.srcl-status .vdot { margin-right: 0; cursor: default; }
.srcl-status .vdot:hover { transform: none; }

/* "Your filter matched nothing" and "this project has no sources" must not look
   alike (Screenshot 13). The dashed box is the first; the table's bare `.et-empty`
   line stays what a page with no data at all would show. */
.srcl-empty {
  display: inline-block;
  padding: 10px 14px;
  border: 1px dashed var(--sky-dark);
  border-radius: 4px;
  background: var(--sky-lighter);
  font: 400 12px/18px var(--font-sans);
  color: var(--ink-light);
}
/* page.sources.dossier */

/* The blocks of a source, inside AttrSourcePanel's hull. The hull's own rules
   (.ap, .ap-head, .ap-head-title, .ap-body) carry the scrolling and the head;
   everything below draws what a source is, which is not what an attribute is.

   Both themes: every colour is a token, and the two that are not tokens in the
   prototype (the badge grounds) are the same tokens the version badges use, so
   a dark ground never gets a fixed alpha over it. */

.sd-head { padding: 12px 14px; border-bottom: 1px solid var(--sky); }
.sd-name {
  font: 500 15px/21px var(--font-sans); color: var(--ink); text-wrap: pretty;
}
.sd-sub {
  margin-top: 2px;
  font: 400 12px/17px var(--font-sans); color: var(--ink-light); text-wrap: pretty;
}
/* The badge explains the PROVENANCE, not a write path: in the MVP nothing is
   editable either way. */
.sd-badge {
  display: inline-flex; align-items: center; gap: 6px;
  margin-top: 8px; padding: 2px 9px 2px 7px; border-radius: 10px;
  font: 500 11px/18px var(--font-sans);
}
.sd-badge .material-icons { font-size: 15px; }
.sd-badge.is-architrave { background: var(--sky-light); color: var(--ink-light); }
.sd-badge.is-customer { background: var(--blue-lightest); color: var(--blue-darker); }

/* Eleven fields, as many per row as 140px allows. At the pane's usual width
   that is two; maximised it is four, and neither needs a breakpoint. */
.sd-grid {
  padding: 12px 14px;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 12px 16px;
  border-bottom: 1px solid var(--sky);
}
.sd-field { min-width: 0; }
.sd-field-label { display: flex; align-items: center; gap: 4px; }
.sd-field-label span {
  font: 700 10px/14px var(--font-sans); letter-spacing: 1.25px;
  text-transform: uppercase; color: var(--ink-lighter);
}
.sd-lock { font-size: 12px !important; color: var(--ink-lightest); }
.sd-field-value {
  margin-top: 2px;
  font: 400 12px/17px var(--font-sans); color: var(--ink); text-wrap: pretty;
}

/* One card shape for the source type and the delivery log. */
.sd-card {
  margin: 12px 14px; border: 1px solid var(--sky); border-radius: 6px;
  background: var(--surface-raised);
}
.sd-card-head {
  display: flex; align-items: center; gap: 8px;
  padding: 9px 12px; border-bottom: 1px solid var(--sky);
}
.sd-card-head .material-icons { font-size: 17px; color: var(--ink-light); }
.sd-card-title { font: 500 13px/18px var(--font-sans); color: var(--ink); }
.sd-card-gap { flex: 1; }
.sd-card-meta {
  font: 400 11px/16px var(--font-sans); color: var(--ink-lighter); white-space: nowrap;
}
.sd-card-body { padding: 9px 12px 11px; }
.sd-card-text {
  font: 400 11px/16px var(--font-sans); color: var(--ink-light); text-wrap: pretty;
}
.sd-chips { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.sd-chip {
  padding: 1px 8px; border-radius: 10px;
  background: var(--sky-light); color: var(--ink-light);
  font: 400 11px/17px var(--font-sans);
}

.sd-steps { padding: 9px 12px; display: flex; flex-direction: column; gap: 6px; }
.sd-step { display: flex; align-items: flex-start; gap: 8px; }
.sd-step-icon { font-size: 16px !important; flex: 0 0 auto; margin-top: 1px; }
/* Done is green, open is red — and open covers the step still in tolerance too
   (Punkt 32). A delivery that has not arrived is not half-arrived. */
.sd-step-icon.is-done { color: var(--success); }
.sd-step-icon.is-open { color: var(--critical); }
.sd-step-label {
  flex: 1; min-width: 0;
  font: 400 12px/17px var(--font-sans); color: var(--ink); text-wrap: pretty;
}
.sd-step-when {
  font: 400 11px/17px var(--font-sans); color: var(--ink-lighter); white-space: nowrap;
}
.sd-card-foot {
  padding: 0 12px 11px; display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
}
.sd-card-note {
  flex: 1; min-width: 140px;
  font: 400 11px/16px var(--font-sans); color: var(--ink-lighter); text-wrap: pretty;
}

.sd-versions { padding: 4px 14px 14px; }
.sd-versions-head {
  margin: 8px 0 6px;
  font: 700 10px/14px var(--font-sans); letter-spacing: 1.25px;
  text-transform: uppercase; color: var(--ink-lighter);
}
/* A button, not a div: picking a stand steers the pane beside this one, and a
   row that steers something is reachable from the keyboard. */
.sd-version {
  display: flex; flex-direction: column; width: 100%; text-align: left;
  padding: 8px 10px; margin-bottom: 6px;
  border: 1px solid var(--sky); border-radius: 4px;
  background: var(--surface-raised); cursor: pointer;
}
.sd-version:hover { border-color: var(--blue-lighter); }
.sd-version.is-on { border-color: var(--blue); background: var(--blue-lightest); }
.sd-version-top {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
}
.sd-version-stand {
  font: 500 13px/18px var(--font-sans); color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.sd-version-badge {
  padding: 0 7px; border-radius: 10px;
  font: 500 10px/16px var(--font-sans); letter-spacing: .5px; text-transform: uppercase;
}
.sd-version-badge.is-valid {
  background: var(--success-lightest); color: var(--success-darker);
}
.sd-version-badge.is-old { background: var(--sky-light); color: var(--ink-light); }
.sd-version-change {
  margin-top: 2px;
  font: 400 11px/16px var(--font-sans); color: var(--ink-light); text-wrap: pretty;
}
.sd-version-trail {
  margin-top: 1px;
  font: 400 11px/16px var(--font-sans); color: var(--ink-lighter); text-wrap: pretty;
}
.sd-versions-foot {
  margin-top: 8px;
  font: 400 11px/16px var(--font-sans); color: var(--ink-lightest); text-wrap: pretty;
}
/* page.sources.stand */

/* NO ROW MARKINGS HERE. The design asks for three — a marked row, a changed
   row, a row that failed its check — and nothing would set them: the rows
   arrive as plain arrays, and no draft preview marks a row against the type
   schema. They come with that producer, tokens and all (the git history of this
   file holds a version for both themes). */

/* `.sv-head` is ReferenceSource's head and stays a block; only here does it
   carry a badge and a button beside the title. */
.sv-stand-head { display: flex; align-items: flex-start; gap: 8px; }
.sv-stand-headtext { flex: 1; min-width: 0; }
/* One line, cut off — the pane is narrow beside two others, and a five-line
   heading pushes the table down for a name the list and the dossier both show
   in full. The design handoff cuts it the same way. */
.sv-stand-head .sv-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sv-stand-head .sv-tag { flex: 0 0 auto; }
.sv-stand-head .sv-tag.is-released {
  background: var(--success-lightest);
  color: var(--success-darker);
}
.sv-stand-head .btn-icon { flex: 0 0 auto; }

/* 30px rows: the stand table is read across a line, and the 24px of `.sv-table`
   loses it. `height` on the cell, not padding, so the row is the stated size
   whatever the font does. */
.sv-table--stand td { height: 30px; padding: 0 8px; font-variant-numeric: tabular-nums; }
.sv-table--stand th.is-num, .sv-table--stand td.is-num { text-align: right; }

.sv-stand-foot {
  flex: 0 0 auto;
  padding: 8px 16px;
  border-top: 1px solid var(--sky);
  font: 400 11px/16px var(--font-sans);
  color: var(--ink-lighter);
  text-wrap: pretty;
}
.sv-stand-foot .is-error { color: var(--critical); margin-top: 4px; }

/* ── Verarbeitung: the runs page ───────────────────────────────────────────
   Three blocks under each other in the scrolling `.page`; the tables are the
   shared `.data-table`, the badges the shared `.badge`. Every colour is a token,
   so the dark theme needs nothing of its own. */
.proc-page .data-table { margin-bottom: 24px; }
.proc-sub { display: block; font-size: 11px; line-height: 14px; }
.proc-badges { display: inline-flex; gap: 4px; flex-wrap: wrap; }
.proc-badges + .proc-error { display: block; }
.proc-error, .proc-failed {
  display: block; font: 400 11px/14px var(--font-sans); color: var(--critical);
  max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.proc-check { display: inline-flex; align-items: center; gap: 8px; font: 400 13px/18px var(--font-sans); color: var(--ink); cursor: pointer; }
.proc-check input:disabled + span { color: var(--ink-lighter); }
.proc-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.proc-runs td.actions-cell .btn { margin-left: 4px; }
.proc-log-row td { padding: 0 12px 12px; background: var(--sky-lighter); }
.proc-orphans-row td { padding: 0 12px 12px; background: var(--sky-lighter); }
.proc-orphans-row .proc-orphans { margin: 0; }
.proc-quote { max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink-light); }
.proc-select { max-width: 320px; font: 400 12px/18px var(--font-mono); color: var(--ink); background: var(--surface-raised); border: 1px solid var(--rule); border-radius: 6px; padding: 4px 8px; }
.proc-log {
  margin: 0; padding: 8px 12px; max-height: 320px; overflow: auto;
  font: 400 11px/15px var(--font-mono); color: var(--ink); background: var(--surface-raised);
  border: 1px solid var(--rule); border-radius: 6px; white-space: pre;
}

/* The chain: the classes of the prototype's chain layout,
   renamed from pt- to proc-. The page stops scrolling; the head and the bar
   stay, `.proc-body` under them is the scroll container. Head flat, so the
   stage has the room. */
.page.proc-chained { display: flex; flex-direction: column; overflow: hidden; padding-top: 12px; padding-bottom: 16px; }
.proc-chained .crumb { margin-bottom: 2px; }
.proc-chained .page-head { margin-bottom: 10px; flex: none; }
.proc-chained .page-head > div { display: flex; align-items: baseline; gap: 12px; }
.proc-chained .page-title { font-size: 20px; line-height: 26px; margin: 0; }
.proc-chained .page-subtitle { font-size: 12px; }
.proc-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
.proc-chain { flex: none; display: flex; align-items: stretch; margin-bottom: 12px; overflow-x: auto; }
.proc-chain-arrow { display: flex; flex-direction: column; justify-content: center; align-items: center; min-width: 32px; color: var(--blue); }
.proc-chain-arrow .material-icons { font-size: 26px; line-height: 26px; }
.proc-drop { font-size: 11px; line-height: 14px; color: var(--ink-lighter); min-height: 14px; }
.proc-stage {
  flex: 1 1 0; min-width: 118px; max-width: 190px; display: flex; flex-direction: column; gap: 2px;
  padding: 6px 10px; text-align: left; cursor: pointer; border: 1px solid var(--rail-bg); border-radius: 8px;
  background: var(--rail-bg); font: 400 12px/16px var(--font-sans); color: var(--white);
}
.proc-stage:hover { border-color: var(--blue-light); }
.proc-stage.is-on { background: var(--surface-raised); color: var(--ink); border-color: var(--blue); box-shadow: 0 0 0 2px var(--blue-lighter); }
.proc-stage-label { font-weight: 600; font-size: 11px; line-height: 14px; }
.proc-stage-row { display: flex; align-items: center; gap: 8px; }
.proc-stage-n { font-size: 18px; line-height: 22px; font-weight: 600; font-feature-settings: "tnum"; }
.proc-stage-sub { font-size: 11px; line-height: 14px; color: rgba(255, 255, 255, .7); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.proc-stage.is-on .proc-stage-sub { color: var(--ink-lighter); }
.proc-stage:not(.is-on) .proc-flag { color: #FF9B9B; }
.proc-stage.is-on .proc-flag { color: var(--critical); }
.proc-stage-run { margin-top: auto; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 11px; }
.proc-stage-run .badge { font-size: 10px; padding: 0 6px; }
.proc-open { align-self: flex-start; }
.proc-detail { padding: 16px 20px; }
.proc-substep { border-left: 3px solid var(--blue-lighter); padding-left: 16px; margin: 8px 0 28px; }
.proc-callout { padding: 10px 14px; margin-bottom: 12px; background: var(--blue-lightest); border-left: 3px solid var(--blue); border-radius: 4px; font-size: 13px; line-height: 19px; }
.proc-note { font-size: 12px; margin: 0 0 8px; }
.proc-source strong { font-weight: 600; }
.proc-file { margin-left: 8px; font-size: 12px; }
.proc-page .data-table td.mono { white-space: nowrap; }
.proc-mapping-row { cursor: pointer; }
.proc-mapping-row:hover td, .proc-mapping-row.is-on td { background: var(--blue-lightest); }
.proc-caret { width: 28px; color: var(--ink-lighter); }
.proc-caret .material-icons { font-size: 20px; vertical-align: middle; }
.proc-oneline { max-width: 0; width: 60%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.proc-cls { margin-right: 14px; font-size: 12px; }
.proc-mapping-open > td { padding-top: 0; background: var(--sky-lighter); }
.proc-unmapped td { background: var(--sky-lighter); }
.proc-classlist { max-height: 360px; overflow: auto; border: 1px solid var(--rule); border-radius: 6px; background: var(--surface-raised); margin: 4px 0 8px; }
.proc-page .proc-classlist .data-table { margin: 0; }
.proc-classlist thead th { position: sticky; top: 0; z-index: 1; }
.proc-zero td { color: var(--ink-lightest); }
/* Stufe 4 „Prüfung": two columns down to the window's edge,
   the list scrolls, the panel stands; inside the panel only the OCR text
   scrolls. From the prototype's pt-b-split and pt-docpanel. */
.proc-body:has(> .proc-split) { overflow: hidden; }
.proc-split { height: 100%; display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 16px; }
.proc-split-list { min-height: 0; overflow-y: auto; margin: 0; }
.proc-docpanel { min-height: 0; display: flex; flex-direction: column; gap: 10px; padding: 14px 16px; overflow: hidden; margin: 0; }
.proc-docpanel-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.proc-docpanel-title { min-width: 0; overflow-wrap: anywhere; }
.proc-docpanel-head .btn { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
.proc-docpanel-head .material-icons { font-size: 16px; }
.proc-path { font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.proc-compare { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; font-size: 13px; }
.proc-big { font-size: 15px; line-height: 21px; font-weight: 600; }
.proc-reasons { display: flex; flex-direction: column; gap: 4px; font-size: 13px; }
.proc-unknown { color: var(--critical); font-style: italic; }
.proc-low { color: var(--critical); }
.proc-hint { color: var(--ink-light); }
.proc-picker { display: flex; flex-direction: column; gap: 8px; }
.proc-input { font: 400 13px/18px var(--font-sans); color: var(--ink); background: var(--surface-raised); border: 1px solid var(--rule); border-radius: 6px; padding: 6px 10px; }
.proc-picker-list { display: flex; flex-direction: column; border: 1px solid var(--rule); border-radius: 6px; background: var(--surface-raised); max-height: 160px; overflow: auto; }
.proc-picker-item { text-align: left; padding: 6px 10px; border: 0; border-bottom: 1px solid var(--rule); background: none; cursor: pointer; font: 400 13px/18px var(--font-sans); color: var(--ink); }
.proc-picker-item:hover { background: var(--blue-lightest); }
.proc-ocr { flex: 1; min-height: 120px; display: flex; flex-direction: column; }
.proc-ocr pre { flex: 1; min-height: 0; margin: 0; padding: 12px; overflow: auto; white-space: pre-wrap; background: var(--sky-lighter); border: 1px solid var(--rule); border-radius: 6px; font: 400 12px/18px var(--font-mono); color: var(--ink); }
.proc-scope { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; font-size: 13px; }
.proc-scope .proc-select { font-family: var(--font-sans); max-width: 420px; }
.proc-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.proc-chip { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; border: 1px solid var(--rule); border-radius: 12px; background: var(--surface-raised); color: var(--ink); font: 400 12px/18px var(--font-sans); cursor: pointer; }
.proc-chip.is-on { border-color: var(--blue); box-shadow: 0 0 0 1px var(--blue); }
.proc-chip:disabled { opacity: .5; cursor: default; }
.proc-group { border-top: 1px solid var(--rule); padding: 8px 0; }
.proc-group summary { cursor: pointer; font-weight: 600; font-size: 13px; }
.proc-crow { display: grid; grid-template-columns: 2fr 1.4fr 1.6fr 48px; gap: 12px; padding: 6px 8px; font-size: 13px; cursor: pointer; border-radius: 4px; }
.proc-crow:hover, .proc-crow.is-on { background: var(--blue-lightest); }
.proc-crow-name { overflow-wrap: anywhere; }
/* Die Startkaesten der Stufen 1 bis 3: from the prototype's
   pt-boxes, pt-args-box, pt-path-row and pt-modal. */
.proc-start-boxes { display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); gap: 12px; align-items: start; margin-bottom: 12px; }
.proc-start-box { padding: 12px 16px; margin: 0; display: flex; flex-direction: column; gap: 8px; }
.proc-start-box .proc-note { margin: 0; }
.proc-start-head { display: flex; align-items: center; gap: 8px; }
.proc-start-arg { display: flex; flex-direction: column; gap: 4px; font-size: 13px; }
.proc-start-row { display: flex; gap: 8px; align-items: center; }
.proc-start-select { max-width: none; width: 100%; min-width: 0; font-family: var(--font-sans); font-size: 13px; }
.proc-start-row .proc-start-select { flex: 1; }
.proc-start-pick { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
.proc-start-pick .material-icons, .proc-start-item .material-icons { font-size: 18px; color: var(--blue); }
.proc-start-found { font-size: 13px; }
.proc-start-actions { margin-top: 4px; }
/* Stufe 6: the table per extractor sits inside its box. */
.proc-page .proc-start-box .data-table { margin-bottom: 0; }
.proc-start-modal-bg { position: fixed; inset: 0; z-index: 200; background: rgba(15, 23, 42, .42); display: flex; align-items: center; justify-content: center; }
.proc-start-modal { width: 640px; max-width: calc(100vw - 32px); padding: 16px 20px; display: flex; flex-direction: column; gap: 10px; }
.proc-start-modal-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.proc-start-path { font-size: 12px; overflow-wrap: anywhere; }
.proc-start-list { display: flex; flex-direction: column; border: 1px solid var(--rule); border-radius: 6px; background: var(--surface-raised); max-height: 360px; overflow: auto; }
.proc-start-item { display: grid; grid-template-columns: 24px minmax(0, 1fr) auto; gap: 8px; align-items: center; text-align: left; padding: 6px 10px; border: 0; border-bottom: 1px solid var(--rule); background: none; cursor: pointer; font: 400 13px/18px var(--font-sans); color: var(--ink); }
.proc-start-item:hover { background: var(--blue-lightest); }

/* ── Der Vergleich der Miete je m² ──────────────────────────

   Zwei Zahlen im Wert des Feldes `rent_sqm`: die eigene gross, der Vergleich
   klein darunter. Der Vergleich darf die eigene Zahl nicht überstimmen — er ist
   eine Spanne aus einer Liste, sie ist an einem Vertrag gemessen. Darum die
   Schriftgrösse der Regelzeile (`ap-attr-rule`) und nicht die des Wertes.

   Jede Farbe ein Token, wie überall sonst: das Dossier hat ein helles und ein
   dunkles Thema, und ein fester Wert hier wäre im dunklen unlesbar. */
.rb { display: block; }
.rb-ist { display: block; }
.rb-vgl {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px;
  margin-top: 2px;
  font: 400 11px/18px var(--font-sans); color: var(--ink-lighter);
}
.rb-vgl-label {
  font: 700 10px/14px var(--font-sans);
  letter-spacing: 1.5px; font-variant-caps: all-small-caps;
}
.rb-vgl-spanne { color: var(--ink-light); }
.rb-vgl-quelle { color: var(--blue); text-decoration: none; }
.rb-vgl-quelle:hover { text-decoration: underline; }
