/* ERP demo UI.
 *
 * Subject: internal tool for a shop that builds mobile dividing walls that
 * run on rails. Two ideas carry the look, everything else stays quiet:
 *   1. the rail — a Projekt is a rail, its Vorgänge are numbered carriages
 *      hanging off it (teaches the two-level model; docs/data-model.md).
 *   2. provenance made visible — values inferred from Kuhnle wear an amber
 *      "Altdaten" mark; confirmed/fact values render plain.
 */

:root {
  --ground: #e7e5e0; /* warm aluminium */
  --panel: #ffffff;
  --panel-2: #f6f4f0;
  --ink: #1c1b19; /* warm near-black */
  --muted: #6b6862;
  --faint: #97928a;
  --rail: #a8a29a; /* anodized grey */
  --hairline: #d7d3cb;
  --accent: #154889; /* RAL 5005 signal blue */
  --accent-soft: #e4ebf4;
  --legacy: #c77b23; /* amber — inferred-data marker only */
  --legacy-soft: #f6ecdd;
  --ok: #2f6b3d;
  --ok-soft: #e4efe6;
  --stop: #9a3b2e;
  --stop-soft: #f2e4e1;

  --font-body: "Archivo", system-ui, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, monospace;
  --radius: 3px;
  /* Between the stacked parts of a list screen: finder, filters, table. */
  --list-gap: 0.85rem;
  --wordmark-width: 4.5rem;
  --shadow: 0 1px 2px rgba(28, 27, 25, 0.06), 0 1px 8px rgba(28, 27, 25, 0.04);
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
}

a {
  color: var(--accent);
  text-decoration: none;
}
a:hover {
  text-decoration: underline;
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ---- top bar --------------------------------------------------------- */

.topbar {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  padding: 0 1.5rem;
  height: 56px;
  background: var(--panel);
  border-bottom: 1px solid var(--hairline);
}

.wordmark {
  /* A fixed width, so the tabs under the bar can start where the areas do. */
  flex: 0 0 var(--wordmark-width);
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-weight: 700;
  font-size: 1.05rem;
  letter-spacing: 0.02em;
  color: var(--ink);
}
.wordmark:hover {
  text-decoration: none;
}
.wordmark__mark {
  width: 15px;
  height: 15px;
  background: var(--accent);
  /* three panels sliding off a rail — the mark echoes the layout */
  box-shadow: 5px 0 0 -1px var(--panel), 6px 0 0 -1px var(--accent);
}

/* The areas. Each link runs the full height of the bar so the open one can
   carry its mark on the bar's bottom edge, where the tabs below hang off it
   — the rail again, with the screens of one desk as its carriages. */
.topbar__nav,
.topbar__setup {
  display: flex;
  align-self: stretch;
  gap: 1.25rem;
  font-weight: 500;
}
.topbar__nav {
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
}
.topbar__setup {
  margin-left: auto;
}
.topbar__area {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  white-space: nowrap;
  color: var(--muted);
  border-bottom: 3px solid transparent;
  border-top: 3px solid transparent;
}
.topbar__area:hover {
  color: var(--ink);
  text-decoration: none;
}
.topbar__area[aria-current] {
  color: var(--ink);
  border-bottom-color: var(--accent);
}
.topbar__gear {
  width: 1rem;
  height: 1rem;
}

/* The open area's screens. Quieter than the bar above — a choice inside a
   desk, not between desks — but the same white, so bar and tabs read as one
   header rather than as two bands stacked on the strips below them. */
.sectionbar {
  display: flex;
  gap: 1.5rem;
  /* Under the first area, so the tabs hang off the bar they belong to. */
  padding: 0 1.5rem 0 calc(3rem + var(--wordmark-width));
  overflow-x: auto;
  scrollbar-width: none;
  background: var(--panel);
  border-bottom: 1px solid var(--hairline);
  font-size: 0.88rem;
}
.sectionbar__link {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.6rem 0 calc(0.6rem - 2px);
  white-space: nowrap;
  color: var(--muted);
  border-bottom: 2px solid transparent;
}
.sectionbar__link:hover {
  color: var(--ink);
  text-decoration: none;
}
.sectionbar__link[aria-current] {
  color: var(--ink);
  font-weight: 500;
  border-bottom-color: var(--ink);
}

/* Too narrow for one row: the areas get a row of their own under the brand
   and the account, scrolling sideways if they must, rather than being cut
   off at the edge where nobody would know they exist. */
@media (max-width: 960px) {
  .topbar {
    flex-wrap: wrap;
    height: auto;
    gap: 0 1rem;
  }
  .wordmark,
  .topbar__setup,
  .account,
  .langswitch {
    height: 52px;
    align-items: center;
  }
  .topbar__nav {
    order: 1;
    flex-basis: calc(100% + 3rem);
    height: 44px;
    margin: 0 -1.5rem;
    padding: 0 1.5rem;
    border-top: 1px solid var(--hairline);
  }
  .sectionbar {
    padding-left: 1.5rem;
  }
}
/* A phone: settings is its gear, and a long name is cut short. */
@media (max-width: 600px) {
  .topbar__setup .topbar__label {
    display: none;
  }
  .account__name {
    max-width: 7rem;
  }
}

.langswitch {
  margin-left: auto;
  display: flex;
  gap: 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.8rem;
}
.langswitch a {
  color: var(--faint);
}
.langswitch a[aria-current] {
  color: var(--ink);
  font-weight: 600;
}

/* The person's own menu, at the end of the bar: who is signed in, and what
   belongs to them rather than to the work. */
.account {
  position: relative;
  display: flex;
  align-self: stretch;
  margin-left: auto;
}
.topbar__setup + .account {
  margin-left: 0;
}
.account__toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  list-style: none;
  cursor: pointer;
  font-size: 0.88rem;
  color: var(--muted);
}
.account__toggle::-webkit-details-marker {
  display: none;
}
/* A chevron drawn from two borders, turned when the menu is open. */
.account__toggle::after {
  content: "";
  width: 0.4rem;
  height: 0.4rem;
  margin-top: -0.2rem;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg);
}
.account[open] .account__toggle::after {
  margin-top: 0.2rem;
  transform: rotate(-135deg);
}
.account__toggle:hover,
.account[open] .account__toggle {
  color: var(--ink);
}
.account__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.account__menu {
  position: absolute;
  top: calc(100% + 1px);
  right: 0;
  z-index: 20;
  min-width: 13rem;
  padding: 0.35rem 0;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: 0 10px 30px rgba(28, 27, 25, 0.14);
}
.account__row,
.account__item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  width: 100%;
  padding: 0.5rem 0.9rem;
  font: inherit;
  font-size: 0.88rem;
}
.account__label {
  color: var(--muted);
}
.account__row .langswitch {
  margin-left: 0;
}
.account__item {
  background: none;
  border: 0;
  border-top: 1px solid var(--hairline);
  color: var(--ink);
  text-align: left;
  cursor: pointer;
}
.account__item:hover {
  background: var(--panel-2);
}

.page {
  max-width: 1140px;
  margin: 0 auto;
  padding: 1.35rem 1.5rem 4rem;
}
/* A screen that has to hold a readable page of A4 beside its own fields.
   Opt-in per screen rather than the default: everything else is read in
   lines of text, and a line that runs the width of a monitor is harder to
   read, not easier. */
.page--wide {
  max-width: 1480px;
}
/* Beside the inbox rail, a wide page stops short of the strip standing in
   its gutter instead of running under it — never narrower than an ordinary
   page, and below the width where the strip leaves the gutter for the
   bottom corner (see `.inbox`) the question does not arise. */
@media (min-width: 1260px) {
  .shell:has(> .inbox) > .page--wide {
    max-width: max(1140px, min(1480px, calc(100vw - 10rem)));
  }
}
/* The inbox rail is out of flow (see `.inbox`), so the shell is one column and
   the list keeps the same width and the same left edge as every other screen —
   the table does not move when the operator crosses from Projekte to Parteien.
   The shell stays a box of its own because a drop anywhere on it is a drop
   into the rail. */
.shell__main {
  min-width: 0;
}

/* `border` too: a button keeps its default border otherwise, and that is
   what is left showing of it — a speck at the corner of its box. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---- index ----------------------------------------------------------- */

h1 {
  font-size: 1.5rem;
  font-weight: 700;
  margin: 0;
  letter-spacing: -0.01em;
}

/* The finder is the page's instrument, so it gets the width and the weight:
   a wide, calm console you type a Projektnummer into. The mono face makes a
   typed number look native and signals "type here first". */
.finder {
  margin: 0.5rem 0 var(--list-gap);
}
.finder__title {
  font-size: 1.15rem;
  font-weight: 600;
  color: var(--muted);
  letter-spacing: 0.02em;
  margin-bottom: 0.75rem;
}
.finder__field {
  position: relative;
  display: flex;
  align-items: center;
}
.finder__icon {
  position: absolute;
  left: 1rem;
  width: 20px;
  height: 20px;
  color: var(--faint);
  pointer-events: none;
}
.finder__input {
  width: 100%;
  padding: 0.95rem 1rem 0.95rem 3rem;
  font-family: var(--font-mono);
  font-size: 1.15rem;
  color: var(--ink);
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.finder__input::placeholder {
  color: var(--faint);
}
.finder__input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.finder__field:focus-within .finder__icon {
  color: var(--accent);
}

.results {
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  /* Scroll rather than clip if the columns still exceed the width (e.g. long
     German labels on a narrow viewport). */
  overflow-x: auto;
}

table {
  width: 100%;
  border-collapse: collapse;
}
/* Index tables state their column widths in a <colgroup> and lay out fixed.
   With auto layout a cell kept to one line (`col-desc`, `col-client`) cannot
   shrink below its own text, so one long company name pushes the trailing
   columns out of the panel; fixed layout gives every column the width it was
   given regardless of what is in it. */
.table--cols {
  table-layout: fixed;
}
/* Below this a column would have to give up width it needs, so the panel
   scrolls instead — a scrollbar costs less than a shortened value. This is
   the *only* width at which one of these tables scrolls sideways: above it
   the columns add up to the table by construction, because they are declared
   through `_list.html`'s `columns` rather than as a mix of percentages and
   rem that sums to 100% at one viewport and nowhere else.

   Only inside `.results`, which is the element that scrolls. A floor on a
   table with nothing to scroll it is not a floor, it is an overflow: the
   table runs out of its panel and over whatever is beside it, which is what
   a narrow table in a column of a split screen would do. */
.results .table--cols {
  min-width: 58rem;
}
/* Truncation is never a default: a shortened date or a clipped badge reads as
   a value rather than as damage, so a column says for itself that its content
   may be cut (`.col-text` and the prose columns below). A column holding
   something rigid — a date, a number, a badge — is given the width it needs in
   the screen's own <colgroup>, in rem rather than as a share, so it keeps that
   width at every viewport. Where one is nevertheless too narrow the content
   overflows visibly, which is the bug showing itself. */
.col-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* A head is sized with its column — a column declared in rem is as wide as
   its head needs — but a flexible column narrows with the window, and a
   head one word wider than that would otherwise run under its neighbour
   or past the panel's edge and be clipped there. So a head that does not
   fit breaks, at a hyphen where the language allows one, and is never
   cut. `anywhere` rather than `break-word`, because only it lets a word
   count as breakable when a box sizes itself — the sort link below is
   one, and would otherwise hold its head to its longest word. */
thead th {
  overflow-wrap: anywhere;
  hyphens: auto;
  text-align: left;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--faint);
  padding: 0.6rem 1rem;
  border-bottom: 1px solid var(--hairline);
}
tbody td {
  padding: 0.6rem 1rem;
  border-bottom: 1px solid var(--panel-2);
}
tbody tr:last-child td {
  border-bottom: none;
}
/* Whole-row link without JS or an invalid <a> around <tr>: the row's real
   anchor keeps the href (new tab, keyboard, focus), and its ::after overlay is
   stretched across the row so a click anywhere navigates to the detail page.
   `row-link` marks the cell holding that anchor, whichever column leads the
   table — a number (Projekte), a name (Parteien), a timestamp (Kuhnle). */
tbody tr {
  position: relative;
}
/* Only rows that actually lead somewhere get the affordance — a table of plain
   figures (invoice lines) must not invite a click. */
tbody tr:has(.row-link) {
  cursor: pointer;
}
tbody tr:has(.row-link):hover td,
tbody tr:has(.row-link):focus-within td {
  background: var(--accent-soft);
}
.row-link a::after {
  content: "";
  position: absolute;
  inset: 0;
}
/* A control in such a row — the release on the review list — stands above
   the overlay, or the row's click would swallow it; so does a link to
   somewhere else than the row's own, such as a line's Vorgang. */
tbody tr:has(.row-link) :is(button, input, select, textarea),
tbody tr:has(.row-link) td:not(.row-link) a {
  position: relative;
  z-index: 1;
}
/* A mark that leads somewhere of its own — the Lagerliste's price notice —
   stands above the overlay too, and carries the warning banner's amber. */
.price-warn {
  position: relative;
  z-index: 1;
  display: inline-flex;
  vertical-align: -0.15em;
  margin-right: 0.3rem;
  color: var(--legacy);
}
.price-warn:hover {
  color: var(--ink);
}
/* The leading value identifies the row, it is not a link among links: blue and
   underlined would falsely single it out while the whole row navigates. The
   anchor stays (href/keyboard/new-tab and the overlay) but reads as plain
   text; the row's hover + pointer carry the affordance. */
.row-link a,
.row-link a:hover {
  color: var(--ink);
  text-decoration: none;
}

/* Sortable header: the caret is the only ornament, and only on the active
   column, so the sort state reads at a glance without chrome on every head. */
.th-sort a {
  display: inline-flex;
  max-width: 100%;
  align-items: center;
  gap: 0.3rem;
  color: var(--faint);
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
}
.th-sort a:hover {
  color: var(--ink);
  text-decoration: none;
}
.th-sort.is-sorted a {
  color: var(--accent);
}
.caret {
  font-size: 0.7em;
  line-height: 1;
}

/* The filter panel (`_list.html`'s `filter_panel`): one row per axis, its
   name in a label column of one width on every screen, so the controls of
   every row — and every list — start on one line. */
.filters {
  --filter-label: 7.5rem;
  --control-height: 2rem;
  display: grid;
  gap: 0.55rem;
  margin-bottom: var(--list-gap);
}
.filters__row {
  display: grid;
  grid-template-columns: var(--filter-label) minmax(0, 1fr);
  column-gap: 0.75rem;
  align-items: center;
}
.filters__label {
  font-size: 0.8rem;
  color: var(--muted);
}
.filters__control {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
  min-width: 0;
}
.filters__aside {
  margin-left: auto;
}
.filters__form {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
}
.filters__range {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}
.filters__dash {
  color: var(--faint);
}
/* The fold sits in the control column under the rows in view; opened, its
   rows step back out to the label column so they align with those above. */
.filters__more {
  flex-basis: 100%;
}
.filters__more:not([open]) {
  flex-basis: auto;
}
/* The fold is a line of text, not a control, so it takes the height of one
   rather than of the controls above it, and sits closer under them — the
   list it narrows starts as high up the page as it can. */
.filters__row--fold {
  margin-top: -0.2rem;
}
/* Closed, the fold is the panel's last line, and the gap under the panel is
   sized for a row of controls; text takes the same gap below as above. */
.filters__row--fold:has(.filters__more:not([open])) {
  margin-bottom: -0.45rem;
}
.filters__more > .fold {
  min-height: 1.5rem;
}
.filters__more-body {
  display: grid;
  gap: 0.55rem;
  margin: 0.2rem 0 0.1rem calc(-1 * (var(--filter-label) + 0.75rem));
}
/* What a closed fold narrows by. Open, the rows say it themselves. */
.filters__more[open] ~ .token {
  display: none;
}
.filters__reset {
  height: 1.7rem;
  padding: 0 0.6rem;
  font-size: 0.8rem;
}

/* Segmented control: one choice out of a fixed few. One outline around all
   of them, the chosen one filled — never loose pills, which read as tags
   that could be combined. Too wide for a narrow screen, it scrolls inside
   its own outline rather than wrapping into a second, broken one. */
.seg {
  display: inline-flex;
  max-width: 100%;
  height: var(--control-height);
  padding: 2px;
  gap: 2px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  background: var(--panel);
  overflow-x: auto;
  scrollbar-width: none;
}
.seg__opt {
  display: inline-flex;
  align-items: center;
  padding: 0 0.7rem;
  border-radius: 2px;
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--muted);
  white-space: nowrap;
}
.seg__opt:hover {
  background: var(--panel-2);
  color: var(--ink);
  text-decoration: none;
}
.seg__opt.is-on {
  background: var(--ink);
  color: var(--panel);
}
/* A Vorgang's status lights in the colours its badge has, so the filter
   speaks the vocabulary of the rows it narrows. A hover outlines it in that
   colour and leaves the fill to the chosen one, as the status rail on a
   Vorgang's page does, so pointing previews without looking chosen. */
.seg__opt--inquiry {
  --tone-ink: var(--ink);
  --tone-soft: var(--panel-2);
  --tone-ring: var(--rail);
}
.seg__opt--offer_created {
  --tone-ink: var(--accent);
  --tone-soft: var(--accent-soft);
  --tone-ring: var(--accent);
}
.seg__opt--ordered {
  --tone-ink: var(--ok);
  --tone-soft: var(--ok-soft);
  --tone-ring: var(--ok);
}
.seg__opt--completed {
  --tone-ink: var(--ink);
  --tone-ring: var(--ink);
}
.seg__opt--cancelled {
  --tone-ink: var(--stop);
  --tone-soft: var(--stop-soft);
  --tone-ring: var(--stop);
}
.seg__opt[class*="seg__opt--"]:hover {
  background: var(--panel);
  color: var(--tone-ink);
  box-shadow: inset 0 0 0 1px var(--tone-ring);
}
.seg__opt[class*="seg__opt--"].is-on:not(.seg__opt--completed) {
  background: var(--tone-soft);
  color: var(--tone-ink);
  box-shadow: inset 0 0 0 1px var(--tone-ring);
}
.seg__opt--completed.is-on {
  background: var(--ink);
  color: var(--panel);
  box-shadow: none;
}

/* The same control as a form field (`_form.html`'s `choice_field`): radios
   under the options, so the chosen one is the checked one. The height is an
   input's, so it stands on a row's control line beside them, and it keeps
   the width of its options rather than stretching across the column. */
.seg--field {
  height: 2rem;
  justify-self: start;
  align-self: start;
}
.seg--field .seg__opt {
  position: relative;
  cursor: pointer;
}
.seg__radio {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.seg--field .seg__opt:has(> .seg__radio:checked) {
  background: var(--ink);
  color: var(--panel);
}
/* The empty answer — offen, keine Angabe — chosen is no answer yet: shown
   as the place the control rests, not filled like a decision, so a form
   of open items does not read as a column of answers. */
.seg--field .seg__opt:has(> .seg__radio[value=""]:checked) {
  background: var(--panel-2);
  color: var(--ink);
}
/* Read-only: the answer in view, nothing to press. */
.seg--field.is-disabled .seg__opt,
.seg--field.is-disabled .seg__radio {
  cursor: default;
}
.seg--field.is-disabled .seg__opt:hover {
  background: none;
  color: var(--muted);
}
.seg--field.is-disabled .seg__opt:has(> .seg__radio:checked) {
  background: var(--panel-2);
  color: var(--ink);
}
/* The outline drawn inside the option: the control scrolls when it is too
   wide, and a ring outside an option would be clipped by it. */
.seg--field .seg__opt:has(> .seg__radio:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* A select drawn like the rest: the height of a segmented control, the
   app's own chevron rather than the platform's. */
.select {
  height: var(--control-height, 2rem);
  min-width: 10rem;
  max-width: 18rem;
  padding: 0 2rem 0 0.65rem;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  background: var(--panel)
    url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%236b6862' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'><path d='M4.5 6.5L8 10l3.5-3.5'/></svg>")
    no-repeat right 0.55rem center / 0.85rem;
  font: inherit;
  font-size: 0.85rem;
  color: var(--ink);
  appearance: none;
  cursor: pointer;
}
.select:hover,
.date-input:hover {
  border-color: var(--rail);
}
.select:focus-visible,
.date-input:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.date-input {
  height: var(--control-height, 2rem);
  padding: 0 0.5rem;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  background: var(--panel);
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--ink);
}

/* A checklist: a select that takes several (`_list.html`'s
   `_filter_checklist`). Closed it is the select it looks like, naming what
   is chosen; open, a panel of boxes floats under it rather than pushing the
   rows below apart. */
.checklist {
  position: relative;
}
.checklist__toggle {
  display: flex;
  align-items: center;
  min-width: 14rem;
  list-style: none;
}
.checklist__toggle::-webkit-details-marker {
  display: none;
}
.checklist[open] > .checklist__toggle {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.checklist__value {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.checklist__panel {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  z-index: 20;
  display: grid;
  gap: 0.4rem;
  width: max(100%, 17rem);
  padding: 0.4rem;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: 0 10px 30px rgba(28, 27, 25, 0.14);
}
.checklist__find {
  height: 1.9rem;
  padding: 0 0.5rem;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  font: inherit;
  font-size: 0.85rem;
}
.checklist__find:focus-visible {
  outline: none;
  border-color: var(--accent);
}
/* The filter's form is a row elsewhere; in the panel it would shrink the
   list to its widest label and leave the scrollbar mid-panel. */
.checklist__panel > .filters__form {
  display: grid;
}
.checklist__options {
  display: grid;
  max-height: 18rem;
  overflow-y: auto;
}
.checklist__option {
  margin: 0;
  padding: 0.3rem 0.45rem;
  border-radius: 2px;
  font-size: 0.85rem;
  cursor: pointer;
}
.checklist__option:hover {
  background: var(--panel-2);
}
.checklist__option[hidden] {
  display: none;
}
.checklist__clear {
  justify-self: start;
}
/* In a form it is a field like any other, as wide as its column: neither
   floor nor cap of the filter-sized `.select` it borrows its look from. */
.checklist--field .checklist__toggle {
  min-width: 0;
  max-width: none;
}

/* A filter narrowing the list from inside the closed fold. Bordered, since
   it is an act: the cross takes the filter off. */
.token {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  height: 1.7rem;
  padding: 0 0.4rem 0 0.6rem;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  background: var(--panel);
  font-size: 0.8rem;
  color: var(--ink);
}
.token .caret {
  font-size: 1rem;
  color: var(--faint);
}
.token:hover {
  border-color: var(--accent);
  text-decoration: none;
}
.token:hover .caret {
  color: var(--accent);
}

/* A pill: a tag a row carries, or one of a set that can be held together
   (a login's roles). Never a filter — one choice out of a few is the
   segmented control above. */
.chip {
  font-size: 0.8rem;
  font-weight: 500;
  padding: 0.25rem 0.7rem;
  border-radius: 999px;
  border: 1px solid var(--hairline);
  background: var(--panel);
  color: var(--muted);
  cursor: pointer;
}
.chip:hover {
  border-color: var(--faint);
  text-decoration: none;
}
.chip.is-on {
  border-color: transparent;
  color: var(--panel);
  background: var(--ink);
}
/* Never wrapped: a date or an amount broken across two lines is unreadable,
   and the column it sits in is sized in rem to hold it whole. */
.num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* An identifier is never shortened: half a Projektnummer is not a reference,
   and a legacy key can run long. It wraps instead, against the `.num` rule it
   shares a cell with. */
.col-num {
  font-weight: 600;
  white-space: normal;
  overflow-wrap: anywhere;
}
/* A party's name is its identifier, so it leads the row — but names run long.
   The ellipsis sits on an inner span, never on the anchor itself:
   `overflow: hidden` there would clip the row overlay and kill the click
   target outside the name. `_list.html`'s `name_cell` is what guarantees the
   span is there; without it a long name runs across the columns to its right.

   A flex row rather than a line of inline boxes, so the name gives way and
   the badges beside it never do: the name is the one part of the cell that
   still reads when it is cut, and a status badge pushed out of view is a
   fact the row has silently stopped stating. */
.col-name {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  white-space: nowrap;
  font-weight: 500;
}
.col-name a {
  min-width: 0;
}
.col-name > :not(a) {
  flex: none;
}
.col-name a > span {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* One reachability column (mail or phone). */
.col-contact {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--muted);
}
.col-right {
  text-align: right;
}
/* Job description: the most informative field, but kept to one line so the
   table stays scannable — full text lives on the detail screen. */
.col-desc {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink);
}
/* The client is incidental (inferred from the legacy customer key), so it
   reads secondary to the number and description. One line like the rest:
   a wrapped company-plus-contact string sets the row height for the whole
   table and pushes the trailing columns out of the panel. */
.col-client {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--muted);
}
.dash {
  color: var(--faint);
}

.pager {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-top: 1rem;
  font-size: 0.85rem;
  color: var(--muted);
}
.pager__spacer {
  flex: 1;
}
/* A list's bulk acts (`_list.html`'s `bulk_bar`): hidden until a row is
   ticked, then leading the row, with how many are ticked written by
   `static/js/bulk.js`. Not a CSS counter: the list's wrapper is a size
   container, whose style containment keeps the table's increments from
   ever reaching the pager. */
.pager__bulk {
  display: none;
  align-items: center;
  gap: 0.5rem;
  padding-right: 1rem;
  border-right: 1px solid var(--hairline);
}
.page:has(.bulk__pick:checked) .pager__bulk {
  display: flex;
}
.pager__picked {
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
/* The tick column. Its label fills the cell and stands above the row's
   click overlay, so a near miss ticks the row instead of opening it. */
.col-pick {
  padding: 0;
}
.pick {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 2.25rem;
  cursor: pointer;
}
.pager__range {
  font-variant-numeric: tabular-nums;
}
/* The two steps as one control, joined on their shared edge: they move the
   range beside them, so they read as its controls rather than as two more
   buttons in the row. Sized to the page-size select's height. */
.pager__steps {
  display: inline-flex;
}
.pager__step {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  background: var(--panel);
  border: 1px solid var(--hairline);
  color: var(--ink);
}
.pager__step svg {
  width: 1rem;
  height: 1rem;
}
.pager__step:first-child {
  border-radius: var(--radius) 0 0 var(--radius);
}
.pager__step + .pager__step {
  margin-left: -1px;
  border-radius: 0 var(--radius) var(--radius) 0;
}
a.pager__step:hover {
  position: relative;
  border-color: var(--accent);
  color: var(--accent);
}
.pager__step[aria-disabled] {
  color: var(--faint);
  opacity: 0.5;
}
.pager__size {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.pager__label {
  font-size: 0.8rem;
  color: var(--muted);
}
.pager__size .select {
  min-width: 0;
}

/* A paged list runs to hundreds of rows on the page's own scroll, so its
   column labels and its pager hold on to the window while it passes: the
   head at the top, the pager at the bottom until the list's real end
   arrives beneath it. Never a panel scrolling inside the page — a second
   scrollbar under the first is a list nobody can wheel through.

   Sticking needs the panel not to be a scroll container, which its
   sideways `overflow` makes it. So the panel clips instead wherever the
   table fits at its `min-width`, measured on the list itself rather than
   the viewport, since the inbox rail takes width the viewport still
   counts; below that it scrolls sideways as before and the head scrolls
   away with the rows.

   The head's rule is a shadow rather than its border: under
   `border-collapse` a border belongs to the table, not to the cell, and is
   left behind when the cell sticks. */
:has(> .results + .pager) {
  container-type: inline-size;
}
@container (min-width: 58rem) {
  .results:has(+ .pager) {
    overflow: clip;
  }
  .results:has(+ .pager) thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--panel);
    border-bottom: 0;
    box-shadow: inset 0 -1px var(--hairline);
  }
}
/* The stuck pager is window chrome, the top bar's counterpart: white, a
   hairline above it, edge to edge. Only as wide as the list it would read
   as the table's own last row, and rows passing under it as rows cut off.
   The bleed is paint rather than box — a spread shadow for the white, an
   outset border image for the rule, the same hairline the nav bars are
   ruled with — because neither adds to the page's scrollable width, where
   a box that wide would.

   The white also runs down past the bar, to the bottom of the window: on a
   short list, or once the list's end arrives, the bar rests under the last
   row, and the page's own ground showing beneath it would make it read as
   a strip that came loose. The page's bottom padding goes for the same
   reason, so a long list ends with the bar flush on the window's edge.

   Resting, the bar keeps a strip of ground between itself and the panel:
   flush, the panel's last border and the bar's rule read as one line, and
   the list looks cut off with a row still hidden beneath. Stuck, the
   margin is spent and changes nothing. */
.page:has(.results + .pager) {
  padding-bottom: 0;
}
.results + .pager {
  position: sticky;
  bottom: 0;
  z-index: 1;
  margin-top: 1.5rem;
  padding: 0.75rem 0;
  background: var(--panel);
  border-top: 1px solid var(--hairline);
  border-image: linear-gradient(var(--hairline) 0 0) 1 / 1px 0 0 / 0 100vmax;
  box-shadow: 0 0 0 100vmax var(--panel);
  clip-path: inset(0 -100vmax -100vmax);
}

/* A region an htmx swap replaces says so while the request is out: its
   content dims and a bar runs along its edge. Any element can be one —
   give it `.swap-region` and point the controls' `hx-indicator` at it; the
   list controls in `_list.html` all point at `#results`.

   Both wait a moment before they show, so a swap that answers at once does
   not flicker; leaving the state has no delay, so the new rows arrive at
   full strength. Where the region has a pager the bar runs on the pager's
   rule, since that stays on screen however far down the list was read;
   elsewhere it runs along the region's top.

   The filter panel never dims: it holds the control just used, which is
   current, not stale — and an open checklist fading out and back on every
   tick reads as flicker. */
.swap-region {
  position: relative;
}
.swap-region > * {
  transition: opacity 0.12s;
}
.swap-region.htmx-request > :not(.pager, .filters) {
  opacity: 0.45;
  transition: opacity 0.15s 0.15s;
}
.swap-region.htmx-request .pager::before,
.swap-region.htmx-request:not(:has(.pager))::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 2px;
  background: linear-gradient(
      90deg,
      transparent,
      var(--accent) 40%,
      var(--accent) 60%,
      transparent
    )
    0 0 / 40% 100% no-repeat;
  animation:
    swap-busy-in 0s 0.15s both,
    swap-busy 1.1s 0.15s ease-in-out infinite;
}
.swap-region.htmx-request .pager::before {
  top: -1px;
}
.swap-region.htmx-request:not(:has(.pager))::before {
  top: 0;
}
@keyframes swap-busy-in {
  from {
    visibility: hidden;
  }
  to {
    visibility: visible;
  }
}
@keyframes swap-busy {
  from {
    background-position: -70% 0;
  }
  to {
    background-position: 170% 0;
  }
}
@media (prefers-reduced-motion: reduce) {
  .swap-region.htmx-request .pager::before,
  .swap-region.htmx-request:not(:has(.pager))::before {
    background: var(--accent);
    animation: swap-busy-in 0s 0.15s both;
  }
}
/* Inline-flex rather than inline: a button and a link styled as one are a
   <button> and an <a>, and the two centre their text differently as soon as
   a height is forced on them — which a row of controls does. Centring here
   is what makes the two shapes interchangeable wherever a label sits beside
   an action. */
/* The interface's action grammar: one look per meaning, so a user who has
   learned one screen has learned them all.
     link (blue text)          names a place: another page, a file
     button (.btn, bordered)   names an act: opens a form, files, removes,
                               downloads — with a pencil when it opens an
                               editor, a plus when it adds. Also when the
                               act is completed on the screen it leads to:
                               issuing a document is an act, not a place
     primary (.btn--primary)   saves the form it closes; one per form, and
                               shown only once the form differs from what
                               was loaded (`[data-dirty-form]`,
                               `static/js/dirty_form.js`), so a screen
                               never offers a save with nothing to save
     fold (.fold, chevron)     shows or hides more of what is on the page
     lead (.btn--lead)         a record's main act, set in bold like the add
                               bar is in its list: still bordered, since it
                               does not save anything. One per record
     add bar (.add-bar)        adds an entry to the list it closes, where
                               the entry will appear
     drop (×)                  takes a row out of the list being edited,
                               which the save then makes so. Always a
                               cross, never a bin: a bin beside a name
                               reads as deleting what the name is
     turn (.btn--turn, dot)    the way to what waits on the person looking:
                               a bordered button that also carries the dot
                               and the accent border. Decided by the
                               service that owns the step, never by the
                               template
   Grey text is information and is never clickable. Where an editor folds
   out of a record's head, its button sits in the head's top corner, so
   "edit this" is in one place on every detail screen. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  font: inherit;
  font-size: 0.85rem;
  padding: 0.35rem 0.8rem;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  color: var(--ink);
  cursor: pointer;
  text-align: center;
}
.btn:hover {
  border-color: var(--accent);
  text-decoration: none;
}
.btn--turn {
  border-color: var(--accent);
}
.turn {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--accent);
}
.btn:disabled {
  cursor: default;
  opacity: 0.5;
  border-color: var(--hairline);
}
/* A summary drawn as a button: no disclosure triangle, its label's width,
   and pressed while its form is open, so the open form says what opened
   it. */
summary.btn {
  list-style: none;
  width: fit-content;
}
summary.btn::-webkit-details-marker {
  display: none;
}
details[open] > summary.btn {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--accent-soft);
}
/* The act a record is mostly opened for (`.btn--lead`, above). */
.btn--lead {
  font-weight: 600;
}
.btn--danger {
  color: var(--stop);
}
.btn--danger:hover {
  border-color: var(--stop);
}

/* Glyphs are masks over the text colour, so they follow every state a
   control's colour does. */
:root {
  --glyph-edit: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.5' stroke-linejoin='round' stroke-linecap='round'><path d='M10.5 2.75l2.75 2.75L6 12.75H3.25V10z'/><path d='M8.75 4.5l2.75 2.75'/></svg>");
  --glyph-add: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.75' stroke-linecap='round'><path d='M8 3v10M3 8h10'/></svg>");
  --glyph-check: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M3.25 8.5l3 3 6.5-7'/></svg>");
  --glyph-fold: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'><path d='M6 3.5L10.5 8 6 12.5'/></svg>");
}
.btn--edit::before,
.btn--add::before,
.add-bar::before,
.fold::before {
  content: "";
  flex: none;
  width: 0.95em;
  height: 0.95em;
  background: currentColor;
  mask: var(--glyph-edit) center / contain no-repeat;
}
.btn--add::before,
.add-bar::before {
  mask-image: var(--glyph-add);
}

/* Fold: ink text behind a chevron that turns as it opens — neither blue,
   which would promise another page, nor grey, which would read as a
   caption. */
.fold {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  width: fit-content;
  list-style: none;
  font-size: 0.85rem;
  color: var(--ink);
  cursor: pointer;
}
.fold::-webkit-details-marker {
  display: none;
}
.fold::before {
  width: 0.8em;
  height: 0.8em;
  mask-image: var(--glyph-fold);
  color: var(--muted);
  transition: transform 0.12s ease;
}
details[open] > .fold::before {
  transform: rotate(90deg);
}
.fold:hover {
  color: var(--accent);
}
.fold:hover::before {
  color: inherit;
}
@media (prefers-reduced-motion: reduce) {
  .fold::before {
    transition: none;
  }
}

/* Add bar: the full width of the list it closes, dashed like a slot still
   to be filled. Open, it steps back, so the form under it leads. */
.add-bar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.45rem;
  width: 100%;
  padding: 0.7rem 0.75rem;
  list-style: none;
  font: inherit;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--ink);
  background: transparent;
  border: 1.5px dashed var(--rail);
  border-radius: var(--radius);
  text-decoration: none;
  cursor: pointer;
}
.add-bar::-webkit-details-marker {
  display: none;
}
.add-bar:hover {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--accent-soft);
}
details[open] > .add-bar {
  border-style: solid;
  border-color: var(--hairline);
  color: var(--muted);
  background: transparent;
}
/* Its plus turns into the cross that closes it again. */
details[open] > .add-bar::before {
  transform: rotate(45deg);
}
/* Inside a grid of rows the bar is one more row, so it takes a row's
   height rather than a section's — and the fields' width, not the row's:
   it lines up with the inputs above it, between the grips on the left and
   the drop buttons on the right, which the list declares as
   `--row-lead` and `--row-tail`. */
.add-bar--row {
  width: auto;
  margin-left: var(--row-lead, 0);
  margin-right: var(--row-tail, 0);
  padding: 0.3rem 0.6rem;
  font-weight: 500;
  border-width: 1px;
}
.empty {
  padding: 2.5rem 1rem;
  text-align: center;
  color: var(--muted);
}

/* ---- badges & marks -------------------------------------------------- */

.badge {
  display: inline-block;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  border: 1px solid transparent;
  white-space: nowrap;
}
/* The two open states: an Anfrage is quiet (nothing owed to a customer
   yet), an offer out carries the blue that says "waiting on an answer". */
.badge--inquiry {
  background: var(--panel-2);
  color: var(--muted);
  border-color: var(--hairline);
}
.badge--offer_created {
  background: var(--accent-soft);
  color: var(--accent);
}
.badge--ordered {
  background: var(--ok-soft);
  color: var(--ok);
}
.badge--completed {
  background: var(--ink);
  color: var(--panel);
}
.badge--cancelled {
  background: var(--stop-soft);
  color: var(--stop);
}

/* Where an invoice stands: open waits on the customer, so it is the blue
   of an answer awaited; settled is done; overdue is the stop red. */
.badge--open {
  background: var(--accent-soft);
  color: var(--accent);
}
.badge--settled {
  background: var(--ok-soft);
  color: var(--ok);
}
.badge--late {
  background: var(--stop-soft);
  color: var(--stop);
}

/* inferred-from-legacy marker: the one place amber appears — a quiet dashed
   underline on the inferred value, not a loud tag. */
.is-inferred {
  border-bottom: 1px dashed var(--legacy);
  text-underline-offset: 2px;
  cursor: help;
}

.status-row {
  margin-top: 0.9rem;
}

.legend {
  margin: 0.8rem 0 0;
  font-size: 0.78rem;
  color: var(--muted);
}

/* ---- detail: project header ------------------------------------------ */

/* The intake workspace's way back to the results it was opened over
   (`intake/_workspace.html`): a real "back", since it swaps that list in
   again, so it keeps the arrow the trail deliberately does without. */
.workspace__back {
  display: inline-block;
  font-size: 0.85rem;
  margin-bottom: 1rem;
}

/* Where a record sits, from the list down (`_head.html`'s `trail`): blue,
   since every step names a place, and separated rather than arrowed — an
   arrow reads as the browser's back, which this is not. */
.trail {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  margin-bottom: 0.75rem;
  font-size: 0.85rem;
}
/* What narrowed the list rides in its step, the search cut short rather
   than pushing the trail onto a second line. */
.trail__step {
  display: inline-flex;
  align-items: baseline;
  max-width: 100%;
}
.trail__sep {
  margin: 0 0.4rem;
  color: var(--faint);
}
.trail__search {
  max-width: 18rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.trail__between {
  margin: 0 0.45rem;
  color: var(--faint);
}

.project-head {
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 1.25rem 1.5rem;
  margin-bottom: 2rem;
}
.project-head__eyebrow {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--faint);
}
.project-head__number {
  font-family: var(--font-mono);
  font-size: 2rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  margin: 0.1rem 0;
}
/* A Partei leads with its name where a Projekt leads with its number, so
   the same header takes prose rather than the mono numeral treatment. */
.project-head__title {
  font-size: 1.6rem;
  font-weight: 600;
  line-height: 1.2;
  margin: 0.1rem 0;
}
.project-head__site {
  color: var(--muted);
}
/* A fact about the title that is no state of the record — a Vorlage's
   language — rides beside it, quiet, rather than as a badge beside the
   head's button. */
.project-head__aside {
  margin-left: 0.4rem;
  font-size: 1rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--muted);
}
/* Everything tag-like about the record in one row under its name, status
   first — never in a column beside it, where it would compete with the
   corner's button (`templates/_head.html`). */
.project-head__tags {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  margin-top: 0.6rem;
}
/* What is done with the record as a whole, at the end of the tag row:
   right-aligned under the editor's corner, so the head's acts are in one
   column rather than in a band at the foot of the sheet. */
.project-head__acts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-left: auto;
}
.project-head__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: 0.9rem;
  row-gap: 0.2rem;
  margin-top: 0.6rem;
  font-size: 0.85rem;
  color: var(--muted);
}
/* A record read as a cover sheet: the rest of its facts under the head's
   own rule, so the head and the facts are one panel (`templates/_head.html`). */
.project-head__body {
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--hairline);
}
/* The status track stands where a badge would, so it takes the tag row's
   spacing rather than its own. */
.project-head__tags > .track,
.project-head__tags > .status-row {
  margin: 0 0.6rem 0 0;
}
.cover__desc {
  margin: 0 0 0.6rem;
  font-weight: 600;
}
/* Who is on the job beside how far it has got, and the notes beside both:
   columns of one width, so the sheet reads as one form rather than three
   lists. Wrapping rather than a grid, so a column that drops to its own
   line takes the whole of it instead of one cell of two. */
.cover {
  display: flex;
  flex-wrap: wrap;
  gap: 0.8rem 3rem;
  align-items: flex-start;
}
.cover > * {
  flex: 1 1 min(100%, 22rem);
  min-width: 0;
}
.cover__facts {
  grid-template-columns: 9rem minmax(0, 1fr);
  row-gap: 0.3rem;
  column-gap: 0.5rem;
}
.cover__facts dt {
  font-size: 0.85rem;
}
.cover__sep {
  color: var(--faint);
}
.cover__more {
  margin-top: 0.9rem;
}
/* What a column of a cover sheet holds, said as its facts' labels are —
   quiet and at their size — rather than as a section caption, since the
   sheet is one record and not a stack of sections. */
.cover__label {
  display: block;
  margin-bottom: 0.3rem;
  font-size: 0.85rem;
  color: var(--muted);
}

/* An article's cover (`articles/detail.html`): what it costs leads, as the
   one large figure on the sheet, because that is what the Lagerliste is
   opened for; who it is bought from stands beside it. */
.price-now p {
  margin: 0;
}
.price-now__figure {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.2rem 0.5rem;
}
.price-now__figure > .num {
  font-size: 1.6rem;
  font-weight: 600;
  line-height: 1.2;
}
.price-now__unit {
  color: var(--muted);
}
.price-now__as-of,
.price-now__none {
  margin-top: 0.2rem;
  font-size: 0.85rem;
  color: var(--muted);
}
/* One line per supplier, the columns shared through `subgrid` so the
   prices and dates stand under each other however long a name runs. */
.suppliers__list {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto auto;
  column-gap: 1rem;
  row-gap: 0.25rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.suppliers__row {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
  align-items: baseline;
}
.suppliers__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.suppliers__price {
  text-align: right;
}
.suppliers__date,
.suppliers__count {
  font-size: 0.85rem;
  color: var(--muted);
}
.suppliers__count {
  text-align: right;
}
/* Too narrow for a name beside three figures: the name takes its own line
   and the figures run under it, rather than the name cut to a letter. */
@media (max-width: 40rem) {
  .suppliers__list {
    grid-template-columns: auto auto minmax(0, 1fr);
  }
  .suppliers__name {
    grid-column: 1 / -1;
  }
  .suppliers__row + .suppliers__row {
    margin-top: 0.3rem;
  }
}
/* The article's own facts run under both columns, one label column wide,
   so a note of several lines has the sheet's width to break in. */
.facts.article-facts {
  margin-top: 1rem;
}
.article-facts__notes {
  white-space: pre-line;
}
.article-dispute {
  margin-top: 1rem;
}
/* Who a price is from and what is noted about it share a line, the picker
   the wider of the two: a party's card is longer than a note. */
@media (min-width: 40rem) {
  .editor__row.price-add__who {
    grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
  }
  /* Unless the supplier is being created: then what is said about it
     needs the line's whole width, and the note goes under it. */
  .editor__row.price-add__who:has(.party-new) {
    grid-template-columns: minmax(0, 1fr);
  }
}
/* A section with nothing in it says so on its heading's line, quietly,
   rather than as an empty panel the height of a full one: on a record read
   every day, an empty section is the normal case, not news. */
.section__none {
  font-size: 0.85rem;
  color: var(--muted);
}
/* A line of fact about a section, between its head and its panel: a
   price still awaited from a Preisanfrage above the price history. */
.section__note {
  margin: 0 0 0.5rem;
  font-size: 0.85rem;
  color: var(--muted);
}
/* The older rows of a list that shows its newest few (`articles/detail.html`):
   the toggle is a line of the panel itself, ruled off like a row, and says
   what it does in both states. Open, the older rows follow it under the
   same columns. */
.older__toggle {
  display: flex;
  width: 100%;
  padding: 0.6rem 1rem;
  border-top: 1px solid var(--hairline);
}
.older[open] > .older__toggle {
  border-bottom: 1px solid var(--hairline);
}
.older__hide,
.older[open] .older__show {
  display: none;
}
.older[open] .older__hide {
  display: inline;
}
/* Four short fields that read as one line — a price, its basis, its date,
   its quantity — kept on one where there is room for four. */
@media (min-width: 40rem) {
  .editor__row.editor__row--four {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}
/* What a party created from a pick may say about itself, under the pick:
   part of the row it belongs to, so no frame of its own — a rule above it
   and a quiet caption. */
.party-new {
  margin: 0.6rem 0 0;
  padding: 0.6rem 0 0;
  border: 0;
  border-top: 1px dashed var(--hairline);
  min-width: 0;
}
.party-new__legend {
  padding: 0;
  margin-bottom: 0.4rem;
  font-size: 0.8rem;
  color: var(--muted);
}
/* The picker's column is narrow, so a row keeps its fields side by side
   at a smaller width than a form's rows do, and wraps only on a phone. */
.party-new > .editor__row {
  grid-template-columns: repeat(auto-fit, minmax(6.5rem, 1fr));
}
.party-new > .editor__row + * {
  margin-top: 0.5rem;
}
.party-new__legend {
  display: block;
}
/* A record's status chosen in its head stands where its badge would. */
.status-seg {
  display: contents;
}
/* The Projekt a Vorgang's cover hangs under (`cases/detail.html`): the
   rail's grey along its top edge, the second panel tone for its ground, and
   no gap or rounding where the two meet, so the band reads as what the
   cover is attached to and not as a second card. Its number leads, set as
   the Projekt's own page sets it but a size down, since the page is the
   Vorgang's. */
.project-head--parent {
  margin-bottom: 0;
  padding-top: 0.85rem;
  padding-bottom: 0.85rem;
  background: var(--panel-2);
  border-top: 3px solid var(--rail);
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
  box-shadow: none;
}
.project-head--parent + .project-head {
  border-top: 0;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}
.band__line {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: 0.9rem;
  row-gap: 0.1rem;
  /* Clear of the editor's button, which sits level with this line. */
  min-height: 2rem;
  padding-right: 12rem;
}
/* The cover's eyebrow, so "Projekt" and "Vorgang" read as one pair. */
.band__label {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--faint);
}
/* The eyebrow on the title's line (`record_head`'s `label_beside`), in the
   band's rhythm: the same top padding and line height, so the editor's
   button sits level with the title as the band's does with its number. */
.project-head--beside {
  padding-top: 0.85rem;
}
.project-head__line {
  display: flex;
  align-items: baseline;
  column-gap: 0.9rem;
  min-height: 2rem;
  padding-right: 12rem;
}
.project-head__line > .project-head__title,
.project-head__line > .titled {
  min-width: 0;
}
/* Too narrow to share a line with the button: the line drops under it. */
@media (max-width: 40rem) {
  .band__line,
  .project-head__line {
    padding-right: 0;
    padding-top: 2.5rem;
  }
}
.band__number {
  font-family: var(--font-mono);
  font-size: 1.35rem;
  font-weight: 600;
  letter-spacing: 0.01em;
}
.band__parties {
  margin-top: 0.5rem;
}
/* The other pursuits of the same site, so nobody offers it twice on
   different terms without having seen them: folded, its summary counting
   the unsettled ones; open, a list whose columns line up across rows. */
.band__siblings {
  margin-top: 0.5rem;
  font-size: 0.9rem;
}
.band__siblings > .fold {
  flex-wrap: wrap;
  gap: 0.35rem 0.5rem;
}
.band__siblings .siblings__list {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr) max-content;
  column-gap: 1rem;
  max-width: 44rem;
  margin-top: 0.6rem;
}
.band__siblings .sibling {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
}
.band__siblings .sibling__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.band__siblings .sibling .badge {
  justify-self: start;
}
.editor__note {
  margin: 0 0 0.6rem;
  color: var(--muted);
}
/* The old system's reference: present, and visibly not the name. */
.project-head__legacy {
  margin-left: auto;
  font-size: 0.78rem;
  color: var(--faint);
}

/* ---- detail: the rail ------------------------------------------------ */

.rail {
  position: relative;
  margin-left: 9px;
  padding-left: 2.25rem;
}
.rail::before {
  content: "";
  position: absolute;
  left: 0;
  top: 4px;
  bottom: 12px;
  width: 2px;
  background: var(--rail);
  border-radius: 2px;
}
/* single-Vorgang projects read as one thing: no rail chrome needed */
.rail--solo {
  margin-left: 0;
  padding-left: 0;
}
.rail--solo::before {
  display: none;
}
.rail--solo .carriage__joint {
  display: none;
}

.carriage {
  position: relative;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 1.1rem 1.25rem;
  margin-bottom: 1.5rem;
}
/* The joint where a carriage meets the rail. A marker, not a label, and
   centred on both lines it joins. The rail runs at the rail element's own
   left edge, which is this carriage's `-2.25rem` of padding back; the
   connector's middle is `1rem + 11px` down. An even dot halved against a
   2px line lands a pixel over, so the offset is the padding itself rather
   than the arithmetic centre. */
.carriage__joint {
  position: absolute;
  left: calc(-2.25rem);
  top: calc(1rem + 5px);
  transform: translateX(-50%);
  width: 12px;
  height: 12px;
  background: var(--panel);
  border: 2px solid var(--accent);
  border-radius: 50%;
}
/* Connector from the joint to the carriage: it starts where the dot ends
   (its centre plus 6px) and runs to just short of the card. A line that
   stops early reads as two marks rather than as one joint. */
.carriage::before {
  content: "";
  position: absolute;
  left: calc(-2.25rem + 6px);
  top: calc(1rem + 10px);
  width: calc(2.25rem - 8px);
  height: 2px;
  background: var(--rail);
}
.rail--solo .carriage::before {
  display: none;
}

.carriage__head {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  flex-wrap: wrap;
}
.carriage__title {
  font-weight: 600;
  font-size: 1.05rem;
}
.carriage__meta {
  color: var(--muted);
  font-size: 0.85rem;
}
.carriage__kurzz {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--faint);
  margin-left: auto;
}
.carriage__desc {
  margin: 0.5rem 0 0;
  color: var(--muted);
}
.carriage__date {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  color: var(--faint);
}
/* The carriage opens on its own page from its name, and the link stretches
   over the region above the disclosure so the card itself is the target.
   `<a>` may not contain interactive content, so the disclosure cannot be
   inside it — it is lifted above the overlay instead, further down. */
.carriage__body {
  position: relative;
}
.carriage__link {
  display: inline-flex;
  align-items: baseline;
  gap: 0.5rem;
  color: inherit;
  text-decoration: none;
}
/* Out to the card's own padding, so the target has no dead border. The
   insets mirror `.carriage`'s padding; the bottom stops at the body. */
.carriage__link::after {
  content: "";
  position: absolute;
  inset: -1.1rem -1.25rem 0;
}
.carriage__body:hover .carriage__title,
.carriage__link:focus-visible .carriage__title {
  text-decoration: underline;
}
/* The number is part of the name, not a tag beside it: same size and
   weight, set apart by shape alone. Colour would read as a second link
   now that the whole region is one. */
.carriage__number {
  font-family: var(--font-mono);
  font-size: 1.05rem;
  font-weight: 600;
  color: inherit;
}
/* Above the overlay, and visibly its own strip: the one control on the
   card that does something other than open it. */
.carriage__more {
  position: relative;
  z-index: 1;
  margin-top: 0.75rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--hairline);
}
/* ---- the other Vorgänge under one project number --------------------- */

.siblings__list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.sibling {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  padding: 0.35rem 0;
  border-bottom: 1px solid var(--hairline);
}
.sibling:last-child {
  border-bottom: none;
}
.sibling.is-current {
  font-weight: 600;
}
.sibling__seq {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--muted);
  min-width: 1.5rem;
}
.sibling__kurzz {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--faint);
  margin-left: auto;
}

/* ---- status track (echoes the rail) ---------------------------------- */

.track {
  display: flex;
  flex-wrap: wrap;
  row-gap: 0.3rem;
  list-style: none;
  margin: 0.9rem 0 0;
  padding: 0;
  gap: 0;
}
.track__stage {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding-right: 1.1rem;
  position: relative;
  color: var(--faint);
  font-size: 0.78rem;
  white-space: nowrap;
}
.track__stage:not(:last-child)::after {
  content: "";
  width: 1.1rem;
  height: 2px;
  background: var(--hairline);
  margin-left: 0.4rem;
}
.track__dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  border: 2px solid var(--hairline);
  background: var(--panel);
}
.track__stage.is-done {
  color: var(--muted);
}
.track__stage.is-done .track__dot {
  background: var(--rail);
  border-color: var(--rail);
}
.track__stage.is-done:not(:last-child)::after {
  background: var(--rail);
}
/* The current stage wears its status's colour, the same one the status
   badge wears in the lists (`.badge--<status>`), so a Vorgang reads alike on
   its own page and in a table. Only the current stage: passed ones stay
   neutral, or the rail is a row of colours that say nothing. */
.track__stage {
  --stage-ink: var(--accent);
  --stage-soft: var(--accent-soft);
  --stage-halo: var(--accent-soft);
  --stage-text: var(--stage-ink);
}
.track__stage--inquiry {
  --stage-ink: var(--muted);
  --stage-soft: var(--panel-2);
  --stage-halo: var(--hairline);
}
.track__stage--ordered {
  --stage-ink: var(--ok);
  --stage-soft: var(--ok-soft);
  --stage-halo: var(--ok-soft);
}
.track__stage--completed {
  --stage-ink: var(--ink);
  --stage-soft: var(--ink);
  --stage-halo: var(--hairline);
  --stage-text: var(--panel);
}
.track__stage.is-current {
  color: var(--stage-ink);
  font-weight: 600;
}
.track__stage.is-current .track__dot {
  background: var(--stage-ink);
  border-color: var(--stage-ink);
  box-shadow: 0 0 0 3px var(--stage-halo);
}
/* For whoever may edit it, the rail is a control: every stage a bordered
   button, as acts are, that asks before it moves the Vorgang there
   (`_case_parts.track`), and the current one boxed in its status colour like a
   chosen segment. A shade larger than the read-only rail, which is a
   caption; the connectors stay, so it still reads as a sequence. */
.track--editable {
  align-items: center;
}
.track--editable .track__stage {
  font-size: 0.85rem;
  padding-right: 0.6rem;
}
.track--editable .track__stage:not(:last-child)::after {
  width: 0.8rem;
  margin-left: 0.6rem;
}
.track__step {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.25rem 0.6rem;
  font: inherit;
  color: inherit;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
}
button.track__step {
  cursor: pointer;
}
/* A hover outlines the stage in its status's colour and leaves the fill to
   the current one, so pointing at a stage previews where the Vorgang would
   go without looking like having chosen it. */
button.track__step:hover {
  color: var(--stage-ink);
  border-color: var(--stage-ink);
}
button.track__step:hover .track__dot {
  border-color: var(--stage-ink);
}
.track__step--current {
  color: var(--stage-text);
  border-color: var(--stage-ink);
  background: var(--stage-soft);
}
.track__stage.is-current .track__step--current .track__dot {
  background: var(--stage-text);
  border-color: var(--stage-text);
  box-shadow: none;
}
/* The fork: "abgesagt" beside "beendet" behind a slash rather than a
   connector, as the other way a pursuit ends. Red like every act that ends
   something, filled once it is the status. */
.track__stage:has(+ .track__stage--fork)::after {
  display: none;
}
.track__stage--fork::before {
  content: "/";
  margin-right: 0.6rem;
  color: var(--faint);
}
/* At rest it looks like the stages beside it — an option, not an alarm,
   told apart by its cross — and turns red only on the way to being
   chosen. */
button.track__step--cancel:hover {
  color: var(--stop);
  border-color: var(--stop);
}
.track__cross {
  flex: none;
}
.track__step--cancelled {
  color: var(--stop);
  border-color: var(--stop);
  background: var(--stop-soft);
}
button.track__step--cancelled:hover {
  color: var(--stop);
  border-color: var(--stop);
}
.track__reason {
  margin-left: 0.5rem;
  color: var(--muted);
}
/* A lost pursuit's rail is a way back, not a position. */
.track--off .track__stage {
  color: var(--faint);
}

/* A question before an act, over the page: what it asks, then the way
   back and the act, right-aligned like a dialog's. */
.ask {
  width: min(26rem, calc(100vw - 2rem));
  padding: 1rem 1.1rem;
  color: var(--ink);
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: 0 8px 28px rgb(0 0 0 / 0.18);
}
.ask::backdrop {
  background: rgb(0 0 0 / 0.15);
}
.ask > form,
.ask > .ask__body {
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
}
.ask__question {
  margin: 0;
  font-weight: 600;
}
.ask__note {
  margin: 0;
  font-size: 0.85rem;
  color: var(--muted);
}
.ask__acts {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
}

/* ---- carriage sub-sections ------------------------------------------- */

.section {
  margin-top: 1.1rem;
}
/* A section's label with what acts on the section as a whole at its
   right — the place for an action on part of a record, where the head's
   corner is the record's own editor. */
.section__head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.4rem;
}
.section__head > .section__label {
  margin: 0;
}
.section__head > .btn,
.section__head > form {
  margin-left: auto;
}
.section__label {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--faint);
  margin-bottom: 0.4rem;
}

/* A Vorgang's notes, a column of its cover sheet: the text as it was
   written, line breaks kept, and who wrote it when beneath it, quiet.
   Where there are any, the column is a sheet of its own clipped to the
   cover — tinted, so that notes exist is seen before anything is read;
   without any it is only its label and the bar that adds one. */
.notes--some {
  padding: 0.8rem 1rem 0.9rem;
  background: var(--panel-2);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
}
.notes__head {
  display: flex;
  align-items: baseline;
  gap: 0.45rem;
  margin: 0 0 0.6rem;
  font-size: 0.85rem;
  font-weight: 600;
}
.notes__count {
  font-size: 0.78rem;
  font-weight: 400;
  color: var(--faint);
}
.notes__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.7rem;
}
.notes__item + .notes__item {
  padding-top: 0.7rem;
  border-top: 1px solid var(--hairline);
}
/* Short until the fold under the list is opened, which shows the older
   notes and every note in full. The cut is a height on the wrapper rather
   than a line clamp on the text, because a clamped box would stand beside
   the corner instead of flowing round it. */
.notes__item--more {
  display: none;
}
.notes__text {
  display: flow-root;
  max-height: 4lh;
  /* Cut at the foot only: the cross in the corner keeps its hover border
     and focus ring. */
  clip-path: inset(-0.4rem -0.4rem 0 -0.4rem);
}
.notes__body {
  margin: 0;
  white-space: pre-line;
  overflow-wrap: anywhere;
}
.notes:has(> .notes__more[open]) .notes__item--more {
  display: list-item;
}
.notes:has(> .notes__more[open]) .notes__text {
  max-height: none;
  clip-path: none;
}
.notes__more {
  margin-top: 0.6rem;
}
/* Who and when, and the cross that deletes it, in the note's top corner:
   one body line high, so the text beside it keeps its rhythm. */
.notes__meta {
  float: right;
  display: flex;
  align-items: center;
  gap: 0.3rem;
  margin-left: 0.8rem;
  font-size: 0.78rem;
  color: var(--faint);
}
.notes__meta .iconbtn {
  margin-right: -0.3rem;
}
.notes > .notes__add {
  margin-top: 0;
}
.notes > .notes__list + .notes__add,
.notes > .notes__more + .notes__add {
  margin-top: 0.8rem;
}
/* On the tinted sheet the open form is the paper, not the tint again. */
.notes--some .notes__add > .editor__body {
  background: var(--panel);
}

/* Label/value pairs of a record. Same shape as the mail-header list, which
   is the other place a fixed set of facts is read rather than scanned. */
.facts {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.2rem 0.8rem;
  margin: 0;
}
.facts dt {
  color: var(--muted);
}
.facts dd {
  margin: 0;
  overflow-wrap: anywhere;
}

/* A unit of measure. The raw variant marks a legacy spelling that could not
   be read as a unit — usually because it states a quantity — so it is set
   apart rather than passed off as normalised. */
.unit {
  white-space: nowrap;
}
.unit--raw {
  border-bottom: 1px dotted var(--faint);
  cursor: help;
}

.parties {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.3rem;
}
.party {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
}
.party__role {
  min-width: 9rem;
  color: var(--muted);
  font-size: 0.85rem;
}
.party__name {
  font-weight: 500;
}
.tag {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--faint);
  border: 1px solid var(--hairline);
  border-radius: 2px;
  padding: 0.02rem 0.28rem;
}

.subtable {
  font-size: 0.88rem;
}
.subtable th {
  padding: 0.3rem 0.75rem 0.3rem 0;
  font-size: 0.68rem;
}
.subtable td {
  padding: 0.3rem 0.75rem 0.3rem 0;
  border-bottom: 1px solid var(--panel-2);
}

.hours {
  font-family: var(--font-mono);
}
.hours strong {
  font-size: 1.05rem;
}
.hours__count {
  color: var(--muted);
  font-family: var(--font-body);
  font-size: 0.85rem;
}
.muted {
  color: var(--muted);
}

@media (max-width: 640px) {
  .search {
    max-width: 45%;
  }
  .party__role {
    min-width: 6.5rem;
  }
  .project-head__number {
    font-size: 1.6rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  * {
    scroll-behavior: auto;
  }
}

/* auth */
.btn--primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  text-align: center;
}
.btn--primary:hover {
  filter: brightness(1.08);
}


.login {
  max-width: 22rem;
  margin: 3.5rem auto;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
.login__title {
  font-size: 1.3rem;
  font-weight: 600;
}
.login__error {
  padding: 0.5rem 0.75rem;
  background: var(--legacy-soft);
  border-left: 3px solid var(--stop);
  color: var(--stop);
  font-size: 0.9rem;
}
.login__oidc {
  display: block;
}
.login__sep {
  text-align: center;
  color: var(--faint);
  font-size: 0.8rem;
}
.login__form {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}
.field {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
/* Block, so the line it occupies is its own font's and nothing else's. As
   an inline span it shares a line box with the strut of the field around
   it, which is set at the body size — the label then sits on a line taller
   than it looks, and anything measuring that line from the label's own
   metrics (`--control-line`) comes out short. */
.field__label {
  display: block;
  font-size: 0.8rem;
  color: var(--muted);
}
/* A label with its "i": the mark a step after the label's last word, on
   the label's own line, so the row's control line does not move. */
/* A field that must hold a value (`_form.html`, `required_mark`): the
   mark in ink beside the grey label, so the few that are required stand
   out of a column of optional ones without a colour of their own. */
.field__req {
  margin-left: 0.15em;
  color: var(--ink);
  font-weight: 600;
}
/* A value shown where a control would stand — fixed, or set elsewhere —
   on the control line at the control's height. */
.field__value {
  display: flex;
  align-items: center;
  min-height: 2rem;
  min-width: 0;
}
.field__label--info {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  line-height: 1.45;
}
.field__label--info .info__mark {
  width: 0.95rem;
  height: 0.95rem;
  font-size: 0.65rem;
}
.field__input {
  font: inherit;
  /* A control may be narrower than its content. Without this a <select>
     keeps the width of its longest option — a customer name with its role
     after it — and runs over the field beside it, because a grid item's
     automatic minimum is its content rather than zero. */
  min-width: 0;
  max-width: 100%;
  padding: 0.3rem 0.55rem;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--ink);
}
.field__input::placeholder {
  color: var(--faint);
}
/* One height for both shapes. A select is sized from its own metrics and
   comes out taller than an input carrying the same padding, and a row
   aligns its fields at the bottom, so the difference reads as a step
   between two boxes that should look like one control repeated. */
input.field__input,
select.field__input {
  height: 2rem;
}
.field__input:focus {
  outline: none;
  border-color: var(--accent);
}

/* intake */
.finder__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}
/* The wrapper takes the hover a disabled button does not. */
.finder__cta-wait {
  position: relative;
  display: inline-flex;
  cursor: help;
}
/* Why the act waits, under it the moment the pointer is on it: in the
   info mark's box, but without its click, since there is nothing to keep
   open. Hidden rather than absent, so the button is described by it. */
.finder__cta-why {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 20;
  width: max-content;
  max-width: min(22rem, 80vw);
  padding: 0.5rem 0.7rem;
  font-size: 0.8rem;
  line-height: 1.45;
  color: var(--ink);
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: 0 10px 30px rgba(28, 27, 25, 0.14);
  visibility: hidden;
}
.finder__cta-wait:hover > .finder__cta-why {
  visibility: visible;
}
.rail__add {
  margin-top: 1rem;
}
.intake__title {
  font-size: 1.3rem;
  font-weight: 600;
  margin: 0.5rem 0 1rem;
}
/* The intake form and, beside it, what it may already be. Side by side
   while the page is wide enough for both to be read at once; below that the
   matches follow the form. The panel stays in view while a long form
   scrolls, since it answers whatever field is being typed in. */
.intake-layout {
  display: grid;
  grid-template-columns: minmax(0, 44rem) minmax(16rem, 1fr);
  gap: 1.5rem;
  align-items: start;
}
@media (max-width: 62rem) {
  .intake-layout {
    grid-template-columns: minmax(0, 1fr);
  }
}
@media (min-width: 62.01rem) {
  .matches {
    position: sticky;
    top: 1rem;
  }
}
.matches {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}
.matches__title {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--muted);
}
.matches__idle {
  margin: 0;
  color: var(--faint);
  font-size: 0.9rem;
}
/* What the cards could not use, and the way to the complete answer. */
.matches__foot {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  font-size: 0.85rem;
  color: var(--muted);
}
.matches__foot p {
  margin: 0;
}
.matches__list {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.match {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: 0.65rem 0.75rem;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  font-size: 0.88rem;
}
/* Close enough that a new Projekt beside it is asked about: the card says
   so before the question does. */
.match--strong {
  border-color: var(--accent);
  box-shadow: inset 3px 0 0 var(--accent);
}
.match__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.25rem 0.5rem;
}
.match__number {
  font-weight: 600;
}
.match__name {
  min-width: 0;
  font-weight: 600;
  overflow-wrap: anywhere;
}
.match__mark {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--accent);
}
.match__site {
  margin: 0;
}
.match__why,
.match__cases {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.match__why {
  color: var(--muted);
}
.match__signal {
  font-weight: 600;
  color: var(--ink);
}
.match__cases > li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.2rem 0.45rem;
}
.match__use {
  align-self: flex-start;
  margin-top: 0.2rem;
}
.intake__submit {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.5rem;
}
.intake__changed {
  margin: 0;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--accent);
}
.ask__list {
  margin: 0;
  padding-left: 1.1rem;
  font-size: 0.9rem;
}
.intake {
  max-width: 44rem;
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}
.intake__target,
.intake__fields {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: 1rem;
}
.intake__fields legend {
  font-size: 0.85rem;
  color: var(--muted);
  padding: 0 0.4rem;
}
.intake__site {
  display: grid;
  grid-template-columns:
    minmax(0, 2fr) minmax(0, 0.6fr) minmax(0, 1fr) minmax(0, 2fr);
  gap: 0.75rem;
}
@media (max-width: 40rem) {
  .intake__site {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 2fr);
  }
  .intake__site > :first-child {
    grid-column: 1 / -1;
  }
}
.candidates {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  list-style: none;
  margin: 0;
  padding: 0;
}
.candidate {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
}
.candidate__num {
  font-weight: 600;
}
.candidate__desc {
  flex: 1;
  min-width: 0;
  font-size: 0.9rem;
}
.candidate .btn {
  flex: none;
}
.intake__project {
  margin: 0;
  font-weight: 600;
}
.intake__target > p {
  margin: 0;
}
.intake__siblings {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-size: 0.9rem;
}
.intake__siblings .badge {
  margin-left: 0.4rem;
}
/* A secondary action, de-emphasized next to the one beside it. */
.btn--ghost {
  background: transparent;
  color: var(--muted);
}
.party-picker {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
/* A search with nothing typed has no matches below it, and no gap. */
.party-picker > div:empty {
  display: none;
}
.party-opts {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  list-style: none;
  margin: 0;
  padding: 0;
}
/* A match: its radio, what tells it from a namesake, and the way to its
   own page. The radio sits on the name's line, whatever wraps below. */
.party-opt {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0.35rem 0.4rem;
  font-size: 0.9rem;
}
.party-opt:hover {
  background: var(--accent-soft);
  border-radius: var(--radius);
}
.party-opt__body {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  min-width: 0;
  cursor: pointer;
}
.party-card__name {
  font-weight: 500;
  overflow-wrap: anywhere;
}
.party-card__details {
  font-size: 0.82rem;
  color: var(--muted);
  overflow-wrap: anywhere;
}
.party-opt__open {
  font-size: 0.82rem;
  white-space: nowrap;
}
.party-opts__count {
  padding: 0 0.4rem;
  font-size: 0.8rem;
}
.party-opt__more {
  padding: 0.2rem 0.4rem;
}
/* The row that creates a party closes the list, set apart from the
   parties above it. */
.party-opt--new {
  display: flex;
  align-items: center;
  cursor: pointer;
  margin-top: 0.2rem;
  border-top: 1px dashed var(--hairline);
  padding-top: 0.5rem;
}
/* The party picker once settled: the pick as one line, its way back to
   the search at the end. */
.party-pick {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem;
  padding: 0.45rem 0.6rem;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  background: var(--panel-2);
}
.party-pick__name {
  font-weight: 600;
}
.party-pick__body {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 0.1rem;
  min-width: 0;
}
/* What the pick will do, as a quiet tag — not blue, which names a place. */
.party-pick__mark {
  font-size: 0.75rem;
  padding: 0.05rem 0.45rem;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  background: var(--panel);
  color: var(--ink);
}
.party-pick .btn {
  margin-left: auto;
}
/* Beteiligte: each row reads role, then party, so the column of roles
   down the left is the list's index. The matches open under the party,
   never under the role. */
.party-rows {
  --row-tail: calc(1.8rem + 0.5rem);
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}
.party-rows > legend {
  padding: 0;
  margin-bottom: 0.35rem;
}
.party-rows__inherited {
  margin: 0;
  font-size: 0.9rem;
}
/* A contract's tiers (`templates/sales_reps/_tier_row.html`): one line per
   tier, read as "from so much, this rate", with the column labels once
   above. The public rate is a column only once the contract is said to
   have its own; until then it is not drawn at all, rather than drawn
   empty. */
/* The figures of a version in use, disabled as one group and drawn as
   though no fieldset were there. */
.editor__locked {
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}
/* A contract's tiers beside its exclusions, split by a hairline as the
   editor's groups are split above and below. */
.contract-split {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 0 2rem;
  border-top: 1px solid var(--hairline);
  padding-top: 0.9rem;
}
/* Doubled to outrank `.editor__form + .editor__form`, which comes later. */
.contract-split > .editor__form.editor__form {
  border-top: none;
  padding-top: 0;
}
.contract-split > .editor__form + .editor__form {
  border-left: 1px solid var(--hairline);
  padding-left: 2rem;
}
/* The two switches of the tiers belong together, so they sit side by
   side at their own width rather than one per column of the grid. */
.editor__row.tiers__switches {
  grid-template-columns: repeat(2, max-content);
}
/* Wide enough for a switch with both its words, so none is cut. */
.exclusions > .editor__row {
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
}
@media (max-width: 56rem) {
  .contract-split {
    grid-template-columns: minmax(0, 1fr);
  }
  .contract-split > .editor__form.editor__form + .editor__form {
    border-left: none;
    padding-left: 0;
    border-top: 1px solid var(--hairline);
    padding-top: 0.9rem;
    margin-top: 0.9rem;
  }
}

/* Drawn as the app's tables are: a panel, a head row in the table head's
   type over a hairline, a lighter line between rows, the add bar as the
   row that closes them. A grid rather than a <table>, so the public column
   can be left out without a <col> keeping its width. */
.tiers__table {
  width: max-content;
  max-width: 100%;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.tiers__list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.tier {
  display: grid;
  grid-template-columns: 7.5rem 5.5rem 5.5rem 1.8rem;
  align-items: center;
  gap: 0.75rem;
  padding: 0.45rem 1rem;
  border-bottom: 1px solid var(--panel-2);
}
.tier > span {
  display: flex;
  align-items: center;
  gap: 0.35rem;
}
.tier .field__input {
  min-width: 0;
  text-align: right;
}
.tiers__add {
  padding: 0.5rem 1rem 0.6rem;
}
.tier__unit {
  color: var(--muted);
  font-size: 0.85rem;
}
/* The first tier's amount is fixed at zero: written where the box would
   stand, so the units line up under each other. */
.tier__fixed {
  flex: 1;
  padding-inline: 0.5rem;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.tier--head {
  padding-block: 0.6rem;
  border-bottom-color: var(--hairline);
}
.tier--head > span {
  display: block;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--faint);
}
.tier__drop {
  grid-column: -2;
  justify-self: center;
}
/* Without public rates the column is not drawn and the table closes up. */
.tiers:not(:has([name="public_rates"][value="own"]:checked)) .tier {
  grid-template-columns: 7.5rem 5.5rem 1.8rem;
}
.tiers:not(:has([name="public_rates"][value="own"]:checked)) .tier__public {
  display: none;
}
/* A job's own terms have no public rates (`cases/_commission_rep.html`). */
.tiers--plain .tier {
  grid-template-columns: 7.5rem 5.5rem 1.8rem;
}
.tiers__add > .add-bar--row {
  width: 100%;
  margin: 0;
}
/* The worked example as one sentence, the working under what it earns. */
.tier-example {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: baseline;
  gap: 0.15rem 0.6rem;
  margin-top: 0.5rem;
}
.tier-example__label {
  display: flex;
  align-items: baseline;
  gap: 0.3rem;
  font-size: 0.85rem;
  color: var(--muted);
}
.tier-example__result {
  display: grid;
  gap: 0.1rem;
}
.tier-example__result strong {
  font-variant-numeric: tabular-nums;
}
.tier-example__working {
  font-size: 0.8rem;
  color: var(--muted);
}
/* The Akustik flat rate is asked for only once the surface is excluded. */
.exclusions:not(:has([name="excludes_acoustic"][value="1"]:checked))
  .field:has([name="acoustic_flat_rate"]) {
  display: none;
}
@media (max-width: 40rem) {
  .tiers__table {
    width: auto;
  }
  .tier {
    grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) minmax(0, 1fr) 1.8rem;
    gap: 0.5rem;
    padding-inline: 0.6rem;
  }
  .tiers--plain .tier {
    grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) 1.8rem;
  }
}
/* The reps who earn on a Vorgang (`cases/_commission_rep.html`): a block
   each, split by a hairline, the rep and the switch heading it and the drop
   button at the head's end. */
.commission-reps {
  list-style: none;
  margin: 0;
  padding: 0;
}
.commission-rep + .commission-rep {
  border-top: 1px solid var(--hairline);
  padding-top: 0.9rem;
  margin-top: 0.9rem;
}
.editor__row.commission-rep__head {
  grid-template-columns: minmax(12rem, 22rem) max-content 1.8rem;
}
/* On the control line, as a button in a row is (`.editor__row`). */
.commission-rep__drop {
  grid-row: span 3;
  align-self: start;
  margin-top: calc(var(--control-line) + 0.4rem);
}
.commission-rep__contract {
  margin-top: 0.6rem;
}
.commission-rep .contract-split {
  margin-top: 0.9rem;
}
.commission-reps__add {
  margin-top: 0.9rem;
}
/* As on a contract: the Akustik flat rate only once the surface is out. */
.commission-rep:not(:has([name$="-excludes_acoustic"][value="1"]:checked))
  .field:has([name$="-acoustic_flat_rate"]) {
  display: none;
}

.party-rows__list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
}
.party-row {
  display: grid;
  grid-template-columns: 11rem minmax(0, 1fr) 1.8rem;
  align-items: start;
  gap: 0.5rem;
}
/* A settled pick stands on one line with the role beside it, so it takes
   an input's height rather than a card's. */
.party-row .party-pick {
  align-items: center;
  min-height: 2rem;
  padding-block: 0.1rem;
}
.party-row__drop {
  justify-self: center;
  margin-top: 0.3rem;
}
.party-rows:not([data-enhanced]) .party-row__drop {
  visibility: hidden;
}
/* The mark sits on the control the problem is in: the role, or a search
   left without a pick. */
.party-row--missing-role .party-row__role,
.party-row--duplicate-role .party-row__role,
.party-row--unpicked .party-picker > .field__input {
  border-color: var(--stop);
}
.party-row__problem {
  grid-column: 1 / -1;
  margin: 0;
  font-size: 0.85rem;
  color: var(--stop);
}
@media (max-width: 40rem) {
  /* The party takes the whole width under its role, and the bar with it. */
  .party-rows {
    --row-tail: 0;
  }
  .party-row {
    grid-template-columns: minmax(0, 1fr) 1.8rem;
  }
  .party-row .party-picker {
    grid-column: 1 / -1;
    grid-row: 2;
  }
}

/* --- E-Mail-Triage --------------------------------------------------- */

.dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.25rem;
  padding: 2.5rem 1rem;
  margin: 1rem 0;
  border: 2px dashed var(--rail);
  border-radius: var(--radius);
  background: var(--panel-2);
  cursor: pointer;
  transition: border-color 0.12s, background 0.12s;
}
.dropzone.is-over {
  border-color: var(--accent);
  background: var(--accent-soft);
}
.dropzone__hint {
  font-weight: 600;
}
.dropzone__sub,
.dropzone__note {
  font-size: 0.85rem;
  color: var(--muted);
}
/* Shown only when a drop produced no file — the alternative is a drop that
   silently does nothing, which users cannot report and we cannot see. */
.dropzone__status {
  margin-top: 0.6rem;
  max-width: 40rem;
  text-align: left;
}
.dropzone__status[hidden] {
  display: none;
}

/* A sentence the page says about itself, toned by what it means: plain is
   information, ok is an act that went through, warn is something to check
   before relying on what you see, error is something that did not happen.
   One tone per meaning, as with the controls. */
.banner {
  padding: 0.5rem 0.7rem;
  border-radius: var(--radius);
  border-left: 3px solid var(--rail);
  background: var(--panel-2);
  font-size: 0.9rem;
}
.banner--ok {
  border-left-color: var(--ok);
  background: var(--ok-soft);
}
.banner--warn {
  border-left-color: var(--legacy);
  background: var(--legacy-soft);
}
.banner--error {
  border-left-color: var(--stop);
  background: #f6e6e3;
}
/* A banner that asks: its sentence, then the one act it offers. */
.banner-ask {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

/* The notices an act leaves on the page it lands on (`web/flash.py`). */
.flashes {
  display: grid;
  gap: 0.4rem;
  margin-bottom: 1.25rem;
}
.flashes .banner {
  margin: 0;
}
.budget {
  font-size: 0.9rem;
  color: var(--muted);
}

.mail {
  margin: 1rem 0;
  padding: 1rem;
  background: var(--panel);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.mail__head {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding-bottom: 0.6rem;
  border-bottom: 1px solid var(--hairline);
}
.mail__subject {
  font-weight: 600;
}
.mail__progress,
.mail__verdict,
.mail__attachments {
  padding-top: 0.8rem;
}
.mail__status {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 500;
}

/* Findings are facts from the deterministic pass, not a provisional
   verdict — kept visually quiet so they read as context, not as an answer. */
.findings {
  margin: 0.2rem 0 0;
  padding-left: 1.1rem;
  font-size: 0.9rem;
  color: var(--muted);
}

.spinner {
  width: 0.8rem;
  height: 0.8rem;
  border: 2px solid var(--hairline);
  border-top-color: var(--accent);
  border-radius: 50%;
  display: inline-block;
  animation: spin 0.7s linear infinite;
}
@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}
@media (prefers-reduced-motion: reduce) {
  .spinner {
    animation: none;
  }
}

.verdict {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin: 0 0 0.3rem;
}
.verdict__why {
  margin: 0 0 0.6rem;
  color: var(--muted);
}
.chip--low {
  border-color: var(--legacy);
  color: var(--legacy);
}
.chip--medium,
.chip--high {
  border-color: var(--rail);
  color: var(--muted);
}
.small {
  font-size: 0.85rem;
}

.confirm {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--hairline);
}

.attachments {
  list-style: none;
  margin: 0.3rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
.attachment {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  flex-wrap: wrap;
  padding: 0.35rem 0.5rem;
  background: var(--panel-2);
  border-radius: var(--radius);
}
.attachment__name {
  font-family: var(--font-mono);
  font-size: 0.85rem;
}
.attachment__summary {
  margin: 0.3rem 0 0;
  font-size: 0.9rem;
  white-space: pre-wrap;
}
.att__action {
  margin-left: auto;
}
.att__confirm {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

/* --- Posteingang-Leiste ----------------------------------------------- */

/* Out of flow, so every screen centres the same column whether or not the
   rail is there. It hangs from the top of the shell — below whatever chrome
   this page has, tabs and strips included, at no fixed offset — and its
   surfaces stick once scrolled to, because the queue is what interrupts the
   operator and must survive scrolling. The column itself lets clicks
   through: only the panel or the strip is a target. */
.shell {
  position: relative;
}
.inbox {
  position: absolute;
  top: 1rem;
  right: 1rem;
  bottom: 1rem;
  z-index: 10;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.6rem;
  pointer-events: none;
}
/* Never shrunk to the column: on a short page the shell is shorter than the
   queue, and a squeezed panel would scroll by the few pixels it lost. Only
   the viewport caps it (`max-height` below). */
.inbox > * {
  position: sticky;
  top: 1rem;
  flex-shrink: 0;
  pointer-events: auto;
}
.inbox__panel {
  width: 340px;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  max-height: calc(100vh - 2rem);
  overflow-y: auto;
}
/* Three states, because two cannot express "nobody has said yet": `open` and
   `closed` are the operator's own choice and hold at any width, while `auto`
   (no cookie yet) lets the viewport answer — the panel only clears the list
   from 1900px up, and below that a queue that covers the table on first sight
   is the wrong default. */
.inbox--auto .inbox__panel {
  display: none;
}
@media (min-width: 1900px) {
  .inbox--auto .inbox__panel {
    display: flex;
  }
  .inbox--auto .inbox__stripform {
    display: none;
  }
}
/* A wide screen's column leaves a narrower gutter, so there the panel waits
   for a wider window before it takes the gutter by itself. */
@media (min-width: 1900px) and (max-width: 2299px) {
  .shell:has(> .page--wide) .inbox--auto .inbox__panel {
    display: none;
  }
  .shell:has(> .page--wide) .inbox--auto .inbox__stripform {
    display: block;
  }
}
/* Where the gutter cannot hold it, the panel is a drawer over the list rather
   than a column beside it — so it needs a surface of its own; in the gutter
   its cards carry their own. */
@media (max-width: 1899px) {
  .inbox__panel {
    padding: 0.75rem;
    background: var(--ground);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    box-shadow: 0 10px 40px rgba(28, 27, 25, 0.18);
  }
}
.inbox__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 2rem;
}
.inbox__title {
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--faint);
  margin: 0;
}
/* Sized as a control, not a glyph: a caret with the padding of a text run
   is a target nobody finds, and this is the only way back to the strip. */
.inbox__toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.3rem;
  height: 2rem;
  padding: 0 0.6rem;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  cursor: pointer;
  color: var(--muted);
  font-family: inherit;
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  line-height: 1;
}
.inbox__toggle:hover {
  color: var(--ink);
  border-color: var(--rail);
  background: var(--panel-2);
}
.inbox__toggle-icon {
  font-size: 0.95rem;
}

/* Collapsed: a strip, never a hidden panel — it stays a drop target and
   keeps the count in view. */
.inbox__strip {
  position: relative;
  width: 44px;
  min-height: 9rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.6rem;
  padding: 0.7rem 0;
  cursor: pointer;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  color: var(--muted);
}
.inbox__strip:hover {
  color: var(--ink);
  border-color: var(--rail);
}
.inbox__strip.is-over {
  border-color: var(--accent);
  background: var(--accent-soft);
}
.inbox__strip-label {
  writing-mode: vertical-rl;
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.inbox__badge {
  min-width: 1.35rem;
  padding: 0.1rem 0.3rem;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 600;
  color: #fff;
  background: var(--accent);
  border-radius: 999px;
  text-align: center;
}
/* A collapsed rail has no room for a sentence, and a failed drop must still
   say why — so the message floats off the strip instead of being dropped. */
.inbox__popover {
  position: absolute;
  right: 3.5rem;
  top: 0;
  width: 22rem;
  z-index: 5;
  box-shadow: var(--shadow);
}
.inbox__popover[hidden] {
  display: none;
}

.dropzone--inbox {
  margin: 0;
  padding: 1.4rem 0.8rem;
  text-align: center;
}
.dropzone--inbox .dropzone__hint {
  font-size: 0.9rem;
}

.inbox__queue {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.inbox__empty {
  margin: 0;
  font-size: 0.9rem;
  color: var(--faint);
}
.inbox__all {
  width: 100%;
}

.mailcard {
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.mailcard__open {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.25rem;
  width: 100%;
  padding: 0.6rem 0.7rem;
  border: 0;
  background: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.mailcard__open:hover {
  background: var(--accent-soft);
  border-radius: var(--radius);
}
.mailcard__subject {
  font-weight: 600;
  font-size: 0.9rem;
  line-height: 1.3;
}
.mailcard__from,
.mailcard__state {
  font-size: 0.8rem;
  color: var(--muted);
}
.mailcard__state {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-wrap: wrap;
}
.mailcard__state--failed {
  color: var(--stop);
}
.mailcard__class {
  color: var(--ink);
}

.navbadge {
  display: inline-block;
  min-width: 1.15rem;
  padding: 0 0.3rem;
  margin-left: 0.25rem;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 600;
  line-height: 1.35;
  color: #fff;
  background: var(--accent);
  border-radius: 999px;
  text-align: center;
}

/* A count that opens its list filtered sits beside its tab rather than in
   it, so the tab's underline stays under the tab. */
.topbar__slot,
.sectionbar__slot {
  display: inline-flex;
  align-items: stretch;
  gap: 0;
}
/* A dot beside a tab rather than a count (`nav.Section.dot`). */
.navdot {
  display: inline-block;
  margin-left: 0.35rem;
  vertical-align: middle;
}
.navbadge--link {
  align-self: center;
}
.navbadge--link:hover {
  text-decoration: none;
  opacity: 0.85;
}

/* --- Zuordnungs-Arbeitsfläche ----------------------------------------- */

.workspace__cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 18rem;
  gap: 1.25rem;
  align-items: start;
}
.workspace__context {
  padding: 0.9rem;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.proposed {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.2rem;
}
.proposed__number {
  font-family: var(--font-mono);
  font-size: 1.15rem;
  font-weight: 600;
}
.proposed__site {
  margin: 0 0 0.9rem;
  font-size: 0.9rem;
  color: var(--muted);
}
.proposed__cases {
  list-style: none;
  margin: 0.3rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-size: 0.9rem;
}
.proposed__cases li {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  padding: 0.25rem 0.35rem;
  border-radius: var(--radius);
}
/* The Vorgang the verdict points at — the one thing on this card the
   operator is being asked to agree with. */
.proposed__cases .is-proposed {
  background: var(--accent-soft);
  box-shadow: inset 2px 0 0 var(--accent);
}

/* No gutter left to stand in: the strip moves to the bottom corner, where it
   covers the end of the list instead of the header the operator is reading,
   and turns horizontal because a vertical label needs height the corner does
   not have. */
@media (max-width: 1259px) {
  .inbox {
    position: fixed;
    top: auto;
    bottom: 1rem;
  }
  .inbox > * {
    position: static;
  }
  .inbox__panel {
    width: min(340px, calc(100vw - 2rem));
    max-height: calc(100vh - 6rem);
  }
  .inbox__strip {
    width: auto;
    min-height: 0;
    padding: 0.45rem 0.8rem;
    flex-direction: row;
    justify-content: center;
  }
  .inbox__strip-label {
    writing-mode: horizontal-tb;
  }
}

@media (max-width: 1100px) {
  .workspace__cols {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* --- Kuhnle-Sync (nur bis zur Umstellung) -------------------------- */

.run__head {
  margin-bottom: 1.25rem;
}
.run__head h1 {
  margin-bottom: 0.2rem;
}
.run__status {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0 0 1rem;
}
/* Live pull progress: quiet, since it changes every couple of seconds and
   the operator is waiting on it rather than reading it. */
.run__progress {
  margin: -0.6rem 0 1rem;
  font-size: 0.9rem;
  color: var(--muted);
}
.run__current {
  margin-left: 0.4rem;
  font-size: 0.85rem;
  color: var(--ink);
}
.run__heading {
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--faint);
  margin: 1.5rem 0 0.4rem;
}
.runtable {
  border-collapse: collapse;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  min-width: min(30rem, 100%);
}
.runtable th {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--faint);
  text-align: left;
  padding: 0.5rem 0.8rem;
  border-bottom: 1px solid var(--hairline);
}
.runtable td {
  padding: 0.45rem 0.8rem;
  border-bottom: 1px solid var(--panel-2);
}
.runtable tr:last-child td {
  border-bottom: 0;
}
/* Rows the source stopped carrying. Amber, the same mark legacy-inferred
   data wears elsewhere: it means "needs a human", not "broken". */
.is-marked {
  color: var(--legacy);
  font-weight: 600;
}

/* ---- detail: edit panels --------------------------------------------- */

/* Folded away by default: the detail page is read far more often than it is
   edited, so the forms stay out of the way until asked for. */
.editor {
  margin-top: 1rem;
  font-size: 0.9rem;
}
/* The toggle wears a button or an add bar (see the action grammar above);
   this only spaces it from the form it opens. */
.editor[open] > .editor__toggle {
  margin-bottom: 0.7rem;
}
/* Editing the record a head names: the button sits in the head's top
   corner, level with the eyebrow, on every detail screen alike. The
   eyebrow line is raised to the button's height so a long title below
   never runs under it. */
.project-head {
  position: relative;
}
.project-head:has(> .editor > .btn) > .project-head__eyebrow {
  display: flex;
  align-items: center;
  min-height: 2rem;
  margin-top: -0.4rem;
}
/* And the corner is kept clear below it too: a spacer floated where the
   button stands, reaching into the title's first line, so a long title
   wraps short of the button instead of running on just beneath it. Wrapped
   rather than cut, because the end of a name is often what tells it from
   its neighbours — an article's dimensions, a site's street. */
.project-head:has(> .editor > .btn)::before {
  content: "";
  float: right;
  width: 9rem;
  height: 2.4rem;
}
/* On a phone the corner is most of the line: the spacer would leave a word
   alone on it, and the title starts below the button there anyway. */
@media (max-width: 40rem) {
  .project-head:has(> .editor > .btn)::before {
    content: none;
  }
}
.project-head > .editor > .editor__toggle {
  position: absolute;
  top: 0.85rem;
  right: 1.5rem;
}
.project-head > .editor:not([open]) {
  margin-top: 0;
}
/* A record read as a cover sheet is edited in its place: while its editor
   is open the form stands where the sheet was, at the head's full width,
   so no fact is on the screen twice and none is hunted for below. What
   the sheet shows and the form does not set — the derived figures, the
   notes, the acts — waits until the form is saved or closed. */
.project-head:has(> .editor[open]) > .project-head__body {
  display: none;
}
/* A contact row is its own summary: the whole line is the toggle, so the
   list reads as contacts rather than as a column of "edit" buttons. The
   pencil after it says the line opens. */
.editor--inline {
  margin-top: 0;
}
.editor--inline > .editor__toggle {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  column-gap: 0.4rem;
  list-style: none;
  font-size: 0.9rem;
  cursor: pointer;
}
.editor--inline > .editor__toggle::-webkit-details-marker {
  display: none;
}
.editor--inline > .editor__toggle::after {
  content: "";
  align-self: center;
  width: 0.85em;
  height: 0.85em;
  background: var(--faint);
  mask: var(--glyph-edit) center / contain no-repeat;
}
.editor--inline > .editor__toggle:hover::after,
.editor--inline[open] > .editor__toggle::after {
  background: var(--accent);
}
.editor__body {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  padding: 0.9rem 1rem;
  background: var(--panel-2);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
}
.editor__form {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.editor__form + .editor__form {
  border-top: 1px solid var(--hairline);
  padding-top: 0.9rem;
}
/* A form's one save, offered once there is something to save. */
[data-dirty-form][data-enhanced]:not(.is-dirty) [data-dirty-save] {
  display: none;
}
.editor__save {
  display: flex;
}
.editor__legend {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--faint);
}
/* A section that folds is a fold first: ink text behind the chevron, as
   every other fold reads, rather than the grey caption a section that
   stands open carries — grey text is never clickable. */
.fold.editor__legend {
  font-size: 0.85rem;
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
  color: var(--ink);
}
.fold.editor__legend:hover {
  color: var(--accent);
}
/* A row of controls declares three lines once — label, control, hint — and
   every field takes its rows from the row rather than stacking its own.
   Aligning on an edge instead is what put the inputs at different heights:
   a field carrying a hint is taller than its neighbours, so aligning their
   bottoms lifted that one's input by the height of the hint, and aligning
   their tops would drop a checkbox onto the label line. With the lines
   declared here, a label sits on the label line and a control on the
   control line whatever is written above or below it.

   `auto-fit` collapses the tracks nothing lands in and the rest take an
   equal share, so two fields still split the row and three still take a
   third each; `minmax` is the width below which they wrap instead. */
.editor__row {
  /* The label line, at the label's own size and leading, plus the gap under
     it: how far below the top of a band the control line begins. Named once
     because an action that is not a labelled field has to find that line
     for itself. True only because `.field__label` is a block — see there. */
  --control-line: calc(0.8rem * 1.45 + 0.3rem);

  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
  grid-template-rows: auto auto auto;
  align-items: start;
  gap: 0.3rem 0.6rem;
}
/* Three lines high but not pinned to the first three: a field pinned to a
   row cannot wrap, and a row with more fields than the screen has columns
   would add columns past the edge instead of a second band of lines. */
.editor__row > .field {
  grid-row: span 3;
  display: grid;
  grid-template-rows: subgrid;
  gap: 0;
  /* A column may be narrower than what is in it; otherwise a <select> sized
     by its longest option widens its track and overruns its neighbour. */
  min-width: 0;
}
/* A button in the row — or the one-button form that is how a POST sits
   beside a GET — is a control, so it belongs on the control line with the
   inputs rather than on the labels above them, and at the width of its own
   label rather than stretched across a column.

   It finds that line by offset rather than by naming row 2, because an item
   pinned to a row cannot wrap: a row with more controls than the viewport
   has columns would push it into a column of its own past the edge, and the
   grid would overflow the panel. Offsetting instead leaves it an ordinary
   item that wraps with the rest, and the only thing a wrapped label costs
   is a button sitting one line high. */
.editor__row:has(> .field) > .btn,
.editor__row:has(> .field) > form,
.editor__row:has(> .field) > noscript {
  grid-row: span 3;
  align-self: start;
  justify-self: start;
  margin-top: var(--control-line);
}
/* A row holding nothing but actions is a row of buttons rather than a row
   of fields: they belong beside each other, not one per column of a grid
   that was sized for labelled inputs. */
/* Fixed columns rather than stretched ones: `auto-fill` keeps the empty
   tracks a short row leaves, so one field stays the width of one and the
   rows of a form line up column under column. The row is its own
   container, so a field told to take two columns does so only where there
   are two to take. */
/* A price row's four fields are one reading — so many of a unit at a price,
   less a discount — and stay on one line wherever a block is open, the
   narrow sheet included: sized to what each holds rather than wrapped by
   a generic minimum width. */
.editor__row--price {
  grid-template-columns:
    minmax(0, 0.8fr) minmax(0, 1.4fr) minmax(0, 1.1fr) minmax(0, 0.7fr);
}
.editor__row--cols {
  container: cols / inline-size;
  grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
}
@container cols (min-width: 24.6rem) {
  .editor__row--cols > .field--span2 {
    grid-column: span 2;
  }
}
.editor__row:not(:has(> .field)) {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
}
/* The height an input has, so the two read as one control line — as a floor
   rather than as a fixed height, because a label too long for its column
   wraps and a fixed box would cut it. */
.editor__row .btn {
  min-height: 2rem;
  padding-block: 0;
}
.editor__parties {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.3rem;
}
.editor__parties li {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
}
.editor__inline {
  display: inline;
}
.btn--small {
  font-size: 0.72rem;
  padding: 0.15rem 0.45rem;
}

.status-row__reason {
  margin-left: 0.5rem;
  color: var(--muted);
  font-size: 0.85rem;
}

/* ---- detail: change history ------------------------------------------ */

.history {
  margin-top: 2rem;
  padding-top: 1rem;
  border-top: 1px solid var(--hairline);
}
.history__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.35rem;
  font-size: 0.85rem;
}
.history__item {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
}
.history__when {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--faint);
  min-width: 9rem;
}
.history__what {
  flex: 1 1 20rem;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem;
}
.history__case {
  font-size: 0.72rem;
  color: var(--muted);
}
.history__field {
  color: var(--muted);
}
.history__to {
  font-weight: 500;
}
.history__arrow {
  color: var(--faint);
}
.history__note {
  color: var(--muted);
  font-style: italic;
}
.history__who {
  font-size: 0.78rem;
  color: var(--faint);
}
.history__removed {
  text-decoration: line-through;
  color: var(--muted);
  font-weight: 400;
}
/* The tail of the log, folded away: a page is read for what changed
 * lately, and a long record would otherwise be nothing but its history. */
.history__more {
  margin-top: 0.45rem;
}
.history__rest[open] .history__more {
  margin-bottom: 0.35rem;
}

/* Documents — the cabinet. Folders of the company's own project-file
 * template down the side, one drawer open beside them, so the section is
 * the same size on a project with four files and one with four hundred.
 * The open folder loses its right edge into the panel: the drawer is
 * pulled out of the column rather than merely highlighted in it.
 */
/* The cabinet is the page's working surface, so it takes the height of the
 * screen rather than a slot in it: on arrival the cover sheet and the top
 * of the file are both in view, and one scroll puts the whole cabinet on
 * screen. It keeps that one shape whatever is in it — a Vorgang with two
 * folders is still a cabinet, one with twenty scrolls its column — so the
 * page does not resize as drawers are opened. */
.cabinet__body {
  display: grid;
  grid-template-columns: minmax(10rem, 16rem) minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr);
  height: clamp(26rem, calc(100vh - 6rem), 60rem);
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
/* The strip over both panes. Its two cells share one height and one rule,
 * so the column's head and the list's head line up whatever the bar holds,
 * and a form folded out of a button opens over the list rather than
 * pushing it down. The first cell continues the folder column's tint. */
.cabinet__corner,
.cabinet__bar {
  display: flex;
  align-items: center;
  min-height: 2.9rem;
  min-width: 0;
  border-bottom: 1px solid var(--hairline);
}
.cabinet__corner {
  gap: 0.5rem;
  padding: 0 0.9rem 0 0.95rem;
  background: var(--panel-2);
  border-right: 1px solid var(--hairline);
  border-radius: var(--radius) 0 0 0;
}
.cabinet__title {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 600;
}
.cabinet__total {
  font-size: 0.75rem;
  color: var(--faint);
}
.cabinet__bar {
  flex-wrap: wrap;
  gap: 0.5rem 0.75rem;
  padding: 0.35rem 0.9rem;
}
/* A folder name runs to whatever someone typed, so it is capped and cut
 * with an ellipsis rather than allowed to push the buttons beside it onto
 * a second line; the full name is its title and the column's. */
.cabinet__bar > .drawer__title {
  flex: 0 1 auto;
  max-width: min(26rem, 40%);
}
/* What acts where the cabinet stands, beside the name of that place. */
.cabinet__here {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}
/* Filing, at the far end. */
.cabinet__acts {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  margin-left: auto;
  white-space: nowrap;
}
.cabinet__acts a:not(.btn) {
  font-size: 0.85rem;
}

/* The folder column scrolls inside the height the cabinet has, and only
 * where a project touches more folders than the screen holds. */
.cabinet__folders {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
  padding: 0.3rem 0;
  background: var(--panel-2);
  border-right: 1px solid var(--hairline);
  border-radius: 0 0 0 var(--radius);
}
/* A file open beside the list: the cabinet grows a third pane, wider than
 * the list because a page is read there, and the strip runs over both. The
 * list keeps the name and gives up its other columns, which the reader's
 * head and the file's own page carry; the sort they set still holds. */
.cabinet--reading .cabinet__body {
  grid-template-columns: minmax(9rem, 13rem) minmax(14rem, 1fr) minmax(0, 1.6fr);
}
.cabinet--reading .cabinet__bar {
  grid-column: 2 / -1;
}
.cabinet--reading .doc,
.cabinet--reading .docs__head {
  grid-template-columns: 1.1rem minmax(0, 1fr);
}
.cabinet--reading .doc > :nth-child(n + 3),
.cabinet--reading .docs__head > :nth-child(n + 3) {
  display: none;
}
/* The removed files: the column's last folder, set apart from the
 * template's own by the rule a new phase gets. */
.folder--retired {
  margin-top: 0.4rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--hairline);
  color: var(--muted);
}
/* The files filed elsewhere and linked here: a section of the column, one
 * drawer per owner. An arrow before each says the drawer is a way into
 * someone else's cabinet rather than one of this Vorgang's own; its rows
 * leave the pick column empty, since nothing here can be moved. */
.folder--linked .folder__label::before {
  content: "↗";
  display: inline-block;
  width: 1em;
  margin-right: 0.3rem;
  color: var(--faint);
}
.folder--retired + .folders__phase--linked {
  margin-top: 0.4rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--hairline);
}
/* The row being read, marked as the open folder is in the column. */
.doc.is-reading {
  background: var(--accent-soft);
  box-shadow: inset 3px 0 0 var(--accent);
}
.doc-tile.is-reading .tile {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.reader {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  border-left: 1px solid var(--hairline);
}
.reader__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  padding: 0.45rem 0.9rem;
  border-bottom: 1px solid var(--hairline);
}
.reader__name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.reader__link {
  font-size: 0.85rem;
  white-space: nowrap;
}
/* The page itself: grey around it, so where the sheet ends is visible. */
.reader__view {
  flex: 1;
  min-height: 0;
  overflow: auto;
  background: var(--panel-2);
  border-radius: 0 0 var(--radius) 0;
}
.reader__frame {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}
.reader__image {
  display: block;
  max-width: 100%;
  max-height: 100%;
  margin: 0 auto;
  object-fit: contain;
}
.reader__view > .preview {
  margin: 0;
  border: 0;
  border-radius: 0;
}
.reader__view .preview__body {
  max-height: none;
}
.reader__none {
  margin: 0;
  padding: 0.9rem 1rem;
}
.folder {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0.3rem 0.6rem 0.3rem 0.7rem;
  border-left: 3px solid transparent;
  color: var(--ink);
  font-size: 0.82rem;
  line-height: 1.25;
}
.folder:hover {
  background: rgba(21, 72, 137, 0.05);
  text-decoration: none;
}
.folder.is-open {
  background: var(--panel);
  border-left-color: var(--accent);
  font-weight: 600;
}
.folder__label {
  min-width: 0;
  overflow-wrap: anywhere;
}
.folder__count {
  margin-left: auto;
  font-size: 0.72rem;
  color: var(--faint);
}
.folder.is-open .folder__count {
  color: var(--muted);
}
/* A drawer of the template with nothing in it: listed, but quieter than
 * the ones holding something. */
.folder--empty:not(.is-open) .folder__label {
  color: var(--muted);
}
/* A folder inside the open drawer, indented by its depth. One line, cut
 * with an ellipsis: legacy folder names run to a phrase, and a wrapped
 * tree is harder to follow than a cut one. The full name is the title. */
.folder--sub {
  padding-left: calc(0.7rem + var(--depth, 1) * 0.85rem);
  font-size: 0.78rem;
}
.folder--sub .folder__label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow-wrap: normal;
}
/* The drawer or folder the open one sits in: marked as on the way, so the
 * column reads as a path, while only the open one carries the bar. */
.folder.is-on-path {
  font-weight: 600;
}
/* A file held over a folder of the column, as over a row. */
.folder.is-target {
  background: var(--accent-soft);
  box-shadow: inset 3px 0 0 var(--accent);
}
/* The stage a run of folders belongs to: a caption over them rather than
 * a folder itself, so it carries no count and no hover. Its text lines up
 * with the folder labels below it — the 3px the open marker occupies is
 * in the indent — and a rule appears only where a previous run ends. */
.folders__phase {
  padding: 0.35rem 0.7rem 0.15rem 0.95rem;
  font-size: 0.65rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--faint);
}
.folder + .folders__phase {
  margin-top: 0.4rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--hairline);
}
/* The drawer scrolls inside the height the cabinet already has, rather than
 * setting one of its own: the list is taken out of flow so a long one cannot
 * stretch the cabinet, and the floor on `.cabinet__body` carries the height
 * for both panes. */
.cabinet__panel {
  position: relative;
  min-width: 0;
}
/* The padding rides on the list, not the panel: an out-of-flow child
 * resolves against the padding box, so the panel's own would be ignored. */
/* The drawer is one grid holding both shapes: an image occupies a single
 * cell as a tile, everything else spans the full width and reads as the
 * list row it always was. A folder with no images is therefore a plain
 * list — no view mode exists, the content decides. */
.docs {
  position: absolute;
  inset: 0;
  overflow-y: auto;
  list-style: none;
  margin: 0;
  padding: 0.2rem 0.9rem 0.5rem;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  gap: 0.5rem;
  align-content: start;
}
/* A row and the header above the rows share one set of tracks, so the
 * columns line up down the drawer: the name takes what is left, and every
 * other column has the width its content needs. The retired list reuses
 * the row with its own three cells, so it keeps the flex layout. */
.doc,
.docs__head {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: 1.1rem minmax(0, 1fr) 10rem 8.5rem 4.5rem;
  align-items: baseline;
  gap: 0.75rem;
  padding: 0.35rem 0;
  border-bottom: 1px solid var(--hairline);
}
.docs--retired .doc {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}
.docs__head {
  position: sticky;
  top: 0;
  z-index: 1;
  padding: 0.3rem 0 0.25rem;
  background: var(--panel);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
/* A column header sorts and nothing else: grey until it is the active
 * one, the same look as a sortable table head (`.th-sort`). */
.docs__col {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  color: var(--faint);
}
.docs__col:hover {
  color: var(--ink);
  text-decoration: none;
}
.docs__col.is-sorted {
  color: var(--accent);
}
.docs__col--date,
.docs__col--size {
  justify-content: flex-end;
}
.doc__date {
  font-size: 0.75rem;
  color: var(--faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.doc__date {
  text-align: right;
}
/* The gap already separates tiles, so only consecutive rows want a rule
 * between them — and the last one before the label that opens the
 * pictures must not draw a second line under it. */
.doc:last-child,
.doc:has(+ .docs__break) {
  border-bottom: 0;
}
/* Where the drawer changes from rows to tiles. The rule runs out from the
 * label to the edge, so the break reads as a break at a glance while the
 * label stays as quiet as the provenance column beside it. */
.docs__break {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin: 0.45rem 0 0.1rem;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--faint);
}
.docs__break::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--hairline);
}
/* One line, cut with an ellipsis rather than wrapped: legacy names run to
 * a sentence, and a row grown to two lines pushes its provenance and size
 * out of the columns they are read down. The full name is the link's
 * `title`. */
.doc__name {
  flex: 1 1 18rem;
  display: flex;
  align-items: baseline;
  gap: 0.45rem;
  min-width: 0;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.doc__label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* The type at a glance, where Explorer puts its icon: the family's glyph
 * on a chip coloured in the hues the office suites made familiar, so the
 * drawing and the PDF of it differ before either name is read. A family
 * and never the extension — `.msg` or `.eml`, `.rtf` or `.docx` says
 * nothing to the person reading the drawer, and the name carries it
 * anyway. The family's name is the chip's title. */
:root {
  --glyph-file-pdf: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.5' stroke-linejoin='round' stroke-linecap='round'><path d='M4 1.75h5.5l3 3v9.5H4z'/><path d='M9.5 1.75v3h3'/><path d='M6.25 8.5h4M6.25 11h4'/></svg>");
  --glyph-file-text: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.5' stroke-linejoin='round' stroke-linecap='round'><path d='M2.75 3.5h10.5M2.75 6.5h10.5M2.75 9.5h10.5M2.75 12.5h6'/></svg>");
  --glyph-file-sheet: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.5' stroke-linejoin='round' stroke-linecap='round'><path d='M2.25 3h11.5v10H2.25z'/><path d='M2.25 6.5h11.5M2.25 9.75h11.5M6.25 3v10'/></svg>");
  --glyph-file-slides: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.5' stroke-linejoin='round' stroke-linecap='round'><path d='M2 2.75h12v8H2z'/><path d='M8 10.75v2.5M5.5 13.25h5'/></svg>");
  --glyph-file-cad: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.5' stroke-linejoin='round' stroke-linecap='round'><path d='M2.75 13.25V2.75l10.5 10.5z'/><path d='M5.25 10.75V8.5l2.25 2.25z'/></svg>");
  --glyph-file-mail: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.5' stroke-linejoin='round' stroke-linecap='round'><path d='M2 3.75h12v8.5H2z'/><path d='M2.25 4.25L8 8.5l5.75-4.25'/></svg>");
  --glyph-file-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.5' stroke-linejoin='round' stroke-linecap='round'><path d='M2 2.75h12v10.5H2z'/><path d='M2 11l3.5-3.5 3 3 2-2 3.5 3.5'/><circle cx='10.75' cy='5.75' r='1.1'/></svg>");
  --glyph-file-archive: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.5' stroke-linejoin='round' stroke-linecap='round'><path d='M2.75 5.5h10.5v7.75H2.75z'/><path d='M1.75 2.75h12.5V5.5H1.75z'/><path d='M6.5 8h3'/></svg>");
  --glyph-file-other: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.5' stroke-linejoin='round' stroke-linecap='round'><path d='M4 1.75h5.5l3 3v9.5H4z'/><path d='M9.5 1.75v3h3'/></svg>");
  --glyph-file-folder: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.5' stroke-linejoin='round' stroke-linecap='round'><path d='M1.75 4A1.25 1.25 0 0 1 3 2.75h3.25l1.5 1.5H13A1.25 1.25 0 0 1 14.25 5.5v6.5A1.25 1.25 0 0 1 13 13.25H3A1.25 1.25 0 0 1 1.75 12z'/></svg>");
}
.filetype {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.6rem;
  height: 1.2rem;
  align-self: center;
  border-radius: 3px;
  background: var(--filetype, var(--rail));
}
.filetype::before {
  content: "";
  width: 0.85rem;
  height: 0.85rem;
  background: #fff;
  mask: var(--filetype-glyph, var(--glyph-file-other)) center / contain
    no-repeat;
}
.filetype--pdf { --filetype: #a4372b; --filetype-glyph: var(--glyph-file-pdf); }
.filetype--text { --filetype: #2b579a; --filetype-glyph: var(--glyph-file-text); }
.filetype--sheet { --filetype: #217346; --filetype-glyph: var(--glyph-file-sheet); }
.filetype--slides { --filetype: #b7472a; --filetype-glyph: var(--glyph-file-slides); }
.filetype--cad { --filetype: #1b6f78; --filetype-glyph: var(--glyph-file-cad); }
.filetype--mail { --filetype: #5a6b80; --filetype-glyph: var(--glyph-file-mail); }
.filetype--image { --filetype: #6b4e8a; --filetype-glyph: var(--glyph-file-image); }
.filetype--archive { --filetype: #8a7350; --filetype-glyph: var(--glyph-file-archive); }
.filetype--other { --filetype-glyph: var(--glyph-file-other); }
.filetype--folder { --filetype-glyph: var(--glyph-file-folder); }
/* Both read as columns rather than as a tail on the name: right-aligned
 * with a floor wide enough for the usual values, so a row whose size is
 * "656 KB" lines up with one whose size is "76,1 MB". A long provenance
 * still grows past its floor — the name column gives way, not the
 * alignment. */
.doc__meta {
  min-width: 7rem;
  overflow: hidden;
  font-size: 0.78rem;
  color: var(--faint);
  text-align: right;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.doc__size {
  min-width: 4.5rem;
  font-size: 0.72rem;
  color: var(--faint);
  text-align: right;
}
.doc__missing {
  color: var(--stop);
}
.doc__from-mail {
  color: inherit;
}
/* A tile is square: photographs of one site vary wildly in aspect, and a
 * ragged grid is far harder to scan than a cropped one. The name stays
 * under it because two `IMG_00xx.jpg` are told apart by the picture, and
 * the file is told apart from its neighbours by the name. */
.tile {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  min-width: 0;
  text-decoration: none;
}
.tile__image {
  aspect-ratio: 1;
  width: 100%;
  object-fit: cover;
  background: var(--panel-2);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
}
.tile__name {
  overflow: hidden;
  font-size: 0.72rem;
  color: var(--faint);
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A drag anywhere over the section is a drop into it, so the whole
 * cabinet answers rather than a strip inside it. */
.cabinet.is-over .cabinet__body {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-soft), var(--shadow);
}

/* The open drawer: its list, with the bar for picked files under it. It fills
 * the panel the way the list alone did, so the cabinet keeps its height. */
.drawer {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
}
.drawer > .docs {
  position: static;
  flex: 1;
  min-height: 0;
}
.drawer__problem {
  margin: 0.5rem 0.9rem 0;
}
/* The open drawer's or folder's name, in the strip over its list: a
 * heading, never a link. */
.drawer__title {
  min-width: 0;
  margin: 0;
  overflow: hidden;
  font-size: 0.9rem;
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* A small form folded behind its button: naming a new folder, renaming
 * this one. Opens beside the button rather than pushing the list down. */
.fold-form {
  position: relative;
}
.fold-form > summary {
  list-style: none;
  cursor: pointer;
}
.fold-form > summary::-webkit-details-marker {
  display: none;
}
.fold-form > form {
  position: absolute;
  left: 0;
  z-index: 3;
  display: flex;
  gap: 0.4rem;
  margin-top: 0.3rem;
  padding: 0.5rem;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.fold-form input[type="text"] {
  width: 14rem;
}
/* A folder row reads as a row of the list, its badge a folder. */
.filetype--folder {
  --filetype: var(--rail);
}
.doc--folder .doc__label {
  font-weight: 600;
}
.doc__pick {
  margin: 0;
  align-self: center;
}
.doc-tile {
  position: relative;
}
.tile__pick {
  position: absolute;
  top: 0.35rem;
  left: 0.35rem;
  z-index: 1;
}
.docs__empty {
  grid-column: 1 / -1;
  padding: 0.6rem 0;
  font-size: 0.85rem;
}
/* What the picked files go to. Only there once something is picked. */
.drawer__bulk {
  display: none;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.9rem;
  border-top: 1px solid var(--hairline);
  background: var(--panel-2);
}
.cabinet:has(.doc__pick:checked) .drawer__bulk {
  display: flex;
}
.drawer__target {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.82rem;
}
.drawer__target select {
  max-width: 22rem;
}

/* The row a held file would land on as a new version: the same accent
 * the cabinet takes for a drop, narrowed to the one row it means. */
.doc.is-target {
  background: var(--accent-soft);
  box-shadow: inset 3px 0 0 var(--accent);
}
.doc__version {
  font-family: var(--font-mono);
  font-size: 0.7rem;
}
.doc__held {
  color: var(--legacy);
}
.filing__mismatch {
  margin: 0.6rem 1rem 0;
}
.filing__mismatch[hidden],
.filing__foot > .btn[hidden],
.filing__foot > .field[hidden] {
  display: none;
}

/* Who has a working file out, with the way to clear it beside the name:
 * a warning on the document, never a lock on it. */
.edit-held {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
}
.edit-held > form {
  margin-left: auto;
}
.edit-return {
  max-width: 44rem;
}
.version-acts {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.75rem;
}

/* The filing confirmation. Wide enough to read a drawing in, because
 * catching the wrong file is the whole reason it opens. */
.filing {
  width: min(56rem, 92vw);
  max-height: 88vh;
  padding: 0;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--ink);
}
.filing::backdrop {
  background: rgba(28, 27, 25, 0.4);
}
.filing__title {
  margin: 0;
  padding: 0.8rem 1rem;
  font-size: 1rem;
  border-bottom: 1px solid var(--hairline);
}
.filing__body {
  display: grid;
  grid-template-columns: minmax(12rem, 20rem) 1fr;
  height: min(60vh, 30rem);
}
.filing__files {
  list-style: none;
  margin: 0;
  padding: 0.3rem 0;
  overflow-y: auto;
  background: var(--panel-2);
  border-right: 1px solid var(--hairline);
}
.filing__file {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  width: 100%;
  padding: 0.35rem 0.7rem;
  border: 0;
  border-left: 3px solid transparent;
  background: none;
  font: inherit;
  font-size: 0.82rem;
  text-align: left;
  cursor: pointer;
}
.filing__file:hover {
  background: rgba(21, 72, 137, 0.05);
}
.filing__file.is-on {
  background: var(--panel);
  border-left-color: var(--accent);
  font-weight: 600;
}
.filing__name {
  min-width: 0;
  overflow-wrap: anywhere;
}
.filing__size {
  margin-left: auto;
  font-size: 0.72rem;
  color: var(--faint);
}
.filing__preview {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
  padding: 0.6rem;
  overflow: auto;
  background: var(--panel-2);
}
.filing__image {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}
.filing__frame {
  width: 100%;
  height: 100%;
  border: 0;
  background: var(--panel);
}
.filing__text {
  align-self: stretch;
  width: 100%;
  margin: 0;
  font-size: 0.8rem;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
/* Said, not left blank: mail, RTF and CAD are a large share of what gets
 * filed, and none of them can be shown before they are stored. */
.filing__none {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  max-width: 24rem;
  margin: 0;
  color: var(--muted);
  font-size: 0.85rem;
  text-align: center;
}
.filing__none-name {
  color: var(--ink);
  overflow-wrap: anywhere;
}
.filing__none-facts {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--faint);
}
.filing__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.6rem;
  padding: 0.7rem 1rem;
  border-top: 1px solid var(--hairline);
}
.filing__spacer {
  flex: 1 1 auto;
}

@media (max-width: 700px) {
  /* No room for two panes: the folders become a lid over the drawer, and
   * the drawer goes back to setting its own height under it. */
  .cabinet__body {
    grid-template-columns: 1fr;
    grid-template-rows: none;
    /* Stacked, a fixed height would be split between lid and drawer and
     * leave the drawer shorter than it is on a wide screen, not taller. */
    height: auto;
  }
  .cabinet__corner {
    border-right: 0;
    border-radius: var(--radius) var(--radius) 0 0;
  }
  .cabinet__bar {
    flex-wrap: wrap;
  }
  .cabinet__acts {
    flex-wrap: wrap;
  }
  .cabinet__folders {
    max-height: 12rem;
    border-right: 0;
    border-bottom: 1px solid var(--hairline);
    border-radius: 0;
  }
  .cabinet__panel {
    min-height: 14rem;
    max-height: 24rem;
    overflow-y: auto;
  }
  .docs,
  .drawer {
    position: static;
    overflow-y: visible;
  }
  /* Name, date and size are what a phone has room for. */
  .doc,
  .docs__head {
    grid-template-columns: 1.1rem minmax(0, 1fr) 7.5rem 4rem;
  }
  .doc__meta,
  .docs__col-meta {
    display: none;
  }
  /* Under the list rather than beside it, at a height a page can be
   * read at. */
  .cabinet--reading .cabinet__body {
    grid-template-columns: 1fr;
  }
  .cabinet--reading .cabinet__bar {
    grid-column: auto;
  }
  .cabinet--reading .reader {
    grid-column: auto;
    grid-row: auto;
    border-left: 0;
    border-top: 1px solid var(--hairline);
  }
  .reader__view {
    height: 75vh;
  }
}

/* The reader. Sized generously because the point is to make opening a
 * document in place preferable to downloading a copy of it. */
.viewer {
  margin: 0.9rem 0;
  background: var(--panel-2);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
}
.viewer__frame {
  display: block;
  width: 100%;
  height: min(78vh, 900px);
  border: 0;
}
.viewer__image {
  display: block;
  max-width: 100%;
  margin: 0 auto;
}
/* Text extracted on the server for a format the browser must not open.
 * Same frame as the viewer, but it scrolls its own body: mail runs long,
 * and the page controls below it have to stay reachable. */
.preview {
  margin: 0.9rem 0;
  padding: 0.9rem 1rem;
  background: var(--panel-2);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
}
.preview__headers {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.2rem 0.8rem;
  margin: 0 0 0.8rem;
  padding-bottom: 0.8rem;
  border-bottom: 1px solid var(--hairline);
}
.preview__headers dt {
  color: var(--muted);
}
.preview__headers dd {
  margin: 0;
  overflow-wrap: anywhere;
}
.preview__body {
  max-height: min(60vh, 700px);
  margin: 0;
  overflow: auto;
  font: inherit;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.inline-form {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-right: 0.8rem;
}

/* The Vorgänge a file is linked to: one line each, its number and the act
 * that takes the link back, then the form that adds one. */
.doc-links {
  list-style: none;
  margin: 0.3rem 0 0.6rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
.doc-links__row {
  display: flex;
  align-items: center;
  gap: 0.8rem;
}

/* What a message carried. Attachments read as a list; the body images —
 * signature logos and pasted pictures — sit folded behind a summary,
 * present without competing with the mail itself. */
.parts__list {
  list-style: none;
  margin: 0.3rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
.part {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  padding: 0.35rem 0;
  border-bottom: 1px solid var(--hairline);
}
.part:last-child {
  border-bottom: 0;
}
.part__name {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: 0.85rem;
  overflow-wrap: anywhere;
}
.part__size,
.part__filed {
  font-size: 0.78rem;
  color: var(--faint);
}

/* ---- the Vorgänge under their project row ---------------------------- */

/* A project of several pursuits carries them beneath its own row, as one
   block: indented and quieter, with no rule between the project and its
   Vorgänge or between siblings, so the block reads as one entry of the
   list. The Vorgänge carry the dates and badges; the project row, whose
   own would repeat theirs, carries none. */
.row-nested > td {
  border-top: none;
  padding-top: 0.15rem;
  padding-bottom: 0.15rem;
  font-size: 0.85rem;
  color: var(--muted);
}
.row-nested .col-num {
  padding-left: 1.25rem;
}
.row--parent > td,
.row-nested:has(+ .row-nested) > td {
  border-bottom: none;
}
.row-nested--fold > td {
  padding-bottom: 0.45rem;
}

/* The fold that draws a project's other Vorgänge: a button rather than a
   summary, because rows cannot sit inside <details> — so it says whether it
   is open through aria-expanded, which turns the chevron as [open] does. */
button.fold {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 0.85rem;
}
.fold[aria-expanded="true"]::before {
  transform: rotate(90deg);
}

/* ---- Arbeitsarten tree ----------------------------------------------- */

/* The vocabulary the kiosk walks: a Bereich, its Arbeitsarten, and the
   follow-up questions under one. Indentation is the whole point — the depth
   of a branch is what decides how many taps a booking costs. */
.tree {
  list-style: none;
  margin: 0;
  padding: 0;
}
.tree--nested {
  margin-left: 1.5rem;
  border-left: 1px solid var(--hairline);
  padding-left: 0.75rem;
}
.tree__row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.3rem 0;
  border-bottom: 1px solid var(--hairline);
}
.tree__code {
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  min-width: 3.5rem;
}
.tree__name {
  font-weight: 500;
}
/* Reordering sits where the order is visible. Pushed to the end of the row
   so the names stay a readable column. */
.tree__move {
  margin-left: auto;
  display: flex;
  gap: 0.25rem;
}
.tree__item--retired > .tree__row .tree__name {
  color: var(--muted);
  text-decoration: line-through;
}

/* ---- checkboxes ------------------------------------------------------ */

.check {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-right: 1rem;
}
.check__group {
  border: 1px solid var(--hairline);
  border-radius: 4px;
  padding: 0.6rem 0.8rem;
  margin: 0.6rem 0;
}
.check__group legend {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--faint);
}

/* ---- Bereich tabs ---------------------------------------------------- */

/* A Bereich is a place in the vocabulary, not a filter over a result set,
   so these are tabs rather than the list screens' filter segments. They are
   also drop targets: a row dragged onto one changes which Bereiche offer
   it. */
.tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.2rem;
  align-items: flex-end;
  border-bottom: 1px solid var(--hairline);
  margin: 1.2rem 0 0.4rem;
}
.tabs__tab {
  padding: 0.4rem 0.8rem;
  border: 1px solid transparent;
  border-bottom: none;
  border-radius: var(--radius) var(--radius) 0 0;
  color: var(--muted);
  text-decoration: none;
  margin-bottom: -1px;
}
.tabs__tab:hover {
  background: var(--panel-2);
  color: var(--ink);
}
.tabs__tab--on {
  background: var(--panel);
  border-color: var(--hairline);
  border-bottom: 1px solid var(--panel);
  color: var(--ink);
  font-weight: 600;
}
/* Arbeitsarten no Bereich offers: a gap, and it should look like one. */
.tabs__tab--warn {
  color: var(--stop);
}
.badge--shared {
  background: var(--accent-soft);
  color: var(--accent);
}
.tabs__count {
  color: var(--faint);
  font-variant-numeric: tabular-nums;
  margin-left: 0.3rem;
}
/* Dragging over a tab: the drop is about to change this Bereich. */
.tabs__tab--target {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--ink);
}
/* The two readings of a drop, asked at the tab itself. */
.tabs__drop {
  flex-basis: 100%;
  display: flex;
  gap: 0.5rem;
  padding: 0.5rem 0;
}
.tabs__drop--warn {
  color: var(--stop);
}

/* The handle says a row can be dragged; the arrows still do it without. */
.tree__grip {
  color: var(--faint);
  cursor: grab;
  user-select: none;
}
.tree__item--dragging {
  opacity: 0.5;
}


/* ---- day sheet ------------------------------------------------------- */

/* The slip, kept as a slip: whose day it is on a strip across the top, the
   lines in a sheet of their own below it, the total in the hours column
   where the paper has it. The two are separate panels because loading a day
   and keying one are separate acts. */
.dayhead {
  margin: 1.25rem 0;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.dayhead__controls {
  display: flex;
  gap: 1rem;
  align-items: flex-end;
  flex-wrap: wrap;
  padding: 0.85rem 1rem;
}
.dayhead__person {
  flex: 0 1 18rem;
  min-width: 12rem;
}
/* The button belongs to the control line, not to the labels above it, and
   `.field__input` already fixes that line's height for both shapes. Both
   controls apply as they change once the script runs, so it goes then. */
.dayhead__show {
  height: 2rem;
}
.dayhead[data-enhanced] .dayhead__show {
  display: none;
}

/* The week the strip shows, paged a week at a time: the arrows are places
   (the sheet a week over), the label folds out the calendar for a far
   jump, and "Heute" returns only from another week. */
.weeknav {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding: 0.5rem 1rem;
  border-top: 1px solid var(--hairline);
}
.weeknav__go {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.9rem;
  height: 1.9rem;
  color: var(--accent);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  background: var(--panel);
}
.weeknav__go:hover {
  background: var(--accent-soft);
}
.weeknav__jump {
  display: flex;
  align-items: center;
}
.weeknav__jump[open] {
  gap: 0.6rem;
}
.weeknav__jump > summary {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.weeknav__jump .field__input {
  width: 9.5rem;
}
.weeknav__today {
  margin-left: auto;
  font-size: 0.9rem;
}

/* The person's week under the head: each day a link to its sheet, with the
   hours it holds. A working day gone by with nothing on it says so — the
   missing slip is what the strip is for. */
.week {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr)) repeat(2, minmax(0, 0.7fr));
  border-top: 1px solid var(--hairline);
}
.week__day {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  padding: 0.5rem 0.75rem 0.55rem;
  text-decoration: none;
  border-top: 2px solid transparent;
  margin-top: -1px;
}
.week__day + .week__day {
  border-left: 1px solid var(--hairline);
}
.week__day:hover {
  background: var(--panel-2);
}
.week__date {
  font-size: 0.8rem;
  color: var(--accent);
}
.week__hours {
  min-height: 1.25rem;
  font-size: 0.95rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.week__day--weekend {
  background: var(--panel-2);
}
.week__day--missing .week__hours {
  font-weight: 500;
  font-size: 0.85rem;
  color: var(--stop);
}
.week__day--on {
  background: var(--accent-soft);
  border-top-color: var(--accent);
}
.week__day--on .week__date {
  color: var(--ink);
  font-weight: 600;
}
.week__day:last-child {
  border-bottom-right-radius: var(--radius);
}
.week__day:first-child {
  border-bottom-left-radius: var(--radius);
}
@media (max-width: 40rem) {
  .week__day {
    padding: 0.4rem 0.35rem;
  }
  .week__date {
    font-size: 0.72rem;
  }
  .week__hours {
    font-size: 0.82rem;
    white-space: nowrap;
  }
}

.sheet {
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
/* The table is the slip's grid; without a min-width the four controls squeeze
   into a column each too narrow to read a Projektnummer in. */
.sheet table {
  min-width: 52rem;
}
/* On a narrow screen the grid scrolls inside its panel rather than out of
   it; the lists that open from its fields are fixed to the viewport, so the
   scroller does not clip them. */
.sheet__grid {
  overflow-x: auto;
}
/* Fields sit at the top of their cell, so a row whose work type carries a
   legacy code stays aligned with the rows that do not: the note grows the
   row downwards instead of pushing the control up. */
.sheet tbody td {
  vertical-align: top;
  padding: 0.4rem 0.5rem;
}
.sheet thead th {
  padding: 0.6rem 0.5rem;
}
.sheet input[type="text"],
.sheet input[type="date"],
.sheet input[type="email"],
.sheet select {
  width: 100%;
  height: 2rem;
  padding: 0.25rem 0.45rem;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  font: inherit;
  font-size: 0.9rem;
  color: var(--ink);
  background: var(--panel);
}
.sheet input[type="text"]:focus,
.sheet input[type="date"]:focus,
.sheet input[type="email"]:focus,
.sheet select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.sheet select {
  font-size: 0.85rem;
}
.sheet input::placeholder {
  color: var(--faint);
}
.sheet .tag {
  display: inline-block;
  margin-top: 0.3rem;
}
.sheet tfoot td {
  font-weight: 600;
  padding: 0.55rem 0.5rem;
  border-top: 1px solid var(--hairline);
  background: var(--panel-2);
}
.sheet__foot {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.8rem 1rem;
  border-top: 1px solid var(--hairline);
}
/* Saving the day is the act; adding a line is a step on the way to it. */
.sheet__foot .btn--primary {
  margin-left: auto;
  padding: 0.4rem 1.1rem;
}

/* The line a save refused, so the eye lands on it before the message. */
.sheet .row--refused > td {
  background: #fbeae7;
}
/* A saved line the sheet will delete on save. Struck through rather than
   gone: the deletion is the same one the whole-sheet save always performs,
   and until it is saved it can be taken back. */
.sheet__row--dropped > td {
  background: var(--panel-2);
}
.sheet__row--dropped input,
.sheet__row--dropped select {
  color: var(--faint);
  text-decoration: line-through;
  border-style: dashed;
}
.sheet__dropped {
  display: inline-block;
  margin-top: 0.3rem;
  font-size: 0.75rem;
  color: var(--stop);
}
.sheet__tools {
  white-space: nowrap;
}
.iconbtn {
  padding: 0.3rem;
  line-height: 0;
  color: var(--faint);
  background: none;
  border: 1px solid transparent;
  border-radius: var(--radius);
  cursor: pointer;
}
.iconbtn:hover {
  color: var(--stop);
  border-color: var(--hairline);
}
.iconbtn--keep:hover {
  color: var(--accent);
}
/* Both row buttons and the add-line button work in the page. Without the
   sheet's script they would do nothing, so they are not offered. A row
   button that submits a form of its own works without it and stays. */
.sheet:not([data-enhanced]) .iconbtn[type="button"],
.sheet:not([data-enhanced]) [data-line-append] {
  display: none;
}

/* The project box: free text with a list under it, never a picker — a slip
   may name an old key nothing suggests. */
.combo {
  position: relative;
}
.combo__input--unknown {
  border-color: var(--stop);
}
.combo__list {
  position: fixed;
  z-index: 30;
  margin: 0;
  padding: 0.2rem;
  max-width: 26rem;
  max-height: 14rem;
  overflow-y: auto;
  list-style: none;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: 0 6px 18px rgba(28, 27, 25, 0.14);
}
.combo__list[hidden] {
  display: none;
}
.combo__option {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  padding: 0.25rem 0.4rem;
  border-radius: 2px;
  cursor: pointer;
}
.combo__option[aria-selected="true"],
.combo__option:hover {
  background: var(--accent-soft);
}
.combo__ref {
  font-family: var(--font-mono);
  font-weight: 600;
  white-space: nowrap;
}
.combo__label {
  color: var(--muted);
  font-size: 0.85rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.combo__empty {
  padding: 0.25rem 0.4rem;
  color: var(--faint);
  font-size: 0.85rem;
}

/* What the number on a line is, inside the box after the number, as the
   suggestion named it: room is left for the number on the left, the name
   takes the rest and ends in an ellipsis, the whole of it in the box's
   tooltip. Out of the way while the box is typed in. */
.sheet__hint {
  position: absolute;
  top: 0;
  right: 0.45rem;
  max-width: calc(100% - 5rem);
  height: 2rem;
  line-height: 2rem;
  font-size: 0.78rem;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  pointer-events: none;
}
.sheet__hint:empty,
.combo:focus-within .sheet__hint {
  display: none;
}

/* The add bar under the last line: its own body, so lines are appended
   above it and it stays the list's end. Without the script it adds
   nothing, and the plain form's spare rows stand in for it. */
.sheet .sheet__add td {
  padding: 0.35rem 0.5rem 0.5rem;
}
.sheet .sheet__add .add-bar {
  width: 100%;
}
.sheet:not([data-enhanced]) .sheet__add {
  display: none;
}

/* An empty day's ways to begin: the day before, or a whole day away. */
.sheet__start {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--hairline);
  background: var(--panel-2);
  border-radius: var(--radius) var(--radius) 0 0;
}
.sheet__start-label {
  margin-right: 0.25rem;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--ink);
}
.sheet__start-or {
  margin-left: 0.5rem;
  font-size: 0.85rem;
  color: var(--muted);
}

.sheet__keys {
  margin-left: auto;
  font-size: 0.78rem;
  color: var(--faint);
}
.sheet__keys + .btn--primary {
  margin-left: 0;
}
.sheet:not([data-enhanced]) .sheet__keys {
  display: none;
}
@media (max-width: 40rem) {
  .sheet__keys {
    display: none;
  }
}

/* The day against what the person owes and what the law allows. The cap is
   a mark on the bar rather than its end, so a day past it is still drawn. */
.daymeter {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  font-weight: 400;
}
.daymeter__bar {
  position: relative;
  flex: 1 1 auto;
  max-width: 16rem;
  height: 0.5rem;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: 2px;
}
.daymeter__fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: 0;
  background: var(--rail);
  border-radius: 1px;
  transition: width 0.15s ease, background-color 0.15s ease;
}
.daymeter--met .daymeter__fill {
  background: var(--ok);
}
.daymeter--over .daymeter__fill {
  background: var(--stop);
}
.daymeter__mark {
  position: absolute;
  top: -0.3rem;
  bottom: -0.3rem;
  width: 2px;
  margin-left: -1px;
  background: var(--ink);
}
.daymeter__mark--cap {
  background: var(--stop);
}
.daymeter__label {
  font-size: 0.8rem;
  color: var(--muted);
  white-space: nowrap;
}
.sheet:not([data-enhanced]) .daymeter__bar {
  display: none;
}
@media (prefers-reduced-motion: reduce) {
  .daymeter__fill {
    transition: none;
  }
}

/* ---- work-type picker ------------------------------------------------ */

/* The closed picker is a field showing the path chosen: it reads like the
   input beside it, with the caret a select would have, because it is the
   same kind of control. The select underneath stays the posted field. */
.wtp[data-enhanced] select {
  display: none;
}
.wtp__button {
  display: flex;
  align-items: center;
  width: 100%;
  height: 2rem;
  padding: 0.25rem 1.6rem 0.25rem 0.45rem;
  font: inherit;
  font-size: 0.88rem;
  text-align: left;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  background: var(--panel)
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%236b6862' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E")
    right 0.45rem center / 0.8rem no-repeat;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  cursor: pointer;
}
/* Empty, the field looks like the search it also is: typing on it starts
   one, which a bare caret would never suggest. */
.wtp[data-enhanced] ~ .wtp__legacy-below {
  display: none;
}
.wtp__button--empty {
  padding-left: 1.75rem;
  color: var(--faint);
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%2397928a' stroke-width='1.6' stroke-linecap='round'%3E%3Ccircle cx='7' cy='7' r='4.25'/%3E%3Cpath d='M10.25 10.25L13.5 13.5'/%3E%3C/svg%3E"),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%236b6862' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
  background-position:
    left 0.5rem center,
    right 0.45rem center;
  background-size:
    0.85rem,
    0.8rem;
  background-repeat: no-repeat;
}
/* Chosen, a path: the answer in bold, the questions before it quieter.
   The script drops whole steps from the middle when the path does not fit
   (`work_type_picker.js`), so the trail never shrinks and the leaf is cut
   only where it is longer than the field on its own. */
.wtp__trail {
  flex: none;
  color: var(--muted);
  white-space: pre;
}
.wtp__leaf {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: pre;
  font-weight: 600;
}
.wtp__button:focus,
.wtp__button[aria-expanded="true"] {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.sheet__row--dropped .wtp__button {
  color: var(--faint);
  text-decoration: line-through;
  border-style: dashed;
  pointer-events: none;
}

/* Open, it is the tree laid out in steps: one column per question, the
   answer so far marked in each, the next one beside it. */
.wtp-pop {
  position: fixed;
  z-index: 40;
  display: flex;
  flex-direction: column;
  width: min(50rem, calc(100vw - 1rem));
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: 0 10px 28px rgba(28, 27, 25, 0.18);
}
.wtp-pop[hidden] {
  display: none;
}
.wtp-pop__search {
  margin: 0.6rem 0.6rem 0;
  height: 2.1rem;
  padding: 0.25rem 0.55rem;
  font: inherit;
  font-size: 0.92rem;
  color: var(--ink);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
}
.wtp-pop__search:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.wtp-pop__usual {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  padding: 0.55rem 0.6rem 0;
}
.wtp-pop__usual[hidden] {
  display: none;
}
.wtp-pop__usual-label {
  margin-right: 0.2rem;
  font-size: 0.78rem;
  color: var(--muted);
}
.wtp-pop__chip {
  max-width: 100%;
  padding: 0.2rem 0.55rem;
  font: inherit;
  font-size: 0.82rem;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  cursor: pointer;
}
.wtp-pop__chip:hover,
.wtp-pop__chip:focus-visible {
  border-color: var(--accent);
  background: var(--accent-soft);
}
.wtp-pop__cols {
  display: flex;
  height: 18rem;
  margin-top: 0.6rem;
  overflow-x: auto;
  border-top: 1px solid var(--hairline);
}
.wtp-pop__cols[hidden] {
  display: none;
}
.wtp-pop__col {
  flex: 0 0 13rem;
  margin: 0;
  padding: 0.3rem;
  list-style: none;
  overflow-y: auto;
  border-right: 1px solid var(--hairline);
}
.wtp-pop__col:first-child {
  flex-basis: 11rem;
  background: var(--panel-2);
}
.wtp-pop__item {
  position: relative;
  padding: 0.32rem 1.4rem 0.32rem 0.55rem;
  font-size: 0.88rem;
  line-height: 1.3;
  border-radius: 2px;
  cursor: pointer;
}
.wtp-pop__item:hover {
  background: var(--panel-2);
}
.wtp-pop__col:first-child .wtp-pop__item:hover {
  background: var(--panel);
}
.wtp-pop__item--branch::after {
  content: "";
  position: absolute;
  right: 0.5rem;
  top: 50%;
  width: 0.4rem;
  height: 0.4rem;
  margin-top: -0.2rem;
  border-right: 1.5px solid var(--faint);
  border-top: 1.5px solid var(--faint);
  transform: rotate(45deg);
}
.wtp-pop__item--open {
  font-weight: 600;
  background: var(--panel-2);
}
.wtp-pop__col:first-child .wtp-pop__item--open {
  background: var(--panel);
}
.wtp-pop__item[aria-selected="true"] {
  background: var(--accent-soft);
  box-shadow: inset 2px 0 0 var(--accent);
}
.wtp-pop__item--divided {
  margin-top: 0.45rem;
}
.wtp-pop__item--divided::before {
  content: "";
  position: absolute;
  left: 0.2rem;
  right: 0.2rem;
  top: -0.25rem;
  border-top: 1px solid var(--hairline);
}
.wtp-pop__results {
  max-height: 20rem;
  margin: 0.6rem 0 0;
  padding: 0.3rem;
  list-style: none;
  overflow-y: auto;
  border-top: 1px solid var(--hairline);
}
.wtp-pop__results[hidden] {
  display: none;
}
.wtp-pop__hit {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  padding: 0.35rem 0.55rem;
  border-radius: 2px;
  cursor: pointer;
}
.wtp-pop__hit:hover {
  background: var(--panel-2);
}
.wtp-pop__hit[aria-selected="true"] {
  background: var(--accent-soft);
  box-shadow: inset 2px 0 0 var(--accent);
}
.wtp-pop__hit-path {
  font-size: 0.9rem;
}
.wtp-pop__hit-where {
  margin-left: auto;
  font-size: 0.75rem;
  color: var(--muted);
  white-space: nowrap;
}
.wtp-pop__empty {
  padding: 0.5rem 0.55rem;
  font-size: 0.85rem;
  color: var(--faint);
}

/* ---- personnel detail ------------------------------------------------ */

/* A card. The same paper as the index panel and the day sheet, named for
   what it is so a screen that is neither a list nor a form can use it. */
.panel {
  padding: 1.1rem 1.25rem;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}

/* A supplier invoice is read against its own document: the figures and the
   attribution on the left, the page they came off on the right. Even halves,
   because the preview is the thing being read and a column of fields that
   fits is not improved by being wider.

   The preview is sticky, so it stays in view while the allocation form
   below it is worked down — an attribution is decided on what the document
   says, and scrolling away from it to type is what makes people guess. */
.invoice__cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 1.5rem;
  align-items: start;
}
/* No document: the fields have no business stretching across a monitor. */
.invoice__cols--bare {
  grid-template-columns: minmax(0, 46rem);
}
.invoice__data {
  display: grid;
  gap: 1.25rem;
}
.invoice__doc {
  position: sticky;
  top: 1rem;
}
.invoice__doc .viewer {
  margin: 0;
}
.invoice__doc .viewer__frame {
  height: min(84vh, 1100px);
}
.invoice__file {
  margin: 0.45rem 0 0;
  font-size: 0.75rem;
  color: var(--faint);
  overflow-wrap: anywhere;
}
.invoice__file .num {
  font-size: 0.72rem;
}
/* Below this the two would each be too narrow to read, so they stack — and
   a stacked preview must not be sticky, or it covers the form under it. */
@media (max-width: 1100px) {
  .invoice__cols {
    grid-template-columns: minmax(0, 1fr);
  }
  .invoice__doc {
    position: static;
  }
  .invoice__doc .viewer__frame {
    height: min(70vh, 800px);
  }
}

/* The record on the left, what the person has booked on the right: master
   data is edited, hours are only ever read here and followed into the sheet.
   One column as soon as the form would have to squeeze. */
/* A list of days needs a date and a figure, not the page's width. */
.employee__hours {
  max-width: 28rem;
}

/* Fields are as wide as their content deserves: a date box the width of a
   date, a name across the row. Stretching every input to the panel makes a
   short value look like a missing one. */
.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  gap: 0.9rem 1rem;
  margin-bottom: 1.1rem;
}
.form-grid__wide {
  grid-column: 1 / -1;
}
.field__hint {
  font-size: 0.75rem;
  color: var(--faint);
}
textarea.field__input {
  height: auto;
  resize: vertical;
  line-height: 1.4;
}
.form-actions {
  display: flex;
  align-items: baseline;
  gap: 1rem;
  flex-wrap: wrap;
}
.form-actions .legend {
  margin: 0;
  flex: 1 1 18rem;
}

/* The days this person has hours on. A row is a link into that day's sheet,
   so the date leads and the figure closes the line. */
.daylist {
  list-style: none;
  margin: 1rem 0 0;
  padding: 0;
}
.daylist li + li {
  border-top: 1px solid var(--panel-2);
}
.daylist a {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.35rem 0.4rem;
  color: var(--ink);
  border-radius: var(--radius);
}
.daylist a:hover {
  background: var(--accent-soft);
  text-decoration: none;
}
.daylist__hours {
  color: var(--muted);
}
.daylist__empty {
  padding: 0.35rem 0.4rem;
  color: var(--faint);
}
/* A field that spans the row still stops at a readable line length: a name
   stretched across the panel reads as a paragraph, not as a name. */
.form-grid__wide .field__input {
  max-width: 32rem;
}
.form-grid__wide textarea.field__input {
  max-width: none;
}
/* A checkbox reads as a sentence rather than as a labelled box, so it has
   no label line of its own. In a row of fields it therefore names the lines
   it belongs on — the control line beside the inputs, the hint line under
   them — instead of filling them in order and landing one line too high. */
.field--check {
  padding-bottom: 0.4rem;
}
.editor__row > .field--check {
  padding-bottom: 0;
}
.editor__row > .field--check > .check__line {
  grid-row: 2;
  align-self: center;
}
.check__line {
  display: inline-flex;
  align-items: center;
}
.check__line > .check {
  margin-right: 0.35rem;
}

/* What a field means, behind a circled "i" beside its label (`_form.html`,
   `info_toggle`). The explanation floats under the mark rather than
   pushing the row apart, and closes again on a click outside it. */
.info {
  position: relative;
  display: inline-block;
}
.info__mark {
  display: inline-grid;
  place-items: center;
  width: 1.05rem;
  height: 1.05rem;
  font: italic 600 0.72rem/1 Georgia, "Times New Roman", serif;
  color: var(--muted);
  border: 1px solid currentColor;
  border-radius: 50%;
  cursor: pointer;
  list-style: none;
}
.info__mark::-webkit-details-marker {
  display: none;
}
.info__mark:hover,
.info[open] > .info__mark {
  color: var(--accent);
}
.info__text {
  position: absolute;
  top: calc(100% + 6px);
  right: -0.5rem;
  z-index: 20;
  width: max-content;
  max-width: min(22rem, 80vw);
  margin: 0;
  padding: 0.5rem 0.7rem;
  font-size: 0.8rem;
  line-height: 1.45;
  color: var(--ink);
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: 0 10px 30px rgba(28, 27, 25, 0.14);
}
/* Beside a title at the left of the page the explanation hangs from the
   mark's left edge, or it would run off the page. */
.info--start > .info__text {
  right: auto;
  left: -0.5rem;
}
/* Why a record cannot be deleted yet (`templates/_deletion.html`): a lead
   line and one line per kind of reference, in the page's own type, since it
   is read beside the greyed-out control it explains. */
.blockers__lead {
  margin: 0;
  color: var(--muted);
}
.blockers__list {
  margin: 0.2rem 0 0;
  padding-left: 1.1rem;
}
.blockers__list li + li {
  margin-top: 0.15rem;
}
/* A fold's summary is the row's own text (a count), so it drops the
   disclosure marker like the "i" does. */
.info > summary {
  list-style: none;
}
.info > summary::-webkit-details-marker {
  display: none;
}

/* A heading with its "i" (`_form.html`, `titled`): the mark sits on the
   title's line, a step after its last word. The wrapper takes over the
   heading's margins, which would otherwise set the mark off centre. */
.titled {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.titled > :first-child {
  margin: 0;
}
.titled:has(> h2) {
  margin: 1.5rem 0 0.75rem;
}
.titled:has(> .finder__title) {
  margin-bottom: 0.75rem;
}
.titled:has(> .project-head__title) {
  margin: 0.1rem 0;
}
.blocks__intro > .titled {
  margin: 0 0 0.25rem;
}
/* A screen's head written without the finder under it: the intro line that
   once spaced it from the work now sits behind the "i", so the head spaces
   itself. */
.page > .finder__head {
  margin-bottom: 1.25rem;
}
.titled > .info {
  font-size: 1rem;
  font-weight: 400;
}
.editor__row > .field--check > .field__hint {
  grid-row: 3;
}
.section__label + .daylist,
.facts + .daylist {
  margin-top: 1rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--hairline);
}

/* ---- trial-run strip ------------------------------------------------- */

/* The one place the parallel run is mentioned: a hairline strip under the
   bar, in the amber this palette reserves for legacy provenance. Quiet on
   purpose — the demo is meant to look like the finished product, and a
   loud banner on every screen would be the loudest thing on most of them.
   It is rendered only while Kuhnle is a source, so it needs no dismissal:
   it goes away when the import does. */
.trialstrip {
  margin: 0;
  padding: 0.35rem 1.5rem;
  font-size: 0.78rem;
  color: var(--legacy);
  background: var(--legacy-soft);
  border-bottom: 1px solid var(--hairline);
}

/* Category tags under the party's address: what it is, beside the roles
   list further down which says what it has done. */

/* A Bereich that is no longer offered. Still reachable — its work types and
   their hours are still there to read — but struck through, like a retired
   row in the tree below it. */
.tabs__tab--retired {
  color: var(--faint);
  text-decoration: line-through;
}


/* A button that is the whole form — retiring something, say — is the width
   of its label, not of the panel it sits in. */
.editor__form > .btn {
  align-self: flex-start;
}

/* ---- user administration --------------------------------------------- */

/* Whose view an administrator has borrowed. Louder than the trial-run strip
   below it and in the stop colour, because every screen under it is lying
   about who you are until you leave. */
.viewstrip {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.45rem 1.5rem;
  font-size: 0.85rem;
  color: #fff;
  background: var(--stop);
}
.viewstrip .btn {
  background: transparent;
  border-color: rgba(255, 255, 255, 0.6);
  color: #fff;
}
.viewstrip .btn:hover {
  background: rgba(255, 255, 255, 0.12);
  border-color: #fff;
}

/* A user row reads as a person: the name leads, the address and how they
   sign in sit under it in the same cell. */
.user__name {
  font-weight: 500;
}
.user__mail,
.user__meta {
  display: block;
  font-size: 0.8rem;
  color: var(--muted);
}
.user__meta {
  color: var(--faint);
}
.user--off td {
  background: var(--panel-2);
}
.user--off .user__name {
  color: var(--muted);
}
/* A form that is one control in a table cell must not add a line of its own. */
.inlineform {
  display: inline;
}
.user__roles {
  line-height: 2;
}

/* A role chip is a switch: lit when held, quiet when not. The whole table
   swaps back after a click, so the chip never disagrees with the database. */
.chip--role {
  font: inherit;
  font-size: 0.78rem;
  padding: 0.1rem 0.55rem;
  margin-right: 0.3rem;
  border-radius: 999px;
  border: 1px solid var(--hairline);
  background: var(--panel);
  color: var(--muted);
  cursor: pointer;
}
.chip--role:hover {
  border-color: var(--accent);
  color: var(--ink);
}
.chip--role.is-on {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent);
  font-weight: 600;
}

/* Permissions down, roles across: a row answers "who holds this", which is
   the question an audit asks. */
.matrix {
  table-layout: auto;
  min-width: 0;
}
.matrix th {
  text-align: left;
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--faint);
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--hairline);
}
.matrix__role {
  text-align: center !important;
}
.matrix__perm {
  text-transform: none !important;
  letter-spacing: 0 !important;
  font-size: 0.9rem !important;
  font-weight: 400 !important;
  color: var(--ink) !important;
  white-space: nowrap;
}
.matrix__cell {
  text-align: center;
  padding: 0.2rem 0.75rem;
  border-bottom: 1px solid var(--panel-2);
}
.matrix__toggle {
  width: 1.9rem;
  height: 1.9rem;
  font: inherit;
  line-height: 1;
  color: var(--ok);
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  cursor: pointer;
}
.matrix__toggle:hover {
  border-color: var(--accent);
  background: var(--accent-soft);
}
.matrix__toggle.is-on {
  background: var(--ok-soft);
  border-color: #b7d3bd;
  font-weight: 700;
}
/* Held through the wildcard rather than chosen: shown, not offered. */
.matrix__implied {
  color: var(--faint);
  cursor: help;
}

/* ---- Zeiterfassung: slice nav and the monthly read-out ---------------- */

/* Three screens of one slice pointing at each other. Quieter than the
   module links above them: this is where you already are, not where the
   app can take you. */
/* The month and the file, side by side: choosing a month and handing it on
   are the two things this screen is for. */
.monthbar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
  justify-content: space-between;
  margin: 0.4rem 0 1rem;
}
.monthbar__pick {
  display: flex;
  gap: 0.4rem;
  align-items: center;
}
.monthbar__form {
  display: flex;
  gap: 0.35rem;
  align-items: center;
  margin: 0;
}
.monthbar__name {
  margin-left: 0.4rem;
  font-weight: 600;
}

/* What the month comes to, above the rows it comes from — the figure that
   gets handed on is read more often than any single row. */
.monthstats {
  display: flex;
  flex-wrap: wrap;
  gap: 1.75rem;
  align-items: center;
  padding: 0.85rem 1.1rem;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
/* Tiles carrying a line under their figure: aligned on the top edge, so
   the figures sit on one line whatever each tile holds beneath them. */
.monthstats--top {
  align-items: flex-start;
}
.monthstat {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}
.monthstat__label {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--faint);
}
.monthstat__value {
  font-size: 1.15rem;
  font-variant-numeric: tabular-nums;
}
.monthstat--total .monthstat__value {
  font-weight: 600;
}
/* Money past its date: the figure and, on the register, the due date of
   each row it is made of. Colour only marks it — the row's filter and a
   hidden word say it. */
.monthstat--late .monthstat__value,
.late {
  color: var(--stop);
  font-weight: 600;
}
/* What is missing from the month, beside what it holds. */
.monthstat__flags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-left: auto;
}
.tag--warn {
  color: var(--legacy);
  border-color: var(--legacy);
  background: var(--legacy-soft);
}
a.tag--warn:hover {
  text-decoration: none;
  background: var(--panel-2);
}

/* A month-wide figure under a page of rows: it does not add up to what is
   on screen, and it is not meant to. */
.row--total td {
  font-weight: 600;
  border-top: 2px solid var(--hairline);
}
tfoot td {
  padding-top: 0.5rem;
  color: var(--muted);
}
/* A person who booked nothing at all. */
.row--quiet .col-name a {
  color: var(--muted);
}

/* The editor a document text is written in, and the preview beside it.
   Quill's own stylesheet (`quill.core.css`) carries what the editor needs
   to function — the list markers, the alignment classes, the caret. What
   is here is only how it sits in this app: the box is the app's box, so a
   text field and a document text read as the same control. */
.richtext__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem;
  padding: 0.25rem;
  border: 1px solid var(--hairline);
  border-bottom: none;
  border-radius: var(--radius) var(--radius) 0 0;
  background: var(--panel-2);
}
/* Bar and box are one control, but two items of the field's column, which
   puts the field's gap between them; taken back here so the bar sits on
   the box it formats. */
.field > .richtext__bar {
  margin-bottom: -0.3rem;
}
.richtext__btn {
  font: inherit;
  min-width: 1.9rem;
  height: 1.7rem;
  padding: 0 0.4rem;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: none;
  color: var(--muted);
  cursor: pointer;
  line-height: 1;
}
.richtext__btn:hover {
  background: var(--panel);
  color: var(--ink);
}
/* Quill marks the buttons whose format the caret sits in. */
.richtext__btn.ql-active {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent);
}
/* The menu a letter's fields are picked from (`services/text_fields.py`):
   a control in the bar like the buttons beside it. */
.richtext__fields {
  font: inherit;
  font-size: 0.85rem;
  height: 1.7rem;
  padding: 0 0.3rem;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--muted);
}
/* A field in the text: a grey chip, since it is not the words themselves
   but a place the letter fills in. */
.richtext__area .ql-field {
  display: inline-block;
  padding: 0 0.35rem;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  background: var(--panel-2);
  color: var(--muted);
  font-size: 0.85em;
  line-height: 1.4;
}
.richtext__sep {
  width: 1px;
  height: 1.1rem;
  margin: 0 0.15rem;
  background: var(--hairline);
}
.richtext__area {
  border: 1px solid var(--hairline);
  border-radius: 0 0 var(--radius) var(--radius);
  background: var(--panel);
}
.richtext__area:focus-within {
  border-color: var(--accent);
}
.richtext__area .ql-editor {
  font: inherit;
  padding: 0.4rem 0.55rem;
  color: var(--ink);
}
/* The document's own shape, in the editor and in the issue preview: a
   paragraph is a paragraph, and nothing here decides type or colour —
   that is the page frame's (`erp/documents/letter.typ`). Paragraphs sit a
   line apart as they print, so a gap between two is a blank line the
   author wrote, and an empty paragraph is given the line it stands for. */
.richtext__area .ql-editor p,
.prose p {
  margin: 0;
}
.prose p:empty::before {
  content: "\200b";
}
.prose ul,
.prose ol {
  margin: 0;
  padding-left: 1.4rem;
}
.prose .center {
  text-align: center;
}
/* Under the text of the bullet above, as far in as a list item's. */
.prose .indent {
  padding-left: 1.4rem;
}

/* ---- issuing a document ---------------------------------------------- */

/* Two layers, told apart by the ground they sit on — the same rule the rest
 * of the system uses, where a white panel is the record and the warm grey
 * is the application editing it. Here the stakes are higher: what is on the
 * sheet is posted to a customer, and what is beside it never leaves the
 * browser. So the sheet is paper, every tool is grey with a rail down its
 * left edge, and nothing is styled in a way that could be mistaken for the
 * other. */

.sheet {
  display: block;
  margin-top: 1.2rem;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 2rem 2.25rem 1.5rem;
}

/* The head the page frame prints: reference rows on the left, the
   document's own mark on the right, as `documents/letter.typ` places
   them. */
.sheet__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 2rem;
  flex-wrap: wrap;
}
.sheet__refs {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.1rem 1rem;
  margin: 0;
  font-size: 0.9rem;
}
.sheet__refs dt {
  color: var(--muted);
}
.sheet__refs dd {
  margin: 0;
  overflow-wrap: anywhere;
}
.sheet__mark {
  text-align: right;
}
.sheet__type {
  font-weight: 700;
  font-size: 1.05rem;
  letter-spacing: 0.02em;
}
.sheet__by {
  font-size: 0.9rem;
  color: var(--muted);
}

.sheet__address {
  margin-top: 2rem;
  line-height: 1.5;
}
/* The one-line return address over the window, printed at the size it is
   printed at: small, and underscored the way an envelope window shows it. */
.sheet__sender {
  font-size: 0.72rem;
  color: var(--muted);
  border-bottom: 1px solid var(--hairline);
  padding-bottom: 0.15rem;
  margin-bottom: 0.5rem;
  max-width: 24rem;
}

/* One printing region — the opening, the positions, the closing. The label
   names it for whoever is reading the screen and is not on the letter, so
   it rides the rule rather than sitting in the text column. */
.sheet__part {
  margin-top: 2rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--hairline);
}
.sheet__part-label {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--faint);
  margin: 0 0 0.9rem;
}
/* What concerns the region as a whole: the add bar closing its list,
   then the acts on the region, quiet and to the right. */
.sheet__part-foot {
  max-width: 46rem;
  margin-top: 1.2rem;
}
.sheet__part-acts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 0.4rem 1rem;
  margin-top: 0.5rem;
}
.sheet__part-act {
  display: inline;
  margin: 0;
}

/* Where the page frame starts a new page: a dashed rule across the text
   column, named in the middle. */
.sheet__break {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  max-width: 46rem;
  margin: 1.5rem 0;
  font-size: 0.72rem;
  color: var(--faint);
}
.sheet__break::before,
.sheet__break::after {
  content: "";
  flex: 1;
  border-top: 1px dashed var(--rail);
}

/* A paragraph as it will print, and its own handle. Nothing the
 * application adds stands between paragraphs: pointed at, an editable
 * paragraph is washed in the tools' grey with a rail down its left edge,
 * its grip appears in the sheet's margin and its name hangs above it as a
 * tab; clicking anywhere on it opens the editor in its place. A paragraph
 * this Vorgang changed keeps its rail in blue at all times — the change bar
 * a revised document carries. A locked one shows its name and nothing
 * else, because there is nothing it can be asked to do. */
.para {
  position: relative;
  isolation: isolate;
  max-width: 46rem;
  /* How far the wash reaches past the text, so the wording itself never
     moves when it is pointed at. */
  --para-bleed-y: 0.4rem;
  --para-bleed-x: 0.6rem;
  --para-bleed-left: 0.9rem;
}
.para + .para {
  margin-top: 1.5rem;
}
.para::before {
  content: "";
  position: absolute;
  inset: calc(-1 * var(--para-bleed-y)) calc(-1 * var(--para-bleed-x))
    calc(-1 * var(--para-bleed-y)) calc(-1 * var(--para-bleed-left));
  z-index: -1;
  border-left: 2px solid transparent;
  border-radius: 0 var(--radius) var(--radius) 0;
  pointer-events: none;
}
/* The change bar is read off the tag rather than a class on the paragraph:
   a save swaps the tag, never the paragraph that holds the editor. */
.para:has(> .para__tag[data-own])::before {
  border-left-color: var(--accent);
}
.para:not(.para--locked):hover::before,
.para:not(.para--locked):focus-within::before,
.para:has(.para__edit[open])::before {
  background: var(--panel-2);
  border-left-color: var(--rail);
}
.para:has(> .para__tag[data-own]):hover::before,
.para:has(> .para__tag[data-own]):focus-within::before,
.para:has(> .para__tag[data-own]):has(.para__edit[open])::before {
  border-left-color: var(--accent);
}

/* The whole paragraph is the editor's summary, so a click anywhere on it
   opens the editor without a script and a keyboard tabs to it. */
.para__hit {
  position: absolute;
  inset: calc(-1 * var(--para-bleed-y)) calc(-1 * var(--para-bleed-x))
    calc(-1 * var(--para-bleed-y)) calc(-1 * var(--para-bleed-left));
  z-index: 1;
  list-style: none;
  cursor: pointer;
  border-radius: 0 var(--radius) var(--radius) 0;
}
.para__hit::-webkit-details-marker {
  display: none;
}
.para__edit[open] > .para__hit {
  display: none;
}
.para:has(.para__edit[open]) > .para__text {
  display: none;
}
.para__form {
  padding: 0.2rem 0 0.1rem;
}
.para__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.6rem;
}
/* Swapped as one piece by a save, laid out as the row's own buttons. */
.para__acts {
  display: contents;
}
.para__moves {
  display: inline-flex;
  gap: 0.25rem;
  margin-left: auto;
}
/* The add bar is a form's button here rather than a fold's summary, and
   stretches across the sheet as the summary did. */
.para__add {
  display: flex;
  flex-direction: column;
}

/* The grip, in the sheet's margin beside the first line. */
.para__grip {
  position: absolute;
  top: 0.1rem;
  left: calc(-1 * var(--para-bleed-left) - 1.15rem);
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1rem;
  height: 1.3rem;
  color: var(--faint);
  cursor: grab;
  opacity: 0;
}
.para__grip:hover {
  color: var(--ink);
}
.paras:not([data-enhanced]) .para__grip,
.para:has(.para__edit[open]) > .para__grip {
  display: none;
}

/* The name, and what stands about the paragraph, as a tab on the wash's
   top edge at the right — in the gap above the paragraph, so it covers
   none of the wording. */
.para__tag {
  position: absolute;
  right: calc(-1 * var(--para-bleed-x));
  bottom: calc(100% + var(--para-bleed-y));
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 0.45rem;
  max-width: 100%;
  padding: 0.1rem 0.25rem 0 0.55rem;
  background: var(--panel-2);
  border-radius: var(--radius) var(--radius) 0 0;
  font-size: 0.72rem;
  line-height: 1.3;
  color: var(--muted);
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
}
.para__name {
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: 600;
}
.para__state {
  color: var(--faint);
}
/* Not blue text, which would name a place: the change bar's own mark. */
.para__state--own {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  color: var(--muted);
}
.para__state--own::before {
  content: "";
  width: 2px;
  height: 0.85em;
  background: var(--accent);
}
.para__drop-form {
  display: inline;
  margin: 0;
}
.spec__drop.para__drop {
  height: 1.15rem;
  padding: 0 0.1rem;
}
.para:hover > .para__tag,
.para:focus-within > .para__tag,
.para:has(.para__edit[open]) > .para__tag,
.para:hover > .para__grip,
.para:focus-within > .para__grip {
  opacity: 1;
  pointer-events: auto;
}
.para--dragging {
  opacity: 0.4;
}
.para--dragging > .para__tag,
.para--dragging > .para__grip {
  opacity: 0;
}

/* A paragraph that prints nothing still has a place in the list, or the
   handle to change it would have nowhere to hang. */
.para__silent {
  margin: 0;
  color: var(--faint);
  font-style: italic;
}
/* A product description left off: named where it would print, and put
   back from there. */
.para__return {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
}

/* The positions, as the document lists them: number, what it is, what it
   costs. A grid rather than a flex row so the money column lines up down
   the page, which is the only way a total is read. Each is its own handle,
   as a paragraph is: pointed at, it is washed in the tools' grey with a
   rail down its left edge and its grip appears in the sheet's margin;
   clicking it opens its editor in its place, the block card every editor
   of lines draws (`_blocks.html`). */
.sheet__lines {
  max-width: 46rem;
}
/* On the sheet a block is the line it prints as, so the card's own frame
   is drawn only around its opened editor. */
.sheet .block.block--folded {
  position: relative;
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  font-size: inherit;
}
.block__row {
  display: grid;
  grid-template-columns: 3rem minmax(0, 1fr) 7.5rem;
  gap: 0 0.8rem;
  align-items: baseline;
  padding: 0.35rem 0.6rem 0.35rem 0.5rem;
  margin: 0 -0.6rem 0 -0.5rem;
  border-bottom: 1px solid var(--hairline);
  border-left: 2px solid transparent;
  list-style: none;
}
summary.block__row {
  cursor: pointer;
}
.block__row::-webkit-details-marker {
  display: none;
}
summary.block__row:hover,
summary.block__row:focus-visible {
  background: var(--panel-2);
  border-left-color: var(--rail);
}
.block__row-pos {
  color: var(--muted);
}
.block__row-net {
  text-align: right;
}
.block__row-break {
  color: var(--faint);
  font-size: 0.8rem;
}
.block__row-note,
.block__row-unsaved {
  grid-column: 2 / -1;
  font-size: 0.78rem;
  color: var(--faint);
}
/* What a line does not do on the page reads the way the page leaves it. */
.block--off > .block__fold > .block__row > .block__row-label,
.block--off > .block__fold > .block__row > .block__row-net {
  color: var(--faint);
  text-decoration: line-through;
}
.block--alternative .block__row-net {
  color: var(--muted);
}
/* An edit typed and then folded away is still waiting for its save. */
.block__row-unsaved {
  display: none;
  color: var(--accent);
}
.block--dirty .block__row-unsaved {
  display: block;
}
.block--dirty > .block__fold > .block__row {
  border-left-color: var(--accent);
}
.block__fold[open] > .block__row {
  display: none;
}
/* Opened, it is the card, standing out of the sheet as a tool does. */
.block__fold[open] > .block__form {
  margin: 0.6rem 0 0.9rem;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  font-size: 0.9rem;
}
.block--folded.block--dirty > .block__fold[open] > .block__form {
  border-color: var(--accent);
}
.block--folded > .block__grip {
  position: absolute;
  top: 0.35rem;
  left: -1.9rem;
  z-index: 2;
  opacity: 0;
}
.block--folded:hover > .block__grip,
.block--folded:focus-within > .block__grip {
  opacity: 1;
}
.block--folded:has(.block__fold[open]) > .block__grip {
  display: none;
}
/* Closed from its head, where it was opened: the chevron of every fold,
   turned down while open. */
.block__collapse {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.4rem;
  height: 1.4rem;
  padding: 0;
  color: var(--muted);
  background: none;
  border: 0;
  border-radius: var(--radius);
  cursor: pointer;
}
.block__collapse::before {
  content: "";
  width: 0.8em;
  height: 0.8em;
  background: currentColor;
  mask: var(--glyph-fold) center / contain no-repeat;
  transform: rotate(90deg);
}
.block__collapse:hover {
  color: var(--accent);
}
[data-blocks]:not([data-enhanced]) [data-block-close] {
  display: none;
}
/* A block that saves as it is typed in says how that went, quietly, in
   its head: saving, saved, or — in the stop colour — not. */
.block__status {
  flex: none;
  font-size: 0.75rem;
  color: var(--faint);
  white-space: nowrap;
}
.block__status[data-state="failed"] {
  color: var(--stop);
}
[data-blocks][data-enhanced] .block__foot--autosave {
  display: none;
}
/* What the positions come to stands under them as the page prints it,
   not as a panel of its own. */
.sheet .totals.sheet__totals {
  max-width: 46rem;
  padding: 0.5rem 0 0;
  background: none;
  border: 0;
  border-radius: 0;
}
.sheet__adders {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 0.5rem;
  margin-top: 0.75rem;
}
.sheet__adders > .adder {
  margin-top: 0;
}
.sheet__adders > .adder > .add-bar {
  height: 100%;
  padding: 0.45rem 0.6rem;
  font-weight: 500;
}
.sheet__adders > .adder[open] {
  grid-column: 1 / -1;
  grid-row: 2;
}
/* Open, the bar is the fold's slim head rather than a slot: the closed
   bars' shared height would stretch it over the whole fold. */
.sheet__adders > .adder[open] > .add-bar {
  height: auto;
}

/* Issuing is the end of the page, so it is the one place a button is loud.
   The hint belongs to the whole row rather than to either button, and says
   what pressing the loud one does. */
.issue__actions {
  margin-top: 1.5rem;
  align-items: center;
}
.issue__actions-hint {
  flex: 1 1 14rem;
  margin: 0;
  font-size: 0.8rem;
  color: var(--muted);
}

/* The issue screen on the split below. The panel and its wrapper take no
   box of their own, so the document part and the action row are rows of
   the split and the preview shares only the first: it then stops where the
   sheet ends rather than at the button below it, and both columns start
   and end on the same lines. */
.issue__work,
#issue-panel {
  display: contents;
}
.issue__doc,
.issue__actions {
  grid-column: 1;
}
/* The two columns start on one line: the chooser's own top margin would
   otherwise hold the panel below the pane. */
.issue__doc > .editor:first-child {
  margin-top: 0;
}

/* Stacked, the draft goes between the sheet and the button that issues
   it. */
@media (max-width: 1100px) {
  .issue__cols .split__preview {
    grid-row: 2;
  }
}

/* ---- a screen beside the draft it changes ---------------------------- */

/* The work on the left, the draft as it prints on the right. The draft is
   sticky and scrolls inside itself, so the page being changed stays in
   view while the part that changes it is worked on further down. The work
   column takes the larger share: it holds the editors, while a page of A4
   reads at any width that keeps its type legible.

   The preview stretches over its grid row and the pane is sticky inside
   it, so the pane is held within that row and stops where the work beside
   it ends. */
.split {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 6fr);
  column-gap: 1.5rem;
  align-items: start;
}
.split__work {
  grid-column: 1;
  min-width: 0;
}
/* Both columns start on one line: an editor box opening the work column
   would otherwise hold it below the pane by its own top margin. */
.split__work > .editor:first-child {
  margin-top: 0;
}
/* The pane is as tall as the screen, and as a grid item it would make its
   row at least that tall — so a short work column would leave a screen's
   height of nothing between its last box and what follows it. Contained,
   the pane adds no height to the row: the row is the work's, the pane
   stretches over it and is sticky within it, and where the work is
   shorter than the pane, the pane runs on down its own column past the
   row, where nothing is. */
.split__preview {
  grid-column: 2;
  grid-row: 1;
  align-self: stretch;
  contain: size;
}
/* What adds to the work — a fold to add a line, a form to add a block —
   stands under it in a row of its own, so the draft beside the work ends
   with the last box instead of running down past it. */
.split__tail {
  grid-column: 1;
  grid-row: 2;
}
.split__preview .pdfview {
  position: sticky;
  top: 1rem;
  height: calc(100vh - 2rem);
}
/* A heading over the work, where a screen has one, stands in a row of its
   own above both columns, so the draft starts level with the first box it
   draws rather than with the heading over it. Everything else steps down
   a row. */
.split__lead {
  grid-column: 1;
  grid-row: 1;
}
.split:has(> .split__lead) > .split__work,
.split:has(> .split__lead) > .split__preview {
  grid-row: 2;
}
.split:has(> .split__lead) > .split__tail {
  grid-row: 3;
}
/* Below this both would be too narrow to work in, so they stack — and a
   stacked preview must not be sticky, or it covers the work under it. */
@media (max-width: 1100px) {
  .split {
    grid-template-columns: minmax(0, 1fr);
  }
  .split__preview {
    grid-column: 1;
    grid-row: 3;
    margin-top: 1.5rem;
    /* Stacked, the pane is a block in the flow and needs its height. */
    contain: none;
  }
  .split:has(> .split__lead) > .split__preview {
    grid-row: 4;
  }
  .split__preview .pdfview {
    position: static;
    height: min(80vh, 900px);
  }
}

/* ---- the Briefpapier (`templates/letterheads/index.html`) ------------ */

/* Each sheet is shown by sight — the image drawn as a small page lying on
   the panel — because two sheets differ in their artwork, which a name and
   a date never say. The sheet in use and the one being tried are drawn
   larger; the earlier ones are a list to pick from. Whichever the preview
   beside them shows carries the accent rule on its left edge. */
.letterheads__head,
.letterheads > h2 {
  font-size: 1rem;
  font-weight: 600;
  margin: 1.5rem 0 0.5rem;
}
.letterheads__head {
  margin-top: 0;
}
.letterhead {
  display: flex;
  align-items: flex-start;
  gap: 1.25rem;
  color: inherit;
}
a.letterhead:hover {
  border-color: var(--accent);
  text-decoration: none;
}
.letterhead.is-shown,
.letterhead-list__item.is-shown {
  box-shadow: inset 3px 0 0 var(--accent), var(--shadow);
}
.letterhead__thumb {
  flex: none;
  display: block;
  width: 4rem;
  aspect-ratio: 210 / 297;
  object-fit: cover;
  background: #fff;
  border: 1px solid var(--hairline);
  box-shadow: 0 1px 3px rgba(28, 27, 25, 0.14);
}
.letterhead__thumb--large {
  width: 7.5rem;
}
.letterhead__facts {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  min-width: 0;
  flex: 1;
  font-size: 0.9rem;
}
.letterhead__name {
  font-size: 1rem;
  overflow-wrap: anywhere;
}
.letterhead__warn {
  color: var(--stop);
}
.letterhead__note {
  margin: 0.5rem 0 0;
  color: var(--muted);
}
.letterhead__adopt {
  margin-top: 0.75rem;
}
.letterhead__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.75rem;
}
.letterhead-upload {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: end;
  gap: 0.5rem 0.75rem;
}
.letterhead-upload__hint {
  grid-column: 1 / -1;
  margin: 0;
}
.letterhead-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.letterhead-list__item {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.75rem 1.25rem;
}
.letterhead-list__item + .letterhead-list__item {
  border-top: 1px solid var(--hairline);
}
.letterhead-list__pick {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex: 1;
  min-width: 0;
  color: inherit;
}
.letterhead-list__pick:hover .letterhead__name {
  color: var(--accent);
}
@media (max-width: 30rem) {
  .letterhead__thumb--large {
    width: 5rem;
  }
  .letterhead-list__item {
    flex-wrap: wrap;
  }
}

/* ---- a PDF drawn into the page (`static/js/pdfpreview.js`) ---------- */

/* The pane is given a height by where it stands; the scroll box takes what
   the bar leaves of it. */
.pdfview {
  display: flex;
  flex-direction: column;
  height: min(86vh, 1200px);
  background: var(--panel-2);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
}
.pdfview__bar {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--hairline);
  font-size: 0.8rem;
}
.pdfview__label {
  color: var(--muted);
}
.pdfview__sample {
  margin-left: 0.25rem;
  color: var(--faint);
}
.pdfview__status {
  margin: 0.5rem 0.75rem 0;
}
/* Positioned, because a new version is laid out over the old one before
   the swap and has to measure its pages against the same top edge. A size
   container, so a page can be held to the height it is shown in. */
.pdfview__scroll {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 0.75rem;
  container-type: size;
}
.pdfview__pages {
  display: grid;
  gap: 0.75rem;
}
.pdfview__pages--incoming {
  position: absolute;
  inset: 0.75rem 0.75rem auto;
  visibility: hidden;
}
/* The page box is sized from the PDF before anything is drawn, so the
   scroll height is right from the start and the swap does not jump. It is
   never wider than a whole page can be shown at: a narrower page that is
   read in one look beats a wider one that is always cut off. The cap
   assumes A4 portrait, which every document the frame sets is. */
.pdfview__page {
  width: 100%;
  max-width: calc(100cqh * 210 / 297);
  margin-inline: auto;
  background: var(--panel);
  box-shadow: var(--shadow);
}
.pdfview__page canvas {
  display: block;
  width: 100%;
  height: 100%;
}
.pdfview__frame {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}
@media (max-width: 1100px) {
  .pdfview {
    height: min(80vh, 900px);
  }
}

/* ---- the head of a Vorgang's sub-page -------------------------------- */

/* One Vorgang's positions, invoices or next document. The thread's name
 * sits under the title rather than as a number in the corner: these pages
 * are reached from several directions, and "Rechnungen" alone does not say
 * whose — nor would a project number, which several Vorgänge share. */
.subhead {
  margin-bottom: 1.4rem;
}
.subhead__title {
  margin: 0;
}
.subhead__case {
  margin: 0.1rem 0 0;
  color: var(--muted);
}
.subhead__case .num {
  font-family: var(--font-mono);
}

/* A panel's heading sits above the panel, not inside it: the panel has no
   padding of its own because a table fills it to the edge. */
.issue__history-label {
  margin-top: 2rem;
}

/* ---- the blocks a document body is assembled from -------------------- */

/* A Vorlage's lines and a Vorgang's body (`_blocks.html`). Each block is a
   card whose head bar says what kind of block it is and carries what is
   done *to* the block — its heading, whether it prints, the menu — so the
   fields under it are only the block's content. */
.blocks__intro > h2 {
  margin-top: 0;
  margin-bottom: 0.25rem;
}
.blocks__intro > p {
  margin-top: 0;
}
.blocks {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}
.blocks__empty {
  margin: 0;
  padding: 1.5rem 1rem;
  text-align: center;
  color: var(--muted);
  border: 1px dashed var(--hairline);
  border-radius: var(--radius);
}
.block {
  font-size: 0.9rem;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.block__head {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.35rem 0.4rem 0.35rem 0.3rem;
  background: var(--panel-2);
  border-bottom: 1px solid var(--hairline);
  border-radius: var(--radius) var(--radius) 0 0;
}
/* The grip only means something where the script that drags is running;
   without it the menu's up and down are the way to move a block. */
.block__grip {
  display: flex;
  padding: 0.3rem 0.3rem;
  color: var(--rail);
  cursor: grab;
}
.block__grip:hover {
  color: var(--ink);
}
.blocks:not([data-enhanced]) .block__grip {
  display: none;
}
/* The number: the heading's box, as wide as a tender's Ordnungszahl. */
.block__title.block__title--number {
  flex: none;
  width: 4.5rem;
  font-size: 0.85rem;
}
.block__kind {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding-right: 0.2rem;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
}
.block__icon {
  width: 16px;
  height: 16px;
  flex: none;
}
/* The heading is edited where it is read, in the head bar: an input that
   looks like the title it is until pointed at. */
.block__title {
  flex: 1;
  min-width: 0;
  font: inherit;
  font-size: 0.95rem;
  font-weight: 600;
  padding: 0.25rem 0.45rem;
  color: var(--ink);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius);
}
/* A position's price line, boxed as one: the words and the sum on its
   head, as the page prints them, the figures under them. */
.block__price {
  display: grid;
  gap: 0.6rem;
  margin-top: 0.75rem;
  padding: 0.5rem 0.75rem 0.75rem;
  background: var(--ground);
  border-radius: var(--radius);
}
.block__price-head {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}
/* The words: the heading's box, at the weight of body text, its text
   lined up with the labels under it. */
.block__title--price {
  margin-left: -0.45rem;
  font-size: 0.9rem;
  font-weight: 400;
}
.block__price-sum {
  font-size: 0.9rem;
  font-weight: 600;
  white-space: nowrap;
}
.block__price > .block__counts {
  margin-top: 0;
}
.block__title::placeholder {
  font-weight: 400;
  color: var(--faint);
}
.block__title:hover {
  border-color: var(--hairline);
  background: var(--panel);
}
.block__title:focus {
  outline: none;
  border-color: var(--accent);
  background: var(--panel);
}
.block__rule {
  flex: 1;
  border-top: 1px dashed var(--rail);
}
/* Bracketed as the document prints it: offered, not counted. */
.block__amount--aside {
  font-weight: 400;
  color: var(--faint);
}
.block__amount--aside::before {
  content: "(";
}
.block__amount--aside::after {
  content: ")";
}

/* Head-bar buttons: an icon, quiet until pointed at. */
.block__eye,
.block__menu-toggle {
  display: inline-flex;
  padding: 0.3rem;
  color: var(--muted);
  border: 1px solid transparent;
  border-radius: var(--radius);
  cursor: pointer;
}
.block__eye:hover,
.block__menu-toggle:hover,
.block__menu[open] .block__menu-toggle {
  color: var(--ink);
  border-color: var(--hairline);
  background: var(--panel);
}
.block__eye:has(input:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.block__eye-shut {
  display: none;
}
.block__eye:has(input:not(:checked)) .block__eye-open {
  display: none;
}
.block__eye:has(input:not(:checked)) .block__eye-shut {
  display: block;
}
.block__eye:has(input:not(:checked)) {
  color: var(--ink);
  border-color: var(--hairline);
  background: var(--panel);
}
.block__menu {
  position: relative;
}
.block__menu-toggle {
  list-style: none;
}
.block__menu-toggle::-webkit-details-marker {
  display: none;
}
.block__menu-list {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  z-index: 20;
  min-width: 11rem;
  padding: 0.3rem 0;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: 0 10px 30px rgba(28, 27, 25, 0.14);
}
.block__menu-item {
  display: block;
  width: 100%;
  padding: 0.45rem 0.9rem;
  font: inherit;
  font-size: 0.85rem;
  text-align: left;
  color: var(--ink);
  background: none;
  border: 0;
  cursor: pointer;
}
.block__menu-item:hover:not(:disabled) {
  background: var(--panel-2);
}
.block__menu-item:disabled {
  color: var(--faint);
  cursor: default;
}
.block__menu-item--stop {
  margin-top: 0.3rem;
  border-top: 1px solid var(--hairline);
  padding-top: 0.6rem;
  color: var(--stop);
}

.block__body {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  padding: 0.75rem 0.9rem 0.9rem;
}
.block__foot {
  display: flex;
  justify-content: flex-end;
  padding: 0 0.9rem 0.9rem;
}
/* With the script, a block offers its save once there is something to
   save, and marks itself until it is saved. */
.blocks[data-enhanced] .block:not(.block--dirty) .block__foot {
  display: none;
}
.block--dirty {
  border-color: var(--accent);
}
.block--dragging {
  opacity: 0.4;
}

/* A hidden block stays editable but reads as set aside: dashed edge, the
   content faded, and a hatched strip saying what that means. */
.block--off {
  border-style: dashed;
  box-shadow: none;
}
.block--off .block__body,
.block--off .block__title,
.block--off .block__kind {
  opacity: 0.55;
}
.block__off {
  margin: 0;
  padding: 0.35rem 0.9rem;
  font-size: 0.8rem;
  color: var(--muted);
  background: repeating-linear-gradient(
    -45deg,
    transparent 0 6px,
    rgba(28, 27, 25, 0.05) 6px 12px
  );
  border-bottom: 1px solid var(--hairline);
}

/* A page break has nothing to edit, so it is drawn as the cut it is
   rather than as a card. */
.block--page-break {
  background: transparent;
  box-shadow: none;
  border-style: dashed;
}
.block--page-break .block__head {
  background: transparent;
  border-bottom: 0;
}

/* The technical description: attribute and value, one pair per row. The
   attribute is a short name, the value the part that varies and may run
   over lines, so the value takes the larger share. Grip and cross are
   tracks of their own, kept even where the script is missing so the
   columns line up with the head either way. */
.spec {
  --spec-columns: 1.1rem minmax(0, 2fr) minmax(0, 5fr) 1.8rem;
  --row-lead: calc(1.1rem + 0.4rem);
  --row-tail: calc(1.8rem + 0.4rem);
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
.spec__head,
.spec__row {
  display: grid;
  grid-template-columns: var(--spec-columns);
  gap: 0.4rem;
  align-items: start;
}
.spec__head {
  font-size: 0.8rem;
  color: var(--muted);
}
.spec__head > :first-child {
  grid-column: 2;
}
.spec__rows {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.spec__row {
  position: relative;
}
.spec__value {
  min-height: 2rem;
  resize: vertical;
  line-height: 1.35;
}
.spec__grip {
  display: flex;
  justify-content: center;
  padding-top: 0.5rem;
  color: var(--rail);
  cursor: grab;
}
.spec__grip:hover {
  color: var(--ink);
}
.spec__drop {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 2rem;
  color: var(--faint);
  background: none;
  border: 1px solid transparent;
  border-radius: var(--radius);
  cursor: pointer;
}
.spec__drop:hover {
  color: var(--stop);
  border-color: var(--hairline);
}
.spec__row--dragging {
  opacity: 0.4;
}
/* A position's attribute table, folded while it is empty. */
.block__spec > .spec {
  margin-top: 0.4rem;
}
/* Grip, cross and the add button all work in the page; without the
   script they would do nothing, so they are not offered. */
.spec:not([data-enhanced]) .spec__grip,
.spec:not([data-enhanced]) .spec__drop,
.spec:not([data-enhanced]) .spec__add {
  visibility: hidden;
}
.spec__suggest {
  position: absolute;
  top: calc(100% + 2px);
  left: calc(
    1.1rem + 0.4rem + (100% - 1.1rem - 1.8rem - 1.2rem) * 2 / 7 + 0.4rem
  );
  right: calc(1.8rem + 0.4rem);
  z-index: 20;
  margin: 0;
  padding: 0.25rem 0;
  list-style: none;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: 0 10px 30px rgba(28, 27, 25, 0.14);
}
.spec__suggest-item {
  padding: 0.35rem 0.6rem;
  font-size: 0.85rem;
  white-space: pre-line;
  cursor: pointer;
}
.spec__suggest-item:hover,
.spec__suggest-item--active {
  background: var(--accent-soft);
}

/* Counted or alternative: two named choices side by side. */
.block__counts {
  display: flex;
  flex-wrap: wrap;
  width: fit-content;
  margin: 0;
  padding: 0;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  overflow: hidden;
}
.block__count {
  position: relative;
}
.block__count input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.block__count span {
  display: block;
  padding: 0.3rem 0.75rem;
  font-size: 0.82rem;
  color: var(--muted);
  background: var(--panel);
  cursor: pointer;
}
.block__count + .block__count span {
  border-left: 1px solid var(--hairline);
}
.block__count input:checked + span {
  color: var(--accent);
  background: var(--accent-soft);
  font-weight: 600;
}
.block__count:has(input:focus-visible) span {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* What adds a block: the add bar, and under it one button per kind saying
   what that kind is for. */
.adder {
  margin-top: 0.75rem;
}
.adder__kinds {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 0.5rem;
  margin-top: 0.5rem;
}
.adder__kind {
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: 0.65rem;
  row-gap: 0.1rem;
  align-items: start;
  padding: 0.7rem 0.8rem;
  font: inherit;
  text-align: left;
  color: var(--ink);
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  cursor: pointer;
}
/* A spare block with its delete, or the "i" for why it stays, in the
   corner of its card (`document_types/_parts.html`). */
.adder__spare {
  position: relative;
  display: grid;
}
.adder__spare > .adder__kind {
  padding-right: 2.4rem;
}
.adder__corner {
  position: absolute;
  top: 0.35rem;
  right: 0.35rem;
}
.adder__kind:hover {
  border-color: var(--accent);
}
.adder__kind .block__icon {
  grid-row: span 2;
  width: 20px;
  height: 20px;
  color: var(--accent);
}
.adder__name {
  font-size: 0.9rem;
  font-weight: 600;
}
.adder__what {
  font-size: 0.8rem;
  color: var(--muted);
}
/* A Vorlage has no icon, so its card is one column: its name and how
   much it brings. */
.adder__kind--template {
  grid-template-columns: 1fr;
  row-gap: 0.2rem;
}
.adder__kind--template .adder__name {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
}
.adder__hint {
  margin: 0.5rem 0 0;
  font-size: 0.8rem;
  color: var(--muted);
}
.adder__label {
  margin: 0.75rem 0 0;
  font-size: 0.8rem;
  color: var(--muted);
}
.adder__label + .adder__kinds {
  margin-top: 0.35rem;
}
/* A new block by name: one line, the box taking what the button leaves. */
.adder__new {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.75rem;
}
.adder__new > .field {
  flex: 1;
}

/* A block whose wording other documents share says so under its head, as
   information rather than warning: an edit here reaches them too. */
.block__shared {
  margin: 0;
  padding: 0.35rem 0.9rem;
  font-size: 0.8rem;
  color: var(--accent);
  background: var(--accent-soft);
  border-bottom: 1px solid var(--hairline);
}

/* What a body comes to, under its last block: the document's own foot,
   figures right-aligned in one column so they are read down rather than
   along a line, the net set apart as the figure the page exists for. */
.totals {
  padding: 0.8rem 0.9rem;
  font-size: 0.9rem;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
}
.totals__rows {
  display: grid;
  grid-template-columns: 1fr auto;
  row-gap: 0.25rem;
  margin: 0;
  text-align: right;
}
.totals__rows dt {
  color: var(--muted);
}
/* The space between label and figure is the figure's own, so the rule over
   the net runs unbroken across both. */
.totals__rows dd {
  margin: 0;
  padding-left: 1.5rem;
}
.totals__rows .totals__net {
  padding-top: 0.35rem;
  border-top: 1px solid var(--hairline);
  color: var(--ink);
  font-weight: 600;
}
.totals__discount {
  margin-top: 0.4rem;
  text-align: right;
}
.totals__discount[open] {
  text-align: left;
}
.totals__discount[open] > .editor__toggle {
  margin-bottom: 0.5rem;
}

/* A Dokumentart's letter, top to bottom: the opening, where a Vorgang's
   positions go, the closing. The positions are drawn as a gap in the
   letter rather than a card, because nothing about them is edited here. */
.type-letter__slot {
  margin: 0 0 0.5rem;
  font-size: 1.05rem;
}
.type-letter__slot:not(:first-child) {
  margin-top: 1.5rem;
}
.type-letter__positions {
  margin: 1.5rem 0 0;
  padding: 1.1rem 1rem;
  text-align: center;
  font-size: 0.85rem;
  color: var(--muted);
  border-top: 1px dashed var(--rail);
  border-bottom: 1px dashed var(--rail);
}

/* ---- a short row of figures ------------------------------------------ */

/* What a Vorgang agreed, what has gone out, what is left. Three labelled
 * amounts rather than one sentence of them: they are compared against each
 * other, and a reader compares down a column, not along a line. The last
 * leads because it is the one anybody opens this screen to find — the other
 * two are what it was computed from. */
.tally {
  display: flex;
  flex-wrap: wrap;
  gap: 0 2.5rem;
  margin: 0 0 1.2rem;
  padding: 0.7rem 0;
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
}
.tally__item dt {
  font-size: 0.78rem;
  color: var(--muted);
}
.tally__item dd {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 1.1rem;
  color: var(--muted);
}
.tally__item--lead dd {
  color: var(--ink);
  font-weight: 600;
}

/* The amount a block of figures adds up to — a body's net, an invoice's
 * gross, what is still due. Heavier than the lines it follows, but still a
 * figure: it was borrowing the Vorgang-title class, which made a sum read
 * as a heading and inherited a size that belongs to a page's name. */
.figure-lead {
  margin: 0.5rem 0 0;
  font-size: 1.1rem;
  font-weight: 600;
}

/* The moment a choice lands on the sheet.
 *
 * A type whose wording happens to match the one before it changes nothing a
 * reader can see above the fold — the opening blocks are shared across most
 * offer types — and a screen that looks inert reads as broken. So the sheet
 * says it was re-read: HTMX puts `htmx-settling` on what it has just swapped
 * in and takes it off a frame later, which turns a colour set under that
 * class into a fade back to the page.
 *
 * On the document, not on the control: the question being answered is "is
 * this still what would be issued", and the document is where a person is
 * looking for the answer. */
.sheet,
.sheet__mark {
  transition: background-color 500ms ease-out;
}
.sheet.htmx-settling,
#issue-panel.htmx-settling .sheet,
.sheet__mark.htmx-settling {
  background-color: var(--accent-soft);
  transition: none;
}
@media (prefers-reduced-motion: reduce) {
  .sheet,
  .sheet__mark {
    transition: none;
  }
}

/* Einheiten: a table of rows edited in place. Each row is a form of its
   own, so the cells hold controls rather than values, and what a row can
   do is drawn the way the block editor draws it — grip, eye, cross. */
.units tbody td {
  padding: 0.45rem 0.5rem;
  vertical-align: top;
}
.units thead th {
  padding-left: 0.5rem;
  padding-right: 0.5rem;
}
.units .col-center {
  text-align: center;
}
.units__grip {
  display: flex;
  justify-content: center;
  padding: 0.5rem 0;
  color: var(--rail);
  cursor: grab;
  border-radius: var(--radius);
}
.units__grip:hover,
.units__grip:focus-visible {
  color: var(--ink);
}
.units__grip:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.units__row--dragging {
  opacity: 0.4;
}
/* Where the unit is offered: two switches, lit when on. */
.units__chips {
  display: flex;
  gap: 0.3rem;
  padding-top: 0.3rem;
}
.units__chip span {
  display: inline-block;
  font-size: 0.78rem;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  border: 1px solid var(--hairline);
  background: var(--panel);
  color: var(--muted);
  cursor: pointer;
  user-select: none;
}
.units__chip:hover span {
  border-color: var(--accent);
  color: var(--ink);
}
.units__chip input:checked + span {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent);
  font-weight: 600;
}
.units__chip input:focus-visible + span {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.units td .block__eye {
  margin-top: 0.1rem;
}
/* A unit taken out of the choices stays editable, but reads as set aside. */
.units__row:has([data-unit-eye]:not(:checked))
  td:not(:has(.block__eye)) {
  opacity: 0.5;
}
.units__used {
  display: inline-block;
  padding-top: 0.5rem;
  font-size: 0.85rem;
  color: var(--muted);
  cursor: help;
}
.units__drop,
.row-drop {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  color: var(--faint);
  background: none;
  border: 1px solid transparent;
  border-radius: var(--radius);
  cursor: pointer;
}
.units__drop:hover,
.units__drop:focus-visible,
.row-drop:hover,
.row-drop:focus-visible {
  color: var(--stop);
  border-color: var(--hairline);
}
.units__error {
  margin: 0.3rem 0 0;
  font-size: 0.8rem;
  color: var(--stop);
}
/* The save button waits for a change; the grip, the eye's own save and the
   add button are the script's, so the scriptless page goes without them. */
.units[data-enhanced] .units__row:not(.units__row--dirty) .units__save {
  visibility: hidden;
}
.units:not([data-enhanced]) .units__grip,
.units:not([data-enhanced]) [data-unit-new-cancel] {
  visibility: hidden;
}
.units[data-enhanced]:not(.units--adding) .units__new {
  display: none;
}
.units__new td {
  background: var(--panel-2);
}
.units__add {
  margin-top: 0.75rem;
}
.units:not([data-enhanced]) + .units__add,
.units--adding + .units__add {
  display: none;
}

/* The order checklist (`cases/_checklist.html`): one list filled in from
   the top — the checklist's sections, then the decisions — with its
   printout at the foot. */
.order__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  margin-top: 1.5rem;
}
.order__section {
  display: block;
  margin: 1.4rem 0 0.3rem;
}
/* One line per item: what it asks, the way to where its record lives, who
   answered it when, and the answer. The answer stands at the right edge,
   in one column down the list, so the list reads as a form being filled
   in; on a narrow screen it wraps under the text. */
.order__items {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--hairline);
}
.order__item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.3rem 0.9rem;
  padding: 0.45rem 0;
  border-bottom: 1px solid var(--hairline);
}
.order__text {
  flex: 1 1 22rem;
  min-width: 0;
}
.order__mark {
  font-size: 0.8rem;
  white-space: nowrap;
}
.order__link {
  font-size: 0.85rem;
  white-space: nowrap;
}
.order__link:empty {
  display: none;
}
/* Not applicable: the item stays readable, struck through, as the printed
   form shows it. */
.order__item--not_applicable .order__text {
  color: var(--muted);
  text-decoration: line-through;
}
.order__item .seg--field {
  height: 1.8rem;
  margin-left: auto;
}
/* An item done is the one answer the list is worked towards, so it is
   the one drawn in the success green, with a tick: a column of them reads
   as progress at a glance. Not applicable stays the plain chosen ink. */
/* Room for the tick on every such option, so a ticked row's control
   stands in the same column as the rest. */
.order__item .seg__opt:has(> .seg__radio[value="done"]) {
  padding: 0 1.05rem;
}
.order__item .seg__opt:has(> .seg__radio[value="done"]:checked) {
  background: var(--ok);
  color: #fff;
}
.order__item .seg__opt:has(> .seg__radio[value="done"]:checked)::before {
  content: "";
  position: absolute;
  left: 0.3rem;
  top: 50%;
  width: 0.65rem;
  height: 0.65rem;
  translate: 0 -50%;
  background: currentColor;
  mask: var(--glyph-check) center / contain no-repeat;
}
.order__item .info {
  display: inline-block;
  vertical-align: middle;
}
/* A value the reader may see but not set: where the control would be, on
   the control's line. */
.field__static {
  padding: 0.35rem 0;
}

/* A Leistungsdatum or a Leistungszeitraum, switched on the line itself:
   the first day and the period's label show by style alone while
   *Zeitraum* is chosen, so the second date opens at once rather than when
   the save answers. */
.span-field__line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
}
.span-field__line > .field__input {
  flex: 1 1 8.5rem;
}
.span-field__from,
.span-field__period {
  display: none;
}
.span-field:has(.seg__radio[value="period"]:checked) .span-field__from {
  display: block;
}
.span-field:has(.seg__radio[value="period"]:checked) .span-field__period {
  display: inline;
}
.span-field:has(.seg__radio[value="period"]:checked) .span-field__date {
  display: none;
}

/* The issue screen's fold of what the Vorgang already settled. */
.issue__more {
  margin-top: 0.9rem;
}
.issue__more > .editor__row {
  margin-top: 0.6rem;
}

/* A line switched on that the document shown leaves off — a Nachtrag's
   already offered work — reads faint rather than struck: it is still part
   of the job, only not on this page. */
.block--away > .block__fold > .block__row > .block__row-label,
.block--away > .block__fold > .block__row > .block__row-net,
.block--away > .block__fold > .block__row > .block__row-pos {
  color: var(--faint);
}
/* What went out of a line before: one quiet line under its reading. */
.block__row-chip {
  grid-column: 2 / -1;
  font-size: 0.78rem;
  color: var(--muted);
}
.block__history {
  margin: 0;
  padding: 0.35rem 0.9rem;
  font-size: 0.8rem;
  color: var(--muted);
}
.sheet__part-note {
  margin: 0 0 0.5rem;
  font-size: 0.85rem;
  color: var(--muted);
}

/* An Abschlag by positions: one row per position, its sum and how far it
   was billed before, and the two ways to say how far it is billed now. */
.bill__form {
  display: grid;
  gap: 0.75rem;
  margin-top: 0.5rem;
}
.bill__rows {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 7rem 3.5rem 4.5rem 7.5rem;
  gap: 0.35rem 0.75rem;
  align-items: center;
  font-size: 0.88rem;
}
.bill__head {
  font-size: 0.75rem;
  color: var(--muted);
}
.bill__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.bill__qty {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}
.bill__qty > .field__input {
  width: 4.5rem;
}
.bill__right {
  text-align: right;
}
.bill__form > .btn {
  justify-self: start;
}

/* A Nachtrag's answer in the issued list: the one given, and the other
   one click away. */
.issue__answer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
}

/* A Preisanfrage's sheets (`price_requests/detail.html`): a refusal under
   the field it is about, and the suppliers nobody asked yet as a row of
   buttons under the table. */
.sheet__error {
  display: block;
  margin-top: 0.3rem;
  font-size: 0.8rem;
  color: var(--stop);
}
.sheet__error:empty {
  display: none;
}
.suggest {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.75rem;
  font-size: 0.85rem;
}

/* A Preisanfrage, laid out as its mail. Its sheets are panels holding a
   table, so they have none of the document sheet's padding. Their rows
   are as tall as their inputs and centre what they hold, the leading cell
   included: `.col-name` is a flex box, which as a cell neither fills a
   row taller than its text — the row's hover tint then stops short of it,
   notched — nor centres in it, so here it is a cell again. Nothing here
   follows the name inside it, which is what the flex box lays out. */
.pr-sheet {
  padding: 0;
}
.pr-sheet table {
  min-width: 38rem;
}
.pr-sheet tbody td {
  vertical-align: middle;
}
.pr-sheet tbody td.col-name {
  display: table-cell;
}
/* A request's answers: one column per supplier, its head the name, how
   far its answer is and the act that keys one in, stacked so a column
   stays as narrow as its figures. */
.pr-answers .pr-answers__supplier {
  vertical-align: top;
}
.pr-answers__supplier > * {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pr-answers__supplier > .btn {
  display: inline-flex;
  margin-top: 0.35rem;
}
.pr-answers__count {
  font-weight: 400;
  font-size: 0.8rem;
}
.pr-answers__cell {
  white-space: nowrap;
}
.pr-cheapest {
  font-weight: 700;
}
.pr-answer {
  margin-top: 1rem;
}
.pr-answer__title {
  margin: 0;
  font-weight: 600;
}
/* Title, the two fields, the lines: close together, one answer. */
.pr-answer__body {
  gap: 0.6rem;
}
.pr-answer__body > .editor__row {
  margin: 0;
}
.pr-answer__body .field__label .info {
  margin-left: 0.3rem;
}
.pr-answer__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-top: 1rem;
}
.pr-answer__acts {
  display: flex;
  gap: 0.6rem;
}
/* A line the supplier cannot supply: struck through, its figures set
   aside, and said in words beside its name. Read off the switch alone,
   so it needs no script. */
.pr-answer__declined {
  display: none;
  margin-left: 0.5rem;
  font-size: 0.8rem;
  color: var(--stop);
}
.pr-answer__line:has(.decline input:checked) .pr-answer__what {
  text-decoration: line-through;
  color: var(--muted);
}
.pr-answer__line:has(.decline input:checked) .pr-answer__declined {
  display: inline;
}
.pr-answer__line:has(.decline input:checked) td:not(.pr-answer__switch) input {
  opacity: 0.35;
  text-decoration: line-through;
}
.pr-answer__switch {
  text-align: center;
}
/* The switch itself: a bordered square with the refusal's mark, filled in
   the stop colour while the line is refused. */
.decline {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.9rem;
  height: 1.9rem;
  color: var(--muted);
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  cursor: pointer;
}
.decline input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
}
.decline:hover {
  color: var(--stop);
  border-color: var(--stop);
}
.decline:has(input:checked) {
  color: var(--panel);
  background: var(--stop);
  border-color: var(--stop);
}
.decline:has(input:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* The mail's header in the record's head: a label column as a mail client
   has, the subject typed after its fixed start, the suppliers' table as
   the Bcc line. */
.envelope {
  display: grid;
  gap: 0.6rem;
}
.envelope__field {
  display: grid;
  grid-template-columns: 4.5rem minmax(0, 1fr);
  align-items: center;
  gap: 0.75rem;
  min-height: 2.2rem;
}
.envelope__field--list {
  align-items: start;
}
.envelope__field--list > .envelope__label {
  padding-top: 0.6rem;
}
.envelope__label {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.85rem;
  color: var(--muted);
}
.envelope__value {
  min-width: 0;
  overflow-wrap: anywhere;
}
.envelope__value .sheet {
  box-shadow: none;
}
.envelope__subject {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.envelope__fixed {
  flex: none;
}
/* The number does not exist until the mail is made; its place says so
   rather than showing one that might not be it. */
.envelope__pending {
  margin-left: 0.15rem;
  padding: 0 0.3rem;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--faint);
  border: 1px dashed var(--rail);
  border-radius: var(--radius);
}
.envelope__subject .field__input {
  flex: 1 1 auto;
  min-width: 0;
}
/* The mail's fixed text above its lines, and the note under them saying
   what Outlook adds. */
.pr-letter {
  padding: 1rem 1rem 0.25rem;
}
.pr-letter p {
  margin: 0 0 0.75rem;
}
.pr-letter__foot {
  margin: 0;
  padding: 0.6rem 1rem;
  font-size: 0.8rem;
  color: var(--faint);
  border-top: 1px solid var(--hairline);
}
/* The foot of a sheet that adds to it: a search with its matches under
   the lines, the suggestions and the add bar under the suppliers. */
.pr-add {
  flex-direction: column;
  align-items: stretch;
  gap: 0.6rem;
}
.pr-add__find {
  display: flex;
  align-items: center;
  gap: 1rem;
}
.pr-add__find .field__input {
  flex: 1 1 auto;
}
.pr-add__browse {
  flex: none;
  font-size: 0.85rem;
}
.pr-add .suggest {
  margin-top: 0;
}
.pr-add__supplier {
  margin-top: 0;
}
.pr-matches:empty {
  display: none;
}
.pr-matches__list {
  margin: 0;
  padding: 0;
  list-style: none;
}
.pr-match {
  display: grid;
  grid-template-columns: 6rem minmax(0, 1fr) auto;
  align-items: center;
  gap: 0.75rem;
  min-height: 2.4rem;
  padding: 0 0.4rem;
  font-size: 0.9rem;
  border-top: 1px solid var(--hairline);
}
.pr-match:first-child {
  border-top: none;
}
.pr-match__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pr-match__present {
  font-size: 0.85rem;
}
.pr-matches__none,
.pr-matches__more {
  margin: 0;
  padding: 0 0.4rem;
  font-size: 0.85rem;
}

/* The grip a row is dragged by (`static/js/reorder.js`). A button, so the
   arrow keys move the row while it has focus. */
.sheet td.col-grip {
  padding-left: 0.25rem;
  padding-right: 0;
}
.sort-grip {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 2rem;
  padding: 0;
  color: var(--rail);
  background: none;
  border: 1px solid transparent;
  border-radius: var(--radius);
  cursor: grab;
}
.sort-grip:hover,
.sort-grip:focus-visible {
  color: var(--ink);
}
.sort-grip:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.sort-row--dragging > td {
  background: var(--accent-soft);
  opacity: 0.6;
}


/* An invoice's record: where it stands, its terms, its payments, stacked
   in the work column beside the document, or across the page where there
   is none to show. */
.invoice-record {
  display: grid;
  /* One track no wider than the column: a table's floor scrolls inside
     its own box rather than widening the track under the document. */
  grid-template-columns: minmax(0, 1fr);
  gap: 1.25rem;
  align-content: start;
}
/* The head's body: the dates on the left, the account on the right, which
   is read down a column of figures to what is open. */
.invoice-cover {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: start;
  gap: 1rem 3rem;
}
.ledger {
  display: grid;
  grid-template-columns: max-content max-content;
  gap: 0.2rem 2rem;
  margin: 0;
}
.ledger dt {
  color: var(--muted);
}
.ledger dd {
  margin: 0;
  text-align: right;
}
/* The line the account is summed under, one element across both columns
   so it cannot break at the gap between them. */
.ledger__rule {
  grid-column: 1 / -1;
  margin: 0.2rem 0 0.1rem;
  border-top: 1px solid var(--hairline);
}
.ledger__total {
  font-weight: 600;
}
/* A chain's two parts: this invoice's own lines, and what earlier ones
   still owe on it. Labels, not figures, so they take the label's ink. */
.ledger__section {
  margin-top: 0.35rem;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--ink);
}
.ledger > .ledger__section:first-child {
  margin-top: 0;
}
dt.ledger__total {
  color: var(--ink);
}
.invoice-payments .results {
  margin: 0;
}
/* Beside the document the column is narrower than the floor a list's table
   keeps for scrolling (`.results .table--cols`). These tables need none:
   their dates and figures are rem tracks, and the one flexible column
   gives way. */
.invoice-record .results .table--cols {
  min-width: 0;
}
/* A grid gap spaces the record, not the head's own margin. */
.invoice-record > .project-head {
  margin-bottom: 0;
}
.invoice-payments__form {
  margin-top: 0.5rem;
}
.invoice-payments__acts {
  display: flex;
  justify-content: flex-end;
  margin-top: 0.75rem;
}
/* An invoice's siblings: number, kind, date, sum and where it stands, the
   sum aligned on its decimals like any column of figures. */
.invoice-others .siblings__list {
  grid-template-columns: max-content minmax(0, 1fr) max-content max-content max-content;
  max-width: 48rem;
}
.invoice-others__sum {
  text-align: right;
}

/* ---- Bestellungen ------------------------------------------------------- */

/* The chooser before an article goes onto an order: the articles, the
   supplier, which draft — one narrow panel, read top to bottom. */
.po-pick {
  max-width: 44rem;
}
.po-pick__panel > .editor__body {
  display: grid;
  gap: 1.1rem;
}
.po-pick__articles {
  margin: 0;
  padding: 0;
  list-style: none;
}
.po-pick__articles .num {
  margin-right: 0.5rem;
}
.po-pick__articles li {
  padding: 0.15rem 0;
}
.po-pick__drafts {
  display: grid;
  gap: 0.5rem;
  margin: 0;
  padding: 0;
  border: none;
}
.po-pick__drafts > legend {
  margin-bottom: 0.4rem;
}
/* Each draft one row to pick, the whole row the target; the picked one
   ruled in the accent. */
.po-pick__option {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 0.6rem 0.75rem;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  cursor: pointer;
}
.po-pick__option input {
  margin: 0.2rem 0 0;
}
.po-pick__option:has(input:checked) {
  border-color: var(--accent);
  background: var(--accent-soft);
}
.po-pick__warn {
  display: block;
  margin-top: 0.2rem;
  font-size: 0.82rem;
  color: var(--legacy);
}
.po-pick__into {
  margin: 0;
}
.po-pick__foot {
  display: flex;
  justify-content: flex-end;
  margin-top: 1.1rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--hairline);
}
/* An order's head names its fields in full — "Ansprechpartner" — where a
   Preisanfrage's mail header gets by with To and Bcc. */
.envelope--wide .envelope__field {
  grid-template-columns: 8.5rem minmax(0, 24rem);
}
/* The head's fields are one form, laid out as the envelope's rows. */
.envelope__form {
  display: grid;
  gap: 0.6rem;
}
/* A draft's line in two rows, as the page prints a position: the article
   number, the Preisanfrage it was ordered on and the description across
   the width, the figures beneath, starting under the description. The
   sheet's floor is for columns of inputs, which this table no longer has. */
.pr-sheet.po-sheet table {
  min-width: 0;
}
.po-sheet tbody td {
  padding-block: 0.55rem;
}
.po-line__what {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
}
.po-line__what > input {
  flex: 1 1 auto;
  min-width: 0;
}
.po-line__number,
.po-line__quote {
  flex: 0 0 auto;
  font-size: 0.8rem;
}
.po-line__number {
  min-width: 3.5rem;
}
.po-line__quote {
  color: var(--muted);
}
/* Each label over its control, so the three figures fit one row and stand
   in the same place on every line. */
.po-line__figures {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: 0.4rem 0.75rem;
  margin: 0.45rem 0 0 4.1rem;
}
.po-line__field {
  display: inline-grid;
  justify-items: start;
  gap: 0.2rem;
}
.po-line__label {
  font-size: 0.8rem;
  color: var(--muted);
  white-space: nowrap;
}
/* Sized to their figures, over the sheet's full-width inputs. */
.po-line input.po-amount {
  width: 4.75rem;
}
.po-line input.po-basis {
  width: 3rem;
}
.po-line select.po-unit {
  width: 4.5rem;
}
.po-line input[type="date"] {
  width: 8.75rem;
}
.po-total {
  margin: 0;
  padding: 0.6rem 1rem;
  text-align: right;
  border-top: 1px solid var(--hairline);
}
.po-total:empty {
  display: none;
}
/* An order's opening and closing as they print, a paragraph at a time,
   with room on the left for the rail a pointed-at paragraph shows. */
.po-paras {
  padding: 1.1rem 1.25rem 1.1rem 1.75rem;
}
.po-cols .split__work > .project-head {
  margin-top: 0;
}
/* A delivery date gone by while the line is still awaited. */
.po-overdue {
  color: var(--legacy);
  font-weight: 600;
}
.pr-answers__order {
  margin-left: 0.4rem;
  font-size: 0.8rem;
}
.pr-answers__cell .inline-form {
  display: inline-block;
  margin-left: 0.3rem;
  vertical-align: middle;
}
