/* ==========================================================================
   Administration front end - the same design language as the reporting.

   The file is ordered by area; the headings below name them one after another.
   Inside an area everything stands in the order in which it came about - that is
   deliberate: at equal specificity the order decides, and re-sorting inside an
   area would change that.

   The rules for narrow windows (@media) stand gathered at the end, so that they
   override the basic rules and not the other way round.
   ========================================================================== */


/* -------------------------------------------------------------------- Basics */

:root {
  --brand: #57a900;
  --brand-dark: #4a9100;
  --brand-light: #f0f8e6;

  --bg: #f4f6f9;
  --surface: #ffffff;
  --surface-alt: #fafbfc;
  --border: #e3e8ee;
  --border-strong: #cdd5df;

  --text: #1f2933;
  --text-muted: #6b7785;

  --good: #2f9e44;
  --good-bg: #e7f7ec;
  --bad: #c92a2a;
  --bad-bg: #fdeaea;
  /* Neither good nor bad but open - something is waiting for somebody. */
  --warning: #b26a00;
  --warning-bg: #fff4e0;

  /*
   * A find, not a verdict - it draws the eye without saying whether something is right.
   *
   * Not called --accent: an open dropdown reads var(--accent, #333) for its border and relies on
   * that fallback, so defining --accent here would recolour it in passing.
   */
  --highlight: #3b5bdb;
  --highlight-bg: #edf2ff;

  /* Row tints, lighter than the pills that sit on them so a pill keeps its shape. */
  --good-row: #ebfbee;
  --bad-row: #fff5f5;

  /* Navigation as in the warehouse tool (Bootstrap 4 navbar-dark bg-dark / navbar-light bg-light) */
  --nav-bg: #343a40;
  --nav-link: rgba(255, 255, 255, .5);
  --nav-link-hover: rgba(255, 255, 255, .75);
  --nav-link-active: #fff;
  --sub-nav-bg: #f3f3f3;
  --sub-nav-link: rgba(0, 0, 0, .5);
  --sub-nav-link-hover: rgba(0, 0, 0, .7);
  --sub-nav-link-active: rgba(0, 0, 0, .9);

  --radius: 8px;
  --radius-sm: 5px;
  --shadow-sm: 0 1px 2px rgba(20, 30, 45, .06), 0 1px 3px rgba(20, 30, 45, .05);
  --shadow-md: 0 2px 6px rgba(20, 30, 45, .07), 0 6px 16px rgba(20, 30, 45, .06);
  --nav-height: 50px;
  --sub-nav-height: 46px;
}

*, *::before, *::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  /* The reporting CSS still keeps room here for its old bar - no longer needed. */
  padding: 0;
  background: var(--bg);
  color: var(--text);
  /* Font sizes as in the warehouse tool: its Bootstrap sets nothing on the body,
     so 16px and the default sans-serif apply. */
  font-family: sans-serif;
  font-size: 16px;
  line-height: 1.4;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3 {
  margin: 0;
  font-weight: 600;
}

h1 {
  font-size: 28px;
}

h2 {
  font-size: 21px;
}

a {
  color: var(--brand-dark);
  text-decoration: none;
}

a:hover {
  color: var(--brand);
}

body.no-sub-navigation .page {
  padding-top: 62px;
}

/*
 * Embedded page without navigation of its own - that stands in the frame above, so the room for
 * the fixed bars is not needed here.
 */
body.embed-page .page {
  padding-top: 22px;
  padding-bottom: 28px;
}

/* ==========================================================================
   Time tracking
   --------------------------------------------------------------------------
   Taken over from public/assets/css_sass/components/_timeTracking.scss of the
   standalone tool. The rules are carried over from SCSS into ordinary CSS, the
   colours stand as values instead of variables - the front end here builds no
   SCSS. The class names have stayed the same, so that the further pages can be
   pulled along without renaming.
   ========================================================================== */

:root {
  --ze-success: #28a745;
  --ze-success-bg: rgba(40, 166, 69, .2);
  --ze-blue: #007bff;
  --ze-blue-bg: rgba(0, 105, 217, .2);
  --ze-error: #dc3545;
  --ze-orange: #ff841a;
  --ze-grey: #c7c7c7;
  --ze-light-grey: #f3f3f3;
  --ze-dark-grey: #797979;
}

/* ---------------------------------------------------------------- Page frame */

.page {
  font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;
  max-width: 1180px;
  margin: 0 auto;
  /* Room for the two fixed bars - like #mainBody in the warehouse tool. */
  padding: 112px 24px 64px;
}

/*
 * For pages with tables: more room than the reading width of .page, but not the whole window
 * width. That way wide tables like the one in item/itemPrices.php fit without the eye having to
 * travel right across the page on a large screen.
 */
.page.page-table {
  max-width: 1500px;
  padding-left: 28px;
  padding-right: 28px;
}

/*
 * Nearly the full window width, for pages whose content does not fit the table width.
 *
 * It was written for the permanent display (timeTracking/board.php) and the comment said so, which
 * read as though a working page must not use it - it stopped somebody widening a page that needed
 * it. Twelve pages use it today and all but the board are ordinary desk pages: monitoring,
 * photoStudio, supplier portal, finance, todo. The rule is the content, not the screen it hangs on.
 */
.page.page-wide {
  max-width: none;
  padding-left: 32px;
  padding-right: 32px;
}

/*
 * For pages that reach the bottom of the window themselves and scroll within it: the padding at
 * the bottom falls away, or a strip would hang below and the page would get a second scrollbar
 * with nothing in it.
 */
.page.page-fill {
  padding-bottom: 0;
}

.page-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 22px;
}

.page-subtitle {
  margin: 4px 0 0;
  color: var(--text-muted);
}

.page-actions {
  display: flex;
  gap: 8px;
}

/* Without a page head the button row stands alone - then it needs the spacing itself. */
.page > .page-actions {
  flex-wrap: wrap;
  margin-bottom: 22px;
}

/*
 * The same for a row inside a form, which the rule above cannot reach - EX-1464.
 *
 * THE CHILD COMBINATOR ABOVE IS LOAD-BEARING AND STAYS. It is what keeps the spacing OFF the row
 * inside `.page-head`, which Template::pageHead() renders and 84 pages use - there the head already
 * provides it. Dropping the `>` would have been one clause and would have put 22px under the
 * buttons on every one of those pages to fix a single screen.
 *
 * A row nested in a `<form>` matches neither: not a direct child of `.page`, not inside the head.
 * It was therefore flush against whatever followed - on timeTrackingUserValues.php the "Speichern"
 * button sat on the table header, which is the screen this was reported from.
 *
 * A RULE FOR THE CASE RATHER THAN FOR THAT PAGE, Robin's decision of 29.09.2026: the next form that
 * closes with a button row gets the spacing without anybody remembering this. Counted at
 * origin/master 603fdf76b: six `.page-actions` in five files, of which exactly ONE is in this
 * position - so the rule is written for a shape that recurs, not for a population of one.
 */
.page form .page-actions {
  flex-wrap: wrap;
  margin-bottom: 22px;
}

.section-head {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 14px;
  color: var(--text-muted);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 1px;
  text-transform: uppercase;
}

/*
 * A section heading has no spacing above - following on content it would otherwise sit right
 * against it. Concerns the overviews that show several blocks below one another (wholesale feeds,
 * statistics, own tasks).
 */
.table-scroll + .section-head,
.card + .section-head,
.alert + .section-head,
.card-row + .section-head {
  margin-top: 30px;
}

/*
 * The same case the other way round: a table is often followed by another block - an explanation,
 * a hint, a form. `.table-scroll` brings no spacing along and `.card` only one below, so the two
 * were stuck together.
 */
.table-scroll + .card,
.table-scroll + .card-hint,
.table-scroll + .alert {
  margin-top: 22px;
}

.section-head::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

.empty-state {
  text-align: center;
  color: var(--text-muted);
}

.empty-state h1,
.empty-state h2 {
  margin-bottom: 8px;
  color: var(--text);
}

/*
 * Built and styled like the module start pages in the warehouse tool
 * (.modules-container and .button.button-adapter out of its base/_base.scss).
 */
.modules-container {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 1rem;
}

/* Some air between two groups, so the heading belongs to what follows it. */
.modules-container + .section-head {
  margin-top: 28px;
}

.modules-container .button-adapter {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 4rem;
  margin: 0;
  padding: 0 1rem;
  border: 1px solid #000;
  background-color: #f3f3f3;
  color: #000;
  font-weight: bold;
  text-align: center;
  text-decoration: none;
}

.modules-container .button-adapter:hover,
.modules-container .button-adapter:focus {
  background-color: #797979;
  color: #fff;
  text-decoration: none;
}

/* Overall tile: wider and more detailed than the channel tiles. */

/* The shop key beside the section heading - quieter than that heading. */
.section-head-hint {
  margin-left: 8px;
  color: var(--text-muted);
  font-size: 13px;
  font-weight: 400;
}

.alert {
  margin-bottom: 20px;
  padding: 12px 16px;
  border: 1px solid;
  border-left-width: 4px;
  border-radius: var(--radius-sm);
}

.alert-success {
  background: var(--good-bg);
  border-color: var(--good);
  color: #1e7a33;
}

.alert-error {
  background: var(--bad-bg);
  border-color: var(--bad);
  color: #8c1c1c;
}

/*
 * "There is nothing here" is not a success message - the tool has a quiet line for that
 * (div.info), not the green box.
 */
.empty-hint {
  padding: 14px 0;
  color: var(--text-muted);
  text-align: center;
}

/* --- Short notices at the bottom right ---------------------------------- */

/*
 * For pages that do not reload. They lie above everything but take no clicks - otherwise a
 * message would cover the button beneath it.
 */
.admin-messages {
  position: fixed;
  right: 20px;
  bottom: 20px;
  z-index: 10002;
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: flex-end;
  pointer-events: none;
}

.admin-message {
  max-width: min(420px, calc(100vw - 40px));
  padding: 10px 16px;
  border: 1px solid var(--border);
  border-left-width: 4px;
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-md);
  font-size: .92rem;
  animation: adminMessageIn .2s ease-out;
}

.admin-message-success {
  border-left-color: var(--good);
}

.admin-message-error {
  border-left-color: var(--bad);
}

.admin-message.is-leaving {
  opacity: 0;
  transition: opacity .3s ease-in;
}

/* What lies outside the period stays empty - but the grid stands. */
.time-tracking-calendar .calendar-day.empty {
  background: repeating-linear-gradient(45deg, #fafafa, #fafafa 6px, #f2f2f2 6px, #f2f2f2 12px);
}

/*
 * Icons in buttons and tiles: a word space before them, or they stick to the label. The mask
 * takes its colour from currentColor - on coloured buttons it therefore matches by itself.
 */
.button .nav-icon,
.button-adapter .nav-icon {
  margin-right: .35rem;
}

/* In the administration the tiles stand below one another - there the icon carries the row. */
.modules-container .button-adapter .nav-icon {
  vertical-align: -4px;
}

/* ----------------------------------------------------------- Cards and tiles */

.card {
  margin-bottom: 22px;
  padding: 20px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}

/*
 * Two cards side by side - filter and file upload in the ELBA tool, for instance. Below 320px per
 * card the row breaks by itself, so on narrow screens they stand below one another again.
 */
.card-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 0 22px;
}

/* So the button rows of both cards sit at the same height, even with content of differing height. */
.card-row > .card {
  display: flex;
  flex-direction: column;
}

.card-row > .card .form-actions {
  margin-top: auto;
}

/*
 * A flex column so the tiles of a row can be of equal height: the grid stretches the card, and
 * what may grow inside (the scrolling list) fills the rest.
 */
.metric-card {
  display: flex;
  flex-direction: column;
  padding: 16px 18px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--brand);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}

/* Catches the eye as soon as something has been left lying. */
.metric-card.has-backlog {
  border-left-color: #d98324;
}

/*
 * In the overview the cards of a row are of equal height - the height is set by "Alle Kanaele".
 * The list may fill the room that creates, or an empty strip was left below it: measured 52 px at
 * 1920 and up to 141 px at 1440 pixels of window width. It still scrolls, only now at the bottom
 * edge of the card (flex: 1 and min-height: 0 sit on .metric-scroll-list).
 */
.metric-grid.overall > .metric-card .metric-scroll-list {
  /*
   * flex-basis 0 instead of auto: that way the list does not count towards the natural height of
   * the card. The row height is still set by "Alle Kanaele", and the list gets exactly the rest.
   * With basis auto it would demand its full content height and pull the card out to over
   * 5,000 px.
   */
  flex: 1 1 0;
  /*
   * A lower bound so the list does not disappear when the card stands alone in its row and there
   * is no neighbouring height - then it falls back to the same 320 px as everywhere else.
   */
  min-height: 320px;
  max-height: none;
}

/*
 * The refused uploads of EX-1613 share a row with "Fehlende Bilder", which is about 230 px tall.
 * With the 320 px above, two refusals stretched that row to twice its height with empty room in
 * both cards. 120 px still shows two rows; more than that scroll inside the neighbour's height.
 */
.metric-grid.overall > .metric-card .metric-scroll-list.is-compact {
  min-height: 120px;
}

/*
 * In the wide overview label and value stood at the two ends of the card - with half the window
 * width between them. Two pairs per row put the value right beside its label and halve the height
 * along the way. If the card is too narrow for that, it stays single-column.
 *
 * What is asked is the card itself, not the window: since it shares the row with two further
 * tiles, the window width says nothing about the room inside the card. At 1440px of window width
 * the goods value ran out of its column with the old window query.
 */
.metric-card-overall {
  container-type: inline-size;
}

.metric-card-overall .metric-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 18px;
  margin-top: 16px;
}

/*
 * The block is meant for the end of a form and therefore has spacing only above. If it stands
 * above content for once - "Automatisches OK" above the open incoming payments, say - the table
 * would otherwise stick to the buttons.
 */
.form-actions + .table-scroll,
.form-actions + .card-hint,
.form-actions + .data-table {
  margin-top: 18px;
}

.card-hint {
  margin: 0 0 10px;
  color: var(--text-muted);
  font-size: 13px;
}

/*
 * The selection column of the tables - a tick column at the very left.
 *
 * Narrow, centred and without wrapping: it carries a checkbox and nothing else, and without a
 * fixed width it takes as much room as the heading beside it.
 */
table.data-table td.column-select,
table.data-table th.column-select {
  width: 34px;
  text-align: center;
  white-space: nowrap;
}

/*
 * The preview of a mail in the mailTester.
 *
 * White ground, independent of the administration's theme: that way it looks like it does in a
 * mailbox and not like a part of this page.
 */
.mail-preview {
  width: 100%;
  height: 640px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: #fff;
}

/* A message that goes out as plain text - the way a mailbox shows it. */
.mail-preview-text {
  margin: 0;
  padding: 16px;
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: #fff;
  color: #404040;
  font-family: ui-monospace, "SFMono-Regular", "Menlo", monospace;
  font-size: 13px;
  line-height: 20px;
  white-space: pre-wrap;
}

.item-card .card-title {
  margin-top: 0;
}

.time-tracking-status .card-buttons {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: .4rem;
  margin-top: .8rem;
}

/* -------------------------------------------------------------------- Tables */

.table-scroll {
  width: 100%;
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}

/*
 * Without horizontal scrolling: everything is there at once.
 *
 * The table itself always fits - what runs over the edge are only the controls of the action
 * column, which `white-space: nowrap` holds on one line. Here they may wrap, and then nothing
 * stays hidden.
 */
.table-scroll.table-wrap {
  overflow-x: visible;
}

/*
 * `width: 1%` allots the action column less than its controls need - they then run out of the
 * cell. Without this declaration it gets its natural width and the remaining columns share the
 * rest. It may still wrap, in case the window is very narrow.
 */
.table-scroll.table-wrap table.data-table td.column-action,
.table-scroll.table-wrap table.data-table th:last-child {
  width: auto;
  white-space: normal;
}

/* The controls themselves stay whole - wrapping happens only between them. */
.table-scroll.table-wrap table.data-table td.column-action > * {
  margin-bottom: 2px;
  white-space: nowrap;
}

table.data-table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-size: 13px;
}

table.data-table th {
  position: sticky;
  top: 0;
  padding: 10px 14px;
  background: var(--surface-alt);
  border-bottom: 1px solid var(--border-strong);
  color: var(--text-muted);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .4px;
  text-transform: uppercase;
  text-align: left;
  white-space: nowrap;
}

table.data-table td {
  padding: 9px 14px;
  border-top: 1px solid var(--border);
  vertical-align: middle;
}

table.data-table tbody tr:hover td {
  background: rgba(31, 41, 51, .035);
}

table.data-table td.column-action {
  width: 1%;
  white-space: nowrap;
  text-align: right;
}

/* Buttons out of forms should stand next to the links of the column, not below them. */
table.data-table td.column-action form {
  display: inline-block;
  /*
   * Only top and bottom to zero.
   *
   * `margin: 0` stood here, and that was more specific than the rule below which sets the spacing
   * between the controls - a form next to a button therefore stuck to it. It showed everywhere an
   * action column carries a link and a form: "Weiter"/"Verwerfen" in the drafts of the order
   * portal, for example.
   */
  margin-top: 0;
  margin-bottom: 0;
}

table.data-table td.column-action > * + * {
  margin-left: 6px;
}

/* Figures only read well below one another - equal digit width, right-aligned. */
table.data-table th.column-number,
table.data-table td.column-number {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* The CSV button above a table - TableRenderer::withCsvExport(), EX-1614. Right, where the eye ends a row. */
.table-tools {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 8px;
}

/*
 * Sortable column heads - TableRenderer::sortable(), EX-1616.
 *
 * The head is a button so it can be reached with the keyboard; it looks like the head it replaces.
 * The arrow says how the column is sorted right now (aria-sort, set by TableSort.js); an unsorted
 * column shows a faint double arrow, so it is visible that it CAN be sorted.
 */
table.data-table th > button.table-sort {
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  text-align: inherit;
  white-space: nowrap;
  cursor: pointer;
}

table.data-table th > button.table-sort::after {
  content: "\2195";
  margin-left: 4px;
  opacity: .35;
}

table.data-table th[aria-sort="ascending"] > button.table-sort::after {
  content: "\2191";
  opacity: 1;
}

table.data-table th[aria-sort="descending"] > button.table-sort::after {
  content: "\2193";
  opacity: 1;
}

table.data-table th > button.table-sort:hover,
table.data-table th[aria-sort="ascending"],
table.data-table th[aria-sort="descending"] {
  color: var(--text);
}

/* Image table in the article detail. */
.column-check {
  width: 1%;
  text-align: center;
}

/*
 * Hints in data tables stand on lines of their own. In the order, marketplace identifiers and the
 * individual discounts would otherwise run together on a single line.
 */
.data-table .field-hint {
  display: block;
}

/*
 * Two-column tables in which the th labels the row instead of a column.
 *
 * .data-table is built for lists: there the th sits in the thead, sticks to the top when
 * scrolling (position: sticky) and carries a strong line below it, while the td has its line
 * above. As a row label that makes two different lines per row, which moreover do not meet as
 * soon as the value column is taller than the label.
 */
table.data-table.detail-table th {
  position: static;
  width: 34%;
  border-bottom: none;
  border-top: 1px solid var(--border);
  vertical-align: top;
  white-space: normal;
}

/* The first row needs no line - the card heading already stands above it. */
table.data-table.detail-table tbody tr:first-child th,
table.data-table.detail-table tbody tr:first-child td {
  border-top: none;
}

table.data-table.detail-table td {
  vertical-align: top;
}

table.data-table.detail-table tbody tr:hover th {
  background: var(--surface-alt);
}

/*
 * A row label - a th inside tbody, as in the duty-schedule tables, the VAT table and the shipping
 * configuration - EX-1649. The th rule above is written for the head: sticky, a strong line BELOW.
 * In a body row that drew the label's line at the bottom while the cells beside it carry theirs at
 * the top, so the first column showed its own, heavier lines that met the row's lines nowhere
 * (Robin, 13.10.2026: "sometimes its thicker"). The label takes the cells' line instead, as the
 * detail table above already does.
 */
table.data-table tbody th {
  position: static;
  border-bottom: none;
  border-top: 1px solid var(--border);
}

/* The balances table: narrow number fields, so the names keep their room. */
table.data-table td .value-input {
  width: 9rem;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* The column with the checkboxes stays narrow and does not disturb the table's widths. */
.data-table .column-check {
  width: 2rem;
  text-align: center;
}

/* --- Task list: columns and actions ------------------------------------- */

/*
 * One column per note - one comes from the reporting side, the other from whoever is working on
 * it. The width is capped so a long text does not pull the whole table apart.
 */
.data-table .column-note {
  min-width: 12rem;
  max-width: 20rem;
}

.data-table .column-note .field-hint {
  white-space: normal;
}

/*
 * The actions on the right: select and buttons at the same height. The select brings its height
 * from .field-input, the small buttons their own - side by side that looked like two rows.
 *
 * Whether the column wraps is left to the house rule further up (table-wrap): with a narrow
 * window it should be allowed to, and the controls themselves stay whole while it does.
 */
.data-table .column-action > form,
.data-table .column-action > .button,
.data-table .column-action > button,
.data-table .column-action > select {
  display: inline-flex;
  align-items: center;
  vertical-align: top;
}

.data-table .column-action .button,
.data-table .column-action .field-input,
.data-table .column-action .field-select {
  height: 34px;
  padding-top: 0;
  padding-bottom: 0;
}

.data-table .column-action .field-inline {
  width: auto;
  min-width: 8.5rem;
}

/* A mail address needs more than the 8.5rem a select gets by with. */
.data-table .column-action .field-inline-wide {
  min-width: 17rem;
}

/*
 * Several controls in one action column - field, select, button.
 *
 * Outside the table the input may take the room that is left; here it may not. The cell is as
 * wide as its content (column-action sits on width: 1%), and a field that fills the rest makes
 * the group a different width in every row - then the button stands here in one and there in the
 * next, and the column is no longer in line.
 *
 * So every element gets a width of its own and the group moves to the right. All rows are built
 * the same way then, however long the mail address in them is.
 */
.data-table .column-action .field-with-button {
  justify-content: flex-end;
}

.data-table .column-action .field-with-button > .field-input,
.data-table .column-action .field-with-button > .field-select {
  flex: 0 0 auto;
}

/*
 * The button at the end is as wide as the widest one in its column.
 *
 * Otherwise the alignment hangs on the word: in the invitation list it reads "Einladen", for a
 * customer with an account "Erneut" - two characters less. The group is right-aligned, so that
 * one row slid right by exactly those two characters, and the input beside it started later than
 * in the rows above.
 *
 * The width is not a question of beauty: a column whose fields do not stand below one another
 * reads as two columns.
 */
.data-table .column-action .field-with-button > .button {
  min-width: 7rem;
  justify-content: center;
}


/* --------------------------------------------------------------------- Forms */

/* SQL and other input where the indentation counts. */
.field-input.field-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 13px;
  line-height: 1.5;
  white-space: pre;
  overflow-x: auto;
}

.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 16px 20px;
}

/*
 * A fixed number of fields per line - FormRenderer::addRow($fields, 4). EX-1560, Robin 08.10.2026:
 * on the employee form the free grid put five on a line, the long labels wrapped and pushed their
 * inputs down, and the lines stood unevenly apart. Below 900px two per line.
 */
.form-grid.form-grid-4 {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

@media (max-width: 900px) {
  .form-grid.form-grid-4 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.field {
  display: flex;
  flex-direction: column;
}

/*
 * `hidden` has to win: the rule above is more specific than the display:none the browser brings
 * along for [hidden]. Without this, "Hinterlegt" stood in the request overlay even when no proof
 * is on file at all - the same held for the two fields in the board overlay.
 */
.field[hidden] {
  display: none;
}

/*
 * Fields standing directly below one another need the spacing themselves - inside .form-grid it
 * comes from the gap, outside there was none at all. That concerned every form whose fields lie
 * straight in the card.
 */
.field + .field,
.field + .form-grid,
.form-grid + .field,
/* Two grids in succession were not covered - between them the spacing was missing entirely. */
.form-grid + .form-grid {
  margin-top: 16px;
}

/* In the grid the gap provides the spacing - otherwise it would stand twice. */
.form-grid > .field + .field {
  margin-top: 0;
}

/*
 * A heading between the fields of a form - EX-1490.
 *
 * The form's rhythm is built from adjacency (.field + .field and the three rules above), and an
 * h2 between two grids matches none of it; the global `h1, h2, h3 { margin: 0 }` then leaves it
 * with no spacing at all, flush against the row above. 24px rather than the 16px of the field
 * rhythm, so that a new section reads as a bigger break than the next field - one value, and the
 * only one to change if that turns out too much.
 *
 * `.card > h2:first-child` stays more specific, so a form that OPENS with a section keeps its
 * zero top margin and does not gain a gap under the card's padding.
 */
.form-section {
  margin-top: 24px;
}

.field-wide {
  grid-column: 1 / -1;
}

/* Whether a new row starts with this field. */
.field-new-line {
  grid-column: 1;
}

.field-label {
  margin-bottom: 5px;
  color: var(--text-muted);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .6px;
  text-transform: uppercase;
}

.field-input,
.field-select,
.field-textarea {
  width: 100%;
  padding: 9px 11px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  color: var(--text);
  font-family: inherit;
  font-size: 14px;
}

.field-textarea {
  min-height: 92px;
  resize: vertical;
}

.field-input:focus,
.field-select:focus,
.field-textarea:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px rgba(87, 169, 0, .15);
}

.field-hint {
  margin-top: 5px;
  color: var(--text-muted);
  font-size: 12px;
}

.field-check {
  display: flex;
  align-items: center;
  gap: 8px;
}

.field-check input {
  margin: 0;
  accent-color: var(--brand);
}

.field-check label {
  margin: 0;
  font-weight: 400;
  cursor: pointer;
}

/*
 * A filter bar above a list.
 *
 * Not to be done with .form-actions: that is the footer of a form and brings a dividing line
 * above it - over a filter that looks like a cut-off remnant.
 */
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 14px 22px;
}

/* The general rule `.field + .field` does not apply here - side by side it is not needed. */
.filter-bar .field {
  margin-top: 0;
  min-width: 170px;
}

.filter-bar .filter-checks {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 20px;
  /* At the height of the input - align-items lines up the bottom edges. */
  height: 38px;
}

.filter-bar .filter-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 38px;
  margin-left: auto;
}

/* The hint used to stand under the date field and pushed it out of the row. */
.filter-hint {
  margin: 12px 0 0;
  color: var(--text-muted);
  font-size: 12px;
}

.form-actions {
  display: flex;
  gap: 10px;
  align-items: center;
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1px solid var(--border);
}

/*
 * When a form closes with its button row, another one often follows below - to request something,
 * to delete something. Without spacing its card sticks to the buttons; here it gets the same one
 * the cards have between them.
 *
 * Only the row immediately inside the form: the one in a card closes with that card's padding.
 */
form > .form-actions:last-child {
  margin-bottom: 22px;
}

.login-form .field-label {
  margin-top: 14px;
}

.login-form .field-label:first-child {
  margin-top: 0;
}

.login-form .field-hint {
  margin-top: 6px;
}

/* An input with a button beside it, e.g. date + "heute". */
.field-with-button {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* A checkbox and the date it belongs to, in one row. Narrower than 10rem the year is cut off. */
.field-with-check {
  display: grid;
  grid-template-columns: auto minmax(10rem, 1fr);
  align-items: center;
  gap: 8px;
}

.field-with-button .field-input {
  flex: 1 1 auto;
  min-width: 0;
}

.field-with-button .button {
  flex: 0 0 auto;
}

/*
 * Input and button stay together on the left, instead of the field taking the whole width and the
 * button sliding to the right edge.
 */
.field-with-button-left {
  justify-content: flex-start;
}

.field-with-button-left .field-input {
  flex: 0 1 320px;
}

/*
 * A value with its switch beside it - in an ordinary table cell.
 *
 * A <form> is a block element and otherwise drops below whatever stands before it: the badge on
 * top, the button underneath, and a line of air between them that means nothing. In the action
 * columns a rule of its own handles that; outside them this wrapper is needed.
 */
.value-with-action {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}

.value-with-action form {
  margin: 0;
}

/* A value that is only displayed - looks like a field but is not one. */
.field-static {
  padding: 9px 0;
  font-weight: 600;
  word-break: break-word;
}

.image-table .field-input {
  min-width: 0;
}

/*
 * An input standing directly below a list in the same card - without spacing it stuck to the
 * bottom edge of the table.
 */
.form-after-list {
  margin-top: 18px;
}

/* Many buttons of equal rank - they may wrap. */
.form-actions-wrap {
  flex-wrap: wrap;
}

/* Filter choices above a list - the ticks stand beside one another, not below. */
.filter-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 22px;
}

.filter-chips .field-check {
  margin: 0;
}

.embed-search .field-input {
  width: 100%;
  max-width: 420px;
  padding: 4px 9px;
  font-size: 13px;
}

/* Intermediate heading in forms - separates groups such as payment and shipping. */
.form-group-title {
  margin: 20px 0 10px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--border);
  color: var(--text-muted);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .8px;
  text-transform: uppercase;
}

.form-group-title:first-of-type {
  margin-top: 12px;
}

/* A multiple choice - in the tool this was a drop-down list with ticks. */
.checkbox-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 4px 16px;
  margin-top: 6px;
}

.checkbox-option {
  display: flex;
  align-items: center;
  gap: 7px;
  margin: 0;
  font-weight: 400;
}

/*
 * The search stands above the list and should not feel as heavy as an editing form.
 *
 * THE EXCEPTION USED TO BE MADE HALF - EX-1464. It took the margin off and left the divider and the
 * padding, so every search form carried a rule above its button that belongs to an editing form.
 * `.form-actions` is a closing row: the line separates what you filled in from what you submit. A
 * search has one field and one button, and the line reads as a stray rule under the input rather
 * than as a separation.
 *
 * ONE CLAUSE, AND IT REACHES 23 CALL SITES IN TWO FRONTENDS, re-counted at 29.09.2026: 22 in
 * `_admin` and ONE in `_supplier/catalog.php`, one per file. Not the one page it was reported from
 * - the rule was always general and only ever looked local because the report came from a single
 * screen.
 *
 * This said "24 ... counted at origin/master 603fdf76b", and the second of the supplier's two was a
 * comment rather than a call. The revision was named, which is the discipline working; it just does
 * not help when the miscount is in the reading rather than in the drift.
 *
 * THE SUPPLIER AREA IS NOT LEAKAGE, IT SHARES THIS SHELL ON PURPOSE - _supplier/template/head.php:5
 * says so ("the same shell as the other two areas: navigation.css and admin.css"), and all three of
 * its heads link this file. Anybody who later counts 22 in `_admin` and finds a supplier page
 * following the rule should read that as the design rather than as an accident.
 */
/*
 * THE BUTTON BELONGS BESIDE THE FIELDS, NOT ON A ROW OF ITS OWN - EX-1472.
 *
 * Robin, 29.09.2026: "the space of the form buttons look not good", on three screenshots of three
 * different screens. The card is the form and it held two block children - a full-width
 * `.form-grid` and a full-width `.form-actions` below it - so "Anzeigen" landed alone under the
 * LEFTMOST field with the rest of the bar empty beside it. Nothing was wrong with either child;
 * they were simply stacked.
 *
 * Making the card the flex row puts them side by side, and `flex-end` lines the button up with the
 * bottom of the inputs rather than with the labels above them. `flex-wrap` is what keeps the phone
 * case: below roughly 240px of field the actions drop underneath again, which is the old layout
 * and is right when there is no room for anything else.
 *
 * `min-width: 0` on the grid is not decoration - a grid item refuses to shrink below its content
 * without it, and the bar would then push past the card on a narrow screen.
 *
 * THE POPULATION WAS COUNTED BEFORE THE RULE WAS WRITTEN, and it is why this is safe to do in one
 * place: 23 call sites of FormRenderer::filter(), 22 in `_admin` and 1 in `_supplier/catalog.php`,
 * one per file, and EVERY ONE of them is a single addRow() and nothing else. No withTitle(), no
 * addField(), no withBareActions(), no withoutCard(). Three of the 23 reached Robin one at a time;
 * the other 20 are the ones nobody photographed. `_customer` has none.
 *
 * IT SAID 24 UNTIL 29.09.2026, AND THE TWENTY-FOURTH WAS A COMMENT. `_supplier/catalog.php` names
 * FormRenderer::filter() twice: at :155 inside a docblock explaining why the button is passed
 * explicitly, and at :158, which is the call. A textual count cannot tell those apart, and this
 * file EXPLAINS the thing it was counting - the exact shape that makes a scan find its own prose.
 *
 * THE COUNTS BEHIND THAT NUMBER SHARED A KEY, so do not read their agreement as independence: this
 * pass and EX-1464 both keyed on call sites of FormRenderer::filter(). Two countings on one key
 * agree when the key is right and equally when it is wrong - and here it was wrong.
 *
 * A THIRD KEY WAS ADDED AND STILL DID NOT CATCH IT, WHICH IS THE PART WORTH READING. It keyed on
 * what the CSS actually selects - `withCardClass('filter-card')`, the sole emitter, with no
 * hand-written `class="filter-card"` anywhere - and that is a real independent check for a
 * DIFFERENT fault: it would find a card this rule misses. But it never re-counted the call sites.
 * It inherited the 24 and confirmed something else about it.
 *
 * So the paragraph warning against agreement-without-independence was itself written while
 * inheriting the number the agreement was about. A key that cannot produce the figure it is
 * quoted beside has not checked that figure, however sound it is on its own question. The
 * arithmetic was the tell and nobody read it: "24 across 23 files" says one file holds two, and
 * that file is the one with the comment in it.
 *
 * THE SUPPLIER AREA COMES ALONG ON PURPOSE - the same design the rule below already records:
 * `_supplier/template/head.php:5` links this stylesheet deliberately. `_customer` has NO call site
 * at all, so it is untouched by this and by the rule below.
 *
 * AND NOBODY HAS SEEN IT. This repository has no CSS RENDERING test, so nothing in any suite can
 * confirm or deny how this looks. The markup it rests on WAS measured - the form was rendered and
 * its HTML read - but the layout is unverified until a person opens one of the 23.
 *
 * "Rendering" is doing work in that sentence. TimeTrackingPagesTest asserts that this stylesheet
 * CONTAINS `.page-month-list .filter-card`, so a claim of "no CSS test of any kind" has a findable
 * counterexample. It is a string-presence assertion and it cannot see a pixel, so the substance
 * above stands - but the wider claim would have cost the paragraph its credit for no gain.
 */
.filter-card {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 16px 20px;
}

.filter-card .form-grid {
  flex: 1 1 240px;
  min-width: 0;
}

/*
 * The filter row sits ON the field line, so it neutralises all four of the separating edges that
 * `.form-actions` carries - and `margin-bottom` was the one missed.
 *
 * `FormRenderer::filter()` sets CARD_SELF and adds `filter-card`, so the `<form>` IS the card and
 * `.form-actions` is its last child. That makes `form > .form-actions:last-child` above match, and
 * its 22px bottom margin applied here unchanged. `align-items: flex-end` then aligned the MARGIN
 * BOX, not the visible button, lifting the button by exactly those 22px.
 *
 * Measured on the reported screen: button top edge ~41px against field top edge ~62px, so ~21px
 * against 22px predicted - the missing pixel is rounding, not a second cause.
 */
/*
 * THE FILTER BUTTON GETS A ROW OF ITS OWN - Robin, 01.10.2026, in those words: "put the submit
 * button in a own row and add a margin to it from top. that was the issue with it before, that
 * there was no margin from top at all."
 *
 * THIS SUPERSEDES `b594da45d` RATHER THAN EXTENDING IT, and the distinction matters because this
 * rule block is the one that commit edited. Its diagnosis was sound: `form > .form-actions:last-child`
 * matches here, its 22px bottom margin applied unchanged, and `align-items: flex-end` aligns the
 * MARGIN box, so the button floated 22px above the field line. What has been overtaken is the
 * PREMISE the whole block rests on - that the button belongs ON the field line, which is why every
 * separating edge was stripped. Robin has now said it does not. So `margin-top: 0` is not an
 * omission that `margin-bottom: 0` completed; it is the thing he is describing as wrong.
 *
 * `flex-basis: 100%` IS THE HOUSE PATTERN AND NOT A NEW ONE: `.release-image-head .field-hint`
 * does exactly this - a full-width child of a `display: flex; flex-wrap: wrap` parent, taking a
 * line of its own, for the same reason ("the date gets a row of its own"). `.filter-card` already
 * wraps, so the button breaks to its own flex line without anything else changing.
 *
 * `align-items: flex-end` ON THE PARENT IS LEFT ALONE, and that is deliberate. It was the
 * suspected mechanism twice today and it is not one: cross-axis alignment applies WITHIN a flex
 * line, and the button's line now holds only the button. The field line still aligns its fields to
 * their bottom edge, which is what it is there for.
 *
 * THE BOTTOM MARGIN IS STILL WANTED AT 0 - the `<form>` IS the card (FormRenderer's CARD_SELF), so
 * the card's own padding provides the space below and a bottom margin would add a second one inside
 * it - but it is NOT SET HERE ANY MORE. It never worked from here. See the rule below.
 *
 * ⚠ THE SPACING ABOVE THE BUTTON IS 27px AND THAT IS ARITHMETIC, NOT A RENDERED MEASUREMENT:
 * 11px from the `margin-top` below plus the 16px row gap `.filter-card` puts between flex lines.
 * The gap is deliberately not the half that moved - it is shared with the spacing between field
 * rows, so moving it would move those too.
 *
 * IT WAS 38px UNTIL 01.10.2026, AND ROBIN HALVED THE MARGIN HAVING SEEN IT RENDERED: "also reduce
 * the margin for the submit button by the half of it." The button's own row is right and is not in
 * question here; only the gap above it was too big. 22px -> 11px, which is the single dial the
 * paragraph above named at the moment it was written.
 *
 * IT IS SET HERE AND NOT ON `.form-actions` ITSELF, and that is the whole decision in this change.
 * `.form-actions` is the house's closing row for every form, not the filter bar's: counted at
 * `b58e3ca5b`, 32 hand-written call sites across three front ends - 19 in `_admin`, 8 in
 * `_customer`, 5 in `_supplier` - plus every form `FormRenderer::toHtml()` wraps at :316, which is
 * all of them but the `withBareActions()` ones. All three front ends link this one stylesheet and
 * it is the only one that defines `.form-actions`, so halving the 22px in place would have moved
 * the action row of every one of them for a filter bar's sake.
 *
 * This selector carried `margin-top: 0` until `00bf72796` removed it, so the dial sits back where
 * it already lived rather than in a new override.
 */
.filter-card .form-actions {
  flex-basis: 100%;
  margin-top: 11px;
  padding-top: 0;
  border-top: none;
}

/*
 * THE BOTTOM MARGIN NEEDS ITS OWN SELECTOR, AND UNTIL 01.10.2026 IT DID NOT HAVE ONE - review-1.
 *
 * `margin-bottom: 0` stood in the block above from `b594da45d` until today and NEVER APPLIED ONCE.
 * `form > .form-actions:last-child` is 0-2-1 and `.filter-card .form-actions` is 0-2-0, so the
 * house rule's 22px won every time. Checked rather than assumed: `admin.css` is the only stylesheet
 * that defines `.form-actions`, and none of the file's six `!important` declarations is a margin.
 *
 * WHY IT STAYED INVISIBLE FOR SO LONG: the other declarations in that block only have to beat
 * `.form-actions`'s own 0-1-0, so they do apply and the block visibly works. A declaration that
 * loses the cascade looks exactly like one that is obeyed - there is no warning, no console, and
 * the surrounding rule doing its job is itself the reassurance.
 *
 * ⚠ THIS IS NOT A REGRESSION AND NOTHING TODAY MADE IT WORSE. The 22px applied before
 * `b594da45d`, under it, and after `00bf72796`. What changed today is that the gap ABOVE the button
 * was halved to 11px, which is what made the untouched 22px below it worth looking at.
 *
 * `:last-child` IS WHAT BUYS THE SPECIFICITY - 0-3-0 beats 0-2-1 - and it is a true description
 * rather than a lever: `FormRenderer::toHtml()` closes the form with `$html .= $body . $buttons`,
 * so in a filter card this element IS the form's last child. That is the same fact that let
 * `form > .form-actions:last-child` reach it in the first place, which is why the two cannot come
 * apart.
 *
 * IT IS A RULE OF ITS OWN RATHER THAN `:last-child` APPENDED TO THE BLOCK ABOVE, so the narrowing
 * touches only the declaration that needs it. On the block it would quietly make `flex-basis`,
 * `margin-top`, `padding-top` and `border-top` conditional on being the last child too, and a
 * filter card that one day ends with something else would lose its whole layout instead of a
 * margin it never had.
 *
 * AND THE RECORD ABOVE IS CORRECTED WHILE I AM IN HERE: `b594da45d`'s docblock credits
 * `margin-bottom: 0` with taking away the button's 22px lift. Per the above it never applied, so it
 * took away nothing - the lift ended with `flex-basis: 100%` in `00bf72796`, which gives the button
 * a flex line of its own and makes the parent's `align-items: flex-end` moot for it. Right
 * behaviour, wrong recorded cause, and a wrong cause is the sort of thing that gets copied into the
 * next fix.
 */
.filter-card .form-actions:last-child {
  margin-bottom: 0;
}

/*
 * THE ORDER SEARCH IS A CARD WITH A BUTTON, NOT A FILTER BAR - EX-1482, 01.10.2026.
 *
 * `order/orders.php` builds its search with `get()->asCard()` rather than `FormRenderer::filter()`
 * on purpose: the search IS the page's content, not a strip above a list. So it does not get
 * `.filter-card`, and it keeps the ordinary stacked card layout.
 *
 * What it does not want is the DIVIDER. `.form-actions` is the closing row of an editing form and
 * brings a rule above itself; under a single search field that reads as a stray line rather than
 * as a separation, which is what Robin reported. Only the line and the padding that goes with it
 * are switched off here.
 *
 * ⚠ `margin-top` IS DELIBERATELY LEFT ALONE, and that is the whole reason this rule exists
 * instead of `withBareActions()`. Taking the wrapper away would have removed the divider too - and
 * with it the only thing holding the button off the field. Measured: `.form-grid` (gap only, no
 * bottom margin), `.field` (no margin) and `.field-hint` (margin-TOP only) give nothing downward,
 * so a bare button sits flush against the hint. That is the "no margin from top at all" complaint
 * from earlier the same day, which would have reappeared here while a border was being removed.
 *
 * NOT CHANGED, AND NOTED SO IT IS NOT MISTAKEN FOR AN OVERSIGHT: `form > .form-actions:last-child`
 * still adds 22px BELOW this row, inside the card's own padding, exactly as it did on the filter
 * cards before that was given its own selector. Nobody has reported it here and it was not part of
 * what was decided, so it stays until somebody looks at it on purpose.
 */
.order-search .form-actions {
  border-top: none;
  padding-top: 0;
}

/* Visible only to screen readers - the tick alone tells them nothing. */
.field-note-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.time-tracking-calendar .calendar-filter .field,
.time-tracking-department .department-filter .field {
  margin: 0;
}

/* The fields stand in one row - .field-select is otherwise as wide as its container. */
.todo-bulk .field-select {
  width: auto;
  min-width: 11rem;
}

/* --- Multiple choice as a fold-out field -------------------------------- */

.dropdown-checkbox {
  position: relative;
  min-width: 12rem;
}

/* Looks like a select and behaves like one - only with checkboxes inside. */
.dropdown-checkbox-label {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .5rem;
  width: 100%;
  padding: 9px 11px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
  font-family: inherit;
  font-size: 14px;
  text-align: left;
}

.dropdown-checkbox-label::after {
  content: '▾';
  flex: none;
  color: var(--ze-dark-grey);
}

.dropdown-checkbox.is-open .dropdown-checkbox-label {
  border-color: var(--accent, #333);
}

/*
 * The list lies over the page instead of pushing it down - otherwise everything below would jump
 * as soon as somebody opens the field.
 */
.dropdown-checkbox-list {
  position: absolute;
  z-index: 20;
  top: calc(100% + 2px);
  right: 0;
  left: 0;
  display: none;
  max-height: 18rem;
  overflow-y: auto;
  padding: .35rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  box-shadow: var(--shadow-md);
}

.dropdown-checkbox.is-open .dropdown-checkbox-list {
  display: block;
}

.dropdown-checkbox-option {
  display: flex;
  align-items: center;
  gap: .45rem;
  padding: .3rem .4rem;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: 14px;
}

.dropdown-checkbox-option:hover {
  background: var(--ze-light-grey);
}

.dropdown-checkbox-all {
  display: block;
  width: 100%;
  padding: .3rem .4rem;
  margin-bottom: .25rem;
  border: none;
  border-bottom: 1px solid var(--border);
  background: none;
  color: var(--ze-dark-grey);
  cursor: pointer;
  font-family: inherit;
  font-size: .82rem;
  text-align: left;
}

/* Without a script the list stays open - then it is at least usable. */
.no-js .dropdown-checkbox-list {
  display: block;
  position: static;
}

/* ------------------------------------------------- Buttons, badges, symbols */

.pill {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 99px;
  font-size: 11px;
  font-weight: 600;
}

.pill-good {
  background: var(--good-bg);
  color: var(--good);
}

.pill-muted {
  background: var(--surface-alt);
  color: var(--text-muted);
  border: 1px solid var(--border);
}

.pill-bad {
  background: var(--bad-bg);
  color: var(--bad);
}

/*
 * A state still being decided - an invitation nobody has accepted.
 *
 * Grey would mean "done, nothing further", and that is exactly what it is not: somebody is
 * waiting there. Between the green of the running account and the red of the blocked one, this
 * third colour was missing.
 */
.pill-warning {
  background: var(--warning-bg);
  color: var(--warning);
}

.button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 9px 18px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition: background-color .15s ease;
}

.button-primary {
  background: var(--brand);
  border-color: var(--brand-dark);
  color: #fff;
}

.button-primary:hover {
  background: var(--brand-dark);
  color: #fff;
}

.button-secondary {
  background: var(--surface);
  border-color: var(--border-strong);
  color: var(--text);
}

.button-secondary:hover {
  background: var(--surface-alt);
  color: var(--text);
}

.button-danger {
  background: var(--surface);
  border-color: var(--bad);
  color: var(--bad);
}

.button-danger:hover {
  background: var(--bad-bg);
  color: var(--bad);
}

.button-small {
  padding: 5px 12px;
  font-size: 13px;
}

/*
 * The warehouse tool uses Font Awesome. Two glyphs are enough here, embedded
 * as a mask - that way they take on the colour of the link without a whole
 * icon font library having to be loaded.
 */
.nav-icon {
  display: inline-block;
  width: 17px;
  height: 17px;
  vertical-align: -3px;
  background-color: currentColor;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
}

.nav-icon-expand {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8 3H5a2 2 0 0 0-2 2v3M16 3h3a2 2 0 0 1 2 2v3M8 21H5a2 2 0 0 1-2-2v-3M16 21h3a2 2 0 0 0 2-2v-3'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8 3H5a2 2 0 0 0-2 2v3M16 3h3a2 2 0 0 1 2 2v3M8 21H5a2 2 0 0 1-2-2v-3M16 21h3a2 2 0 0 0 2-2v-3'/%3E%3C/svg%3E");
}

.nav-icon-sliders {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M3 6h18M3 12h18M3 18h18'/%3E%3Ccircle cx='9' cy='6' r='2.6' fill='%23fff'/%3E%3Ccircle cx='15' cy='12' r='2.6' fill='%23fff'/%3E%3Ccircle cx='8' cy='18' r='2.6' fill='%23fff'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M3 6h18M3 12h18M3 18h18'/%3E%3Ccircle cx='9' cy='6' r='2.6' fill='%23fff'/%3E%3Ccircle cx='15' cy='12' r='2.6' fill='%23fff'/%3E%3Ccircle cx='8' cy='18' r='2.6' fill='%23fff'/%3E%3C/svg%3E");
}

/* Checklist - the counter beside it names the unfinished tasks. */
.nav-icon-todo {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6h2l1.4 1.6L10 5M13 6.5h7M4 13h2l1.4 1.6L10 12M13 13.5h7M4 20h2l1.4 1.6L10 19M13 20.5h7'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6h2l1.4 1.6L10 5M13 6.5h7M4 13h2l1.4 1.6L10 12M13 13.5h7M4 20h2l1.4 1.6L10 19M13 20.5h7'/%3E%3C/svg%3E");
}

/*
 * The icons of the metric tiles and the hints. In the tool this was Font Awesome; here the same
 * motifs as a mask, so they take on the colour of their row.
 */
.nav-icon-balance {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 4v16M8 20h8M4 7h16M12 4 6 8M12 4l6 4M3 8l-1 4a3 3 0 0 0 6 0L7 8M17 8l-1 4a3 3 0 0 0 6 0l-1-4'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 4v16M8 20h8M4 7h16M12 4 6 8M12 4l6 4M3 8l-1 4a3 3 0 0 0 6 0L7 8M17 8l-1 4a3 3 0 0 0 6 0l-1-4'/%3E%3C/svg%3E");
}

.nav-icon-clock {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='8.5'/%3E%3Cpath d='M12 6.5V12l3.5 2.2'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='8.5'/%3E%3Cpath d='M12 6.5V12l3.5 2.2'/%3E%3C/svg%3E");
}

.nav-icon-umbrella {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 12a8.5 8.5 0 0 1 17 0z'/%3E%3Cpath d='M12 12v7.5a1.8 1.8 0 0 1-3.6 0'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 12a8.5 8.5 0 0 1 17 0z'/%3E%3Cpath d='M12 12v7.5a1.8 1.8 0 0 1-3.6 0'/%3E%3C/svg%3E");
}

.nav-icon-gift {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 8h17v4h-17zM5 12v8.5h14V12M12 8v12.5'/%3E%3Cpath d='M12 8C10.5 4.5 6.5 4 7 6.5S12 8 12 8zM12 8c1.5-3.5 5.5-4 5-1.5S12 8 12 8z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 8h17v4h-17zM5 12v8.5h14V12M12 8v12.5'/%3E%3Cpath d='M12 8C10.5 4.5 6.5 4 7 6.5S12 8 12 8zM12 8c1.5-3.5 5.5-4 5-1.5S12 8 12 8z'/%3E%3C/svg%3E");
}

.nav-icon-medical {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 3h8l4 4v14H6z'/%3E%3Cpath d='M14 3v4h4'/%3E%3Cpath d='M12 11.5v5M9.5 14h5'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 3h8l4 4v14H6z'/%3E%3Cpath d='M14 3v4h4'/%3E%3Cpath d='M12 11.5v5M9.5 14h5'/%3E%3C/svg%3E");
}

.nav-icon-users {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='9' cy='8' r='3.2'/%3E%3Cpath d='M3.5 19c0-3 2.5-5 5.5-5s5.5 2 5.5 5'/%3E%3Cpath d='M16 5.5a3.2 3.2 0 0 1 0 5'/%3E%3Cpath d='M17.5 14.2c1.9.6 3 2.4 3 4.8'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='9' cy='8' r='3.2'/%3E%3Cpath d='M3.5 19c0-3 2.5-5 5.5-5s5.5 2 5.5 5'/%3E%3Cpath d='M16 5.5a3.2 3.2 0 0 1 0 5'/%3E%3Cpath d='M17.5 14.2c1.9.6 3 2.4 3 4.8'/%3E%3C/svg%3E");
}

.nav-icon-calendar {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3.5' y='5' width='17' height='15' rx='2'/%3E%3Cpath d='M3.5 9.5h17'/%3E%3Cpath d='M8 3.5v3'/%3E%3Cpath d='M16 3.5v3'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3.5' y='5' width='17' height='15' rx='2'/%3E%3Cpath d='M3.5 9.5h17'/%3E%3Cpath d='M8 3.5v3'/%3E%3Cpath d='M16 3.5v3'/%3E%3C/svg%3E");
}

.nav-icon-sitemap {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='9' y='3' width='6' height='5' rx='1'/%3E%3Crect x='3' y='16' width='6' height='5' rx='1'/%3E%3Crect x='15' y='16' width='6' height='5' rx='1'/%3E%3Cpath d='M12 8v4'/%3E%3Cpath d='M6 16v-4h12v4'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='9' y='3' width='6' height='5' rx='1'/%3E%3Crect x='3' y='16' width='6' height='5' rx='1'/%3E%3Crect x='15' y='16' width='6' height='5' rx='1'/%3E%3Cpath d='M12 8v4'/%3E%3Cpath d='M6 16v-4h12v4'/%3E%3C/svg%3E");
}

.nav-icon-user-clock {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='9' cy='7' r='3.2'/%3E%3Cpath d='M3.5 20c0-3 2.5-5 5.5-5h.6'/%3E%3Ccircle cx='17' cy='16.5' r='4'/%3E%3Cpath d='M17 14.7v1.8l1.3 1'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='9' cy='7' r='3.2'/%3E%3Cpath d='M3.5 20c0-3 2.5-5 5.5-5h.6'/%3E%3Ccircle cx='17' cy='16.5' r='4'/%3E%3Cpath d='M17 14.7v1.8l1.3 1'/%3E%3C/svg%3E");
}

.nav-icon-building {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='5' y='3' width='14' height='18' rx='1.5'/%3E%3Cpath d='M9 7h2'/%3E%3Cpath d='M13 7h2'/%3E%3Cpath d='M9 11h2'/%3E%3Cpath d='M13 11h2'/%3E%3Cpath d='M10.5 21v-4h3v4'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='5' y='3' width='14' height='18' rx='1.5'/%3E%3Cpath d='M9 7h2'/%3E%3Cpath d='M13 7h2'/%3E%3Cpath d='M9 11h2'/%3E%3Cpath d='M13 11h2'/%3E%3Cpath d='M10.5 21v-4h3v4'/%3E%3C/svg%3E");
}

.nav-icon-key {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='7.5' cy='15.5' r='3.5'/%3E%3Cpath d='M10 13 20 3'/%3E%3Cpath d='M17 6l2.5 2.5'/%3E%3Cpath d='M14.5 8.5 17 11'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='7.5' cy='15.5' r='3.5'/%3E%3Cpath d='M10 13 20 3'/%3E%3Cpath d='M17 6l2.5 2.5'/%3E%3Cpath d='M14.5 8.5 17 11'/%3E%3C/svg%3E");
}

.nav-icon-file-pdf {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M14 3H7a1.5 1.5 0 0 0-1.5 1.5v15A1.5 1.5 0 0 0 7 21h10a1.5 1.5 0 0 0 1.5-1.5V7.5z'/%3E%3Cpath d='M14 3v4.5h4.5'/%3E%3Cpath d='M8.5 16.5v-3.5h1.2a1.2 1.2 0 0 1 0 2.4H8.5'/%3E%3Cpath d='M13 13v3.5h1a1.75 1.75 0 0 0 0-3.5z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M14 3H7a1.5 1.5 0 0 0-1.5 1.5v15A1.5 1.5 0 0 0 7 21h10a1.5 1.5 0 0 0 1.5-1.5V7.5z'/%3E%3Cpath d='M14 3v4.5h4.5'/%3E%3Cpath d='M8.5 16.5v-3.5h1.2a1.2 1.2 0 0 1 0 2.4H8.5'/%3E%3Cpath d='M13 13v3.5h1a1.75 1.75 0 0 0 0-3.5z'/%3E%3C/svg%3E");
}

.nav-icon-file-signature {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M14 3H7a1.5 1.5 0 0 0-1.5 1.5v15A1.5 1.5 0 0 0 7 21h6'/%3E%3Cpath d='M14 3v4.5h4.5'/%3E%3Cpath d='m20.5 13.5-5.5 5.5-2.5.6.6-2.5 5.5-5.5z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M14 3H7a1.5 1.5 0 0 0-1.5 1.5v15A1.5 1.5 0 0 0 7 21h6'/%3E%3Cpath d='M14 3v4.5h4.5'/%3E%3Cpath d='m20.5 13.5-5.5 5.5-2.5.6.6-2.5 5.5-5.5z'/%3E%3C/svg%3E");
}

.nav-icon-floppy-disk {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4.5 5.5A1 1 0 0 1 5.5 4.5h10L19.5 8.5v10a1 1 0 0 1-1 1h-13a1 1 0 0 1-1-1z'/%3E%3Cpath d='M8 4.5v4h6v-4'/%3E%3Crect x='8' y='13' width='8' height='6.5'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4.5 5.5A1 1 0 0 1 5.5 4.5h10L19.5 8.5v10a1 1 0 0 1-1 1h-13a1 1 0 0 1-1-1z'/%3E%3Cpath d='M8 4.5v4h6v-4'/%3E%3Crect x='8' y='13' width='8' height='6.5'/%3E%3C/svg%3E");
}

.nav-icon-phone {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6.5 4h3l1.5 4-2 1.5a11 11 0 0 0 5.5 5.5l1.5-2 4 1.5v3a1.5 1.5 0 0 1-1.6 1.5A15.5 15.5 0 0 1 5 6.1 1.5 1.5 0 0 1 6.5 4z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6.5 4h3l1.5 4-2 1.5a11 11 0 0 0 5.5 5.5l1.5-2 4 1.5v3a1.5 1.5 0 0 1-1.6 1.5A15.5 15.5 0 0 1 5 6.1 1.5 1.5 0 0 1 6.5 4z'/%3E%3C/svg%3E");
}

.nav-icon-stop {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='8.5'/%3E%3Crect x='9' y='9' width='6' height='6' rx='1'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='8.5'/%3E%3Crect x='9' y='9' width='6' height='6' rx='1'/%3E%3C/svg%3E");
}

.nav-icon-house {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 11 12 4.5l8.5 6.5'/%3E%3Cpath d='M6 10v10h12V10'/%3E%3Cpath d='M9.5 15h5v3.5h-5z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 11 12 4.5l8.5 6.5'/%3E%3Cpath d='M6 10v10h12V10'/%3E%3Cpath d='M9.5 15h5v3.5h-5z'/%3E%3C/svg%3E");
}

.nav-icon-info {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='8.5'/%3E%3Cpath d='M12 11v5.5M12 7.6v.6'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='8.5'/%3E%3Cpath d='M12 11v5.5M12 7.6v.6'/%3E%3C/svg%3E");
}

.nav-icon-warning {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 4 2.8 20h18.4z'/%3E%3Cpath d='M12 10v4.5M12 17.4v.6'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 4 2.8 20h18.4z'/%3E%3Cpath d='M12 10v4.5M12 17.4v.6'/%3E%3C/svg%3E");
}

.nav-icon-pen {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 20h4L19.5 8.5a2.1 2.1 0 0 0-3-3L5 17v3z'/%3E%3Cpath d='M14.5 6.5l3 3'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 20h4L19.5 8.5a2.1 2.1 0 0 0-3-3L5 17v3z'/%3E%3Cpath d='M14.5 6.5l3 3'/%3E%3C/svg%3E");
}

.nav-icon-trash {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6.5h16M9.5 6.5V4h5v2.5'/%3E%3Cpath d='M6.5 6.5 7.5 20h9l1-13.5'/%3E%3Cpath d='M10.5 10v6M13.5 10v6'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6.5h16M9.5 6.5V4h5v2.5'/%3E%3Cpath d='M6.5 6.5 7.5 20h9l1-13.5'/%3E%3Cpath d='M10.5 10v6M13.5 10v6'/%3E%3C/svg%3E");
}

.nav-icon-plus {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round'%3E%3Cpath d='M12 5v14M5 12h14'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round'%3E%3Cpath d='M12 5v14M5 12h14'/%3E%3C/svg%3E");
}

.nav-icon-check {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5 10 17.5 19 7'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5 10 17.5 19 7'/%3E%3C/svg%3E");
}

.nav-icon-cross {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round'%3E%3Cpath d='M6 6l12 12M18 6 6 18'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round'%3E%3Cpath d='M6 6l12 12M18 6 6 18'/%3E%3C/svg%3E");
}

.nav-icon-minus {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round'%3E%3Cpath d='M6 12h12'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round'%3E%3Cpath d='M6 12h12'/%3E%3C/svg%3E");
}

.nav-icon-download {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 3v12M7 11l5 5 5-5M4 20h16'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 3v12M7 11l5 5 5-5M4 20h16'/%3E%3C/svg%3E");
}

/* Scan - corners of a viewfinder around barcode bars (EX-1556). */
.nav-icon-scan {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M3 8V5a2 2 0 0 1 2-2h3M16 3h3a2 2 0 0 1 2 2v3M21 16v3a2 2 0 0 1-2 2h-3M8 21H5a2 2 0 0 1-2-2v-3M7 8v8M10 8v8M13 8v8M17 8v8'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M3 8V5a2 2 0 0 1 2-2h3M16 3h3a2 2 0 0 1 2 2v3M21 16v3a2 2 0 0 1-2 2h-3M8 21H5a2 2 0 0 1-2-2v-3M7 8v8M10 8v8M13 8v8M17 8v8'/%3E%3C/svg%3E");
}

.overlay .scan-overlay-content {
  max-width: 480px;
  padding: 16px;
}

.scan-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 12px;
}

.scan-video {
  display: block;
  width: 100%;
  max-height: 50vh;
  background: #000;
  border-radius: var(--radius);
}

.scan-video[hidden] {
  display: none;
}

.scan-status {
  margin: 8px 0;
}

.scan-form {
  display: flex;
  gap: 8px;
}

.scan-form input {
  flex: 1;
  min-width: 0;
}

/*
 * The result is the one thing left to do in the overlay, so it is a full-width primary button
 * (EX-1577). It used to read as faint green text, and "Nicht gefunden - im Shop suchen" was not
 * recognisable as something to click.
 */
.scan-result {
  display: flex;
  justify-content: center;
  width: 100%;
  margin-top: 12px;
  padding: 14px 18px;
  font-size: 16px;
  text-align: center;
}

.scan-status.is-missing {
  font-weight: 600;
}

.scan-result[hidden] {
  display: none;
}

.pill-open {
  background: var(--surface-alt);
  color: var(--text-muted);
  border: 1px solid var(--border);
}

.pill-done {
  background: var(--good-bg);
  color: var(--good);
}

.pill-failed {
  background: var(--bad-bg);
  color: var(--bad);
}

.metric-scroll-title > .pill {
  flex: none;
}

/*
 * Symbols of the time tracking. The tool used Font Awesome (fa-circle-play, fa-circle-pause,
 * fa-user-secret, fa-mobile, fa-desktop); here they stand as an SVG mask like the rest, so they
 * take on the colour of the link and no font library is needed.
 */
.nav-icon-play {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M10 8.5l6 3.5-6 3.5z' fill='%23000'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M10 8.5l6 3.5-6 3.5z' fill='%23000'/%3E%3C/svg%3E");
}

.nav-icon-pause {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M10 8.5v7M14 8.5v7' stroke-linecap='round'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M10 8.5v7M14 8.5v7' stroke-linecap='round'/%3E%3C/svg%3E");
}

/* Somebody else's name: a person is working on another's behalf. */
.nav-icon-login-as {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='8' r='3.5'/%3E%3Cpath d='M4.5 20a7.5 7.5 0 0 1 15 0'/%3E%3Cpath d='M3 11h18'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='8' r='3.5'/%3E%3Cpath d='M4.5 20a7.5 7.5 0 0 1 15 0'/%3E%3Cpath d='M3 11h18'/%3E%3C/svg%3E");
}

.nav-icon-mobile {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='7' y='2.5' width='10' height='19' rx='2'/%3E%3Cpath d='M11 18.5h2'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='7' y='2.5' width='10' height='19' rx='2'/%3E%3Cpath d='M11 18.5h2'/%3E%3C/svg%3E");
}

.nav-icon-desktop {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='2.5' y='4' width='19' height='12' rx='2'/%3E%3Cpath d='M8 20h8M12 16v4'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='2.5' y='4' width='19' height='12' rx='2'/%3E%3Cpath d='M8 20h8M12 16v4'/%3E%3C/svg%3E");
}

/* Clocking in and out is the page's action - the button takes the full width. */
.time-tracking-status .status-card .button-large {
  width: 100%;
  margin-top: 1.5rem;
  padding: 1rem;
  justify-content: center;
  font-size: 1.4rem;
  font-weight: bold;
}

/*
 * Filled instead of merely outlined: in the tool this is the only large button on the page and
 * carries the colour of the action. The house's outline style stays for all other buttons.
 */
.time-tracking-status .status-card .button-large.button-danger {
  background: var(--ze-error);
  border-color: var(--ze-error);
  color: #fff;
}

.time-tracking-status .status-card .button-large.button-danger:hover {
  background: #bd2130;
  border-color: #bd2130;
  color: #fff;
}

.time-tracking-status .status-card .button-large.button-success {
  background: var(--ze-success);
  border-color: var(--ze-success);
  color: #fff;
}

.time-tracking-status .status-card .button-large.button-success:hover {
  background: #1e7e34;
  border-color: #1e7e34;
  color: #fff;
}

/* A button that only holds the space - the next day lies in the future. */
.button.is-disabled,
.button[disabled] {
  opacity: .55;
  cursor: default;
  pointer-events: none;
}

/*
 * The buttons in the card may be smaller than usual - there are three of them in a tight space.
 *
 * ONE HEIGHT FOR ALL OF THEM, or they do not line up. An icon button's content is the 17px .nav-icon
 * while a text button's is a 12px label whose line box is about 14px, so at the same 3px padding the
 * icon buttons stood roughly 3px taller than "Anmelden" beside them. That is the "not in row" half of
 * Robin's report, and it is a different cause from the off-centre glyph - that one was a stray margin.
 *
 * 25px is the tallest of the three as it stands: 17px of icon plus 2x3px padding plus 2x1px border.
 * Forcing the height is deliberate rather than shrinking the icon: it makes the row equal by
 * construction instead of relying on a label's line box and an icon happening to agree.
 *
 * NOT the cause, checked before changing anything: .entry-actions already has align-items: center and
 * already resets the margin on the wrapping forms, so neither stretched boxes nor form margins were
 * involved.
 */
.board-entry .entry-actions .button {
  padding: 3px 8px;
  font-size: 12px;
  min-height: 25px;
}

.board-entry .entry-actions .button.button-icon {
  padding: 3px 6px;
}

/* Buttons carrying only an icon - in the tool, edit and delete are exactly that. */
.button.button-icon {
  padding: 5px 8px;
  line-height: 1;
}

/*
 * NO TRAILING MARGIN ON A BUTTON WITH NO LABEL. `.button .nav-icon` further up gives every icon in a
 * button a word space on its right so it does not stick to the text. A .button-icon has no text, so
 * that space became dead room on one side only and pushed the glyph left of centre - which is what it
 * looked like on the Schwarzes Brett.
 *
 * The `vertical-align` is inert here and kept only because it is harmless: .button is inline-flex with
 * align-items: center, and a flex item ignores vertical-align. The vertical centring comes from the
 * flex box, not from this line.
 */
.button.button-icon .nav-icon {
  margin-right: 0;
  vertical-align: -3px;
}

/*
 * Yes/no in a table column: a tick or a cross says it faster than a word. The meaning stands
 * there as text as well - for the screen reader only, see .field-note-hidden.
 */
.state-icon {
  display: inline-block;
  width: 18px;
  height: 18px;
  vertical-align: -3px;
}

.state-icon.is-good {
  background-color: var(--good);
}

.state-icon.is-bad {
  background-color: var(--bad);
}

.state-icon.is-muted {
  background-color: var(--text-muted);
}

.todo-overlay-content .todo-icon-button .nav-icon {
  margin: 0;
  vertical-align: 0;
}

.todo-work-note > summary .nav-icon {
  width: 13px;
  height: 13px;
  vertical-align: -2px;
  margin-right: .2rem;
}

/* ------------------------------------------------------------------ Overlays */

/*
 * Overlays lie over the page until they are clicked away. The rules used to hang on
 * #downloads-overlay; since there is a second one (the image view of the article detail page)
 * they sit on the class the markup already carried anyway.
 */
.overlay {
  position: fixed;
  inset: 0;
  z-index: 10001;
  display: none;
  align-items: flex-start;
  justify-content: center;
  padding: calc(var(--nav-height) + 24px) 16px 16px;
  background: rgba(31, 41, 51, .45);
}

.overlay.is-open {
  display: flex;
}

.overlay .overlay-content {
  width: 100%;
  max-width: min(1200px, 96vw);
  max-height: 78vh;
  overflow-y: auto;
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
}

/*
 * The image overlay - nine tenths of the window.
 *
 * Started at half, went by way of three quarters to here. That sufficed in the article
 * administration, where one wants to recognise an image; in the photo studio one decides by it
 * whether a backlabel is legible. Not the full width: the dark border all around says that a page
 * still lies beneath - without it, it looks like one of its own.
 */
#image-overlay .overlay-content {
  width: auto;
  max-width: 90vw;
  max-height: 90vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.report-overlay-content .overlay-head,
.schedule-overlay-content .overlay-head,
.request-overlay-content .overlay-head,
.todo-overlay-content .overlay-head,
.todo-form-overlay-content .overlay-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 20px;
  border-bottom: 1px solid var(--border);
}

.report-overlay-content .overlay-head h2,
.schedule-overlay-content .overlay-head h2,
.request-overlay-content .overlay-head h2,
.todo-overlay-content .overlay-head h2,
.todo-form-overlay-content .overlay-head h2 {
  margin: 0;
  font-size: 1.1rem;
}

.report-overlay-content .overlay-body,
.schedule-overlay-content .overlay-body,
.request-overlay-content .overlay-body,
.todo-overlay-content .overlay-body,
.todo-form-overlay-content .overlay-body {
  padding: 18px 20px;
}

.report-overlay-content .overlay-foot,
.schedule-overlay-content .overlay-foot,
.request-overlay-content .overlay-foot,
.todo-form-overlay-content .overlay-foot {
  display: flex;
  gap: 8px;
  padding: 14px 20px;
  border-top: 1px solid var(--border);
}

/*
 * The position of an image - in the same row as the types, right beside them.
 *
 * A narrow field with the label before it: the number has at most three digits, and a field over
 * half the width would be a promise of more.
 *
 * margin-left: auto stood here, which pushed it to the right end of the block. In a wide window
 * two hand widths then lay between the types and the position, and it looked as though it did not
 * belong. The spacing on the left sets it apart without pushing it away.
 */
.release-position {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: 12px;
  padding-left: 12px;
  border-left: 1px solid var(--border);
  color: var(--text-muted);
  font-size: 13px;
}

/*
 * A number field with a button left and right.
 *
 * The arrows a number field brings along are small, different in every browser and impossible to
 * hit on a tablet. Whoever sorts twenty images needs a surface. They sit at the edges of the
 * field and share its border - see Admin.initSteppers().
 */
.stepper {
  display: inline-flex;
  align-items: stretch;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  overflow: hidden;
}

.stepper-button {
  width: 28px;
  padding: 0;
  border: 0;
  background: var(--surface-alt);
  color: var(--text);
  font-size: 15px;
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
}

.stepper-button:hover {
  background: var(--brand);
  color: #fff;
}

/* The field sits inside the group's border - one of its own would be a second. */
.stepper .release-position-input {
  width: 3.5rem;
  min-width: 3.5rem;
  height: 30px;
  border: 0;
  border-radius: 0;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* The browser's arrows are replaced - the same thing twice would be one too many. */
.stepper .release-position-input::-webkit-outer-spin-button,
.stepper .release-position-input::-webkit-inner-spin-button {
  margin: 0;
  -webkit-appearance: none;
}

.stepper .release-position-input {
  -moz-appearance: textfield;
  appearance: textfield;
}

/*
 * The report from the photo studio.
 *
 * A title, a text area, two buttons - narrow like the other form overlays. The base width of
 * 1200px belongs to the overlays that show a list.
 */
.overlay .report-overlay-content {
  max-width: min(38rem, 94vw);
}

/* EX-1658: changing and adding a duty-schedule entry (ScheduleDayRenderer). */
.overlay .schedule-overlay-content {
  max-width: min(34rem, 94vw);
}

/* --- Time-tracking requests in the overlay ------------------------------ */

.overlay .todo-overlay-content {
  max-width: min(52rem, 94vw);
}

.todo-overlay-content .overlay-head h2 .hint {
  margin-left: .4rem;
  color: var(--ze-dark-grey);
  font-size: .8rem;
  font-weight: normal;
}

/* --- Creating and editing a task in the overlay ------------------------- */

.overlay .todo-form-overlay-content {
  max-width: min(46rem, 94vw);
}

/* ----------------------------------------------------------- Embedded pages */

/* The frame fills the rest of the window, the top and sub-module navigation
   stay put. The embedded script itself stays unchanged. */
.embed-body {
  display: flex;
  flex-direction: column;
  height: 100vh;
  overflow: hidden;
  /* The fixed bars lie above it - this much room they need. */
  padding-top: calc(var(--nav-height) + var(--sub-nav-height));
}

.embed-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex: 0 0 auto;
  padding: 7px 24px;
  background: var(--surface-alt);
  border-bottom: 1px solid var(--border);
}

.embed-title {
  color: var(--text-muted);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .5px;
  text-transform: uppercase;
}

.embed-link {
  color: var(--text-muted);
  font-size: 12px;
}

.embed-link:hover {
  color: var(--brand-dark);
}

/* Search field in the bar above the embedded page. */
.embed-search {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 1 1 auto;
  min-width: 0;
}

.embed-frame {
  flex: 1 1 auto;
  width: 100%;
  min-height: 0;
  border: 0;
  background: var(--surface);
}

/* ------------------------------------------------------------------- Sign-in */

.brand-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--brand);
  box-shadow: 0 0 0 3px rgba(87, 169, 0, .25);
  display: inline-block;
}

.login-body {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  padding: 24px;
}

.login-panel {
  width: 100%;
  max-width: 380px;
  padding: 30px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
}

.login-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 24px;
  font-size: 17px;
  font-weight: 600;
}

.login-form {
  display: flex;
  flex-direction: column;
}

.login-submit {
  justify-content: center;
  margin-top: 24px;
}

.login-error {
  margin-bottom: 18px;
  padding: 11px 14px;
  background: var(--bad-bg);
  border: 1px solid var(--bad);
  border-left-width: 4px;
  border-radius: var(--radius-sm);
  color: #8c1c1c;
  font-size: 13px;
}

.login-notice {
  margin-bottom: 18px;
  padding: 11px 14px;
  background: var(--good-bg);
  border: 1px solid var(--good);
  border-left-width: 4px;
  border-radius: var(--radius-sm);
  color: #1e7a33;
  font-size: 13px;
}

.login-intro {
  margin: 0 0 4px;
  color: var(--text-muted);
  font-size: 13px;
  line-height: 1.5;
}

.login-links {
  margin-top: 18px;
  text-align: center;
  font-size: 13px;
}

.login-form + .login-links {
  margin-top: 20px;
}

.login-links a {
  color: var(--text-muted);
  text-decoration: none;
}

.login-links a:hover {
  color: var(--brand-dark);
  text-decoration: underline;
}

/*
 * The name currently being worked under. It stays visible so nobody books in somebody else's
 * name by accident.
 */
.login-as-hint {
  color: var(--warning, #b26a00);
}

.login-as-name {
  margin-left: 4px;
  font-size: 12px;
  font-weight: 600;
}

/* --- Signing in as somebody else ---------------------------------------- */

/*
 * The hint stands on every page of the time tracking. Whoever overlooks it books in another's
 * name - which is why it catches the eye.
 */
.login-as-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-bottom: 18px;
  padding: 10px 14px;
  border: 1px solid var(--ze-orange);
  border-radius: var(--radius);
  background: rgba(255, 119, 0, .15);
}

.login-as-banner form {
  margin: 0 0 0 auto;
}

.login-as-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 8px;
}

.login-as-list form {
  margin: 0;
}

.login-as-entry {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: var(--text);
}

.login-as-entry:hover {
  background: var(--surface-alt);
}

/* Who is currently being worked as - the entry stands out. */
.login-as-entry.acting {
  border-color: var(--ze-orange);
  background: rgba(255, 119, 0, .12);
}

.login-as-entry .avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 2rem;
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  color: #fff;
  font-size: .8rem;
  font-weight: bold;
}

.login-as-entry .entry-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.login-as-entry .entry-text .name {
  font-weight: 600;
}

.login-as-entry .entry-text .detail {
  color: var(--text-muted);
  font-size: .85rem;
}

.login-as-entry .entry-state {
  margin-left: auto;
  color: var(--text-muted);
  font-size: .85rem;
}

/* ---------------------------------------------------------------- Monitoring */

.monitoring-status {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-bottom: 18px;
  color: var(--text-muted);
  font-size: 13px;
}

.metric-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
  gap: 16px;
  margin-bottom: 28px;
}

.metric-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 12px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border);
}

.metric-title {
  font-size: 16px;
  font-weight: 700;
}

.metric-subtitle,
.metric-hint {
  color: var(--text-muted);
  font-size: 11px;
}

.metric-value {
  font-size: 26px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
}

.metric-value.second {
  margin-top: 16px;
}

.metric-unit {
  display: block;
  color: var(--text-muted);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .5px;
  text-transform: uppercase;
}

.metric-delta {
  display: inline-block;
  margin-top: 4px;
  padding: 1px 8px;
  border-radius: 99px;
  font-size: 12px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.metric-delta.up {
  background: var(--good-bg);
  color: var(--good);
}

.metric-delta.down {
  background: var(--bad-bg);
  color: var(--bad);
}

.metric-delta.flat {
  background: var(--surface-alt);
  color: var(--text-muted);
}

.metric-reference {
  margin-top: 3px;
  color: var(--text-muted);
  font-size: 12px;
}

.metric-rows {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 4px 12px;
  margin: 16px 0 0;
  padding-top: 12px;
  border-top: 1px solid var(--border);
  font-size: 13px;
}

.metric-rows dt {
  color: var(--text-muted);
}

.metric-rows dd {
  margin: 0;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* The Salden tab explains each of its columns under the table - EX-1560. */
.value-explanations {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 6px 16px;
  margin: 16px 0 0;
  padding-top: 12px;
  border-top: 1px solid var(--border);
  font-size: 13px;
}

.value-explanations dt {
  font-weight: 600;
}

.value-explanations dd {
  margin: 0;
  color: var(--text-muted);
}

.metric-backlog {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
  font-size: 13px;
}

.metric-backlog-title {
  display: block;
  margin-bottom: 6px;
  color: var(--text-muted);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .5px;
  text-transform: uppercase;
}

.metric-backlog-list {
  margin: 0;
  padding-left: 18px;
}

.metric-backlog-list li {
  margin-bottom: 2px;
}

/*
 * The overview: summary, shipping state and the list of orders not yet transferred stand three
 * abreast in the first row, the overdue delivery dates move below. Columns of equal width, so the
 * row feels calm.
 *
 * No align-items: start - the tiles of a row should be of equal height. The stretching is done by
 * the default value stretch, the tiles themselves are flex columns (see .metric-card).
 */
/* Four tiles in one row: summary, delivery dates, invoices, not in LISA. */
.metric-grid.overall {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

/*
 * A list inside a tile that may scroll, instead of pulling the card out lengthwise.
 *
 * flex: 1 lets it fill the room the equally tall tile leaves over; min-height: 0 is needed for
 * that, or a flex child grows beyond its content and does not scroll. The maximum height caps the
 * tile when it stands alone in its row.
 */
.metric-scroll-list {
  flex: 1 1 auto;
  min-height: 0;
  margin: 10px 0 0;
  padding: 0;
  list-style: none;
  max-height: 320px;
  overflow-y: auto;
  border-top: 1px solid var(--border);
}

.metric-scroll-list li {
  border-bottom: 1px solid var(--border);
}

/* A row of the same shape that leads nowhere - the refused uploads of EX-1613. */
.metric-scroll-link,
.metric-scroll-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 4px;
  color: inherit;
  text-decoration: none;
}

.metric-scroll-link:hover {
  background: var(--surface-alt);
}

.metric-scroll-main,
.metric-scroll-side {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

/*
 * Takes the room between the order number and the values on the right. Without flex the block
 * shrank to its content, and depending on card width a gap of several hundred pixels yawned in
 * the row. min-width: 0 (set above) lets the text be shortened instead.
 */
.metric-scroll-main {
  flex: 1 1 auto;
  overflow: hidden;
}

.metric-scroll-main strong {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.metric-scroll-side {
  align-items: flex-end;
  text-align: right;
  flex: 0 0 auto;
}

/*
 * Two cards side by side, of equal width.
 *
 * The free .metric-grid fills the row by available room - with four cards that puts sometimes one
 * and sometimes three in a line depending on the window width, and what belongs together is torn
 * apart. With two fixed columns pair stands under pair.
 */
.metric-grid.grid-two {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

/*
 * Three cards of equal width - the ratings.
 *
 * Without this rule they fell to three columns at 1,400 px and to two at 1,200 px, and the third
 * then sat alone below the other two, stretched over half the page. Below 900 px the default
 * further down applies again, so one below the other.
 */
.metric-grid.grid-three {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

@media (max-width: 900px) {
  .metric-grid.grid-three {
    grid-template-columns: 1fr;
  }
}

/* Two cards of equal width - list and history of the image uploads side by side. */
.metric-grid.upload-grid-two {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

/*
 * The cards of the order: two side by side, below one another on narrow windows.
 *
 * Without align-items both stretch to the height of the taller one - with "start" the shorter
 * one used to stand beside it as a stump.
 */
.metric-grid.order-grid {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

/*
 * Order number and marking side by side. .metric-scroll-main is a flex column, where every child
 * would get a row of its own - and the badge would stretch across the whole width.
 */
.metric-scroll-title {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

/* The number may be shortened, the badge beside it may not. */
.metric-scroll-title > strong {
  min-width: 0;
}

/*
 * A row with an extra button: the link to the order takes the room, the button stands to its
 * right. It sits beside the link and deliberately not inside it - nested links are not valid
 * HTML.
 */
.metric-scroll-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.metric-scroll-row > .metric-scroll-link {
  flex: 1 1 auto;
  min-width: 0;
}

.metric-scroll-shopify {
  flex: none;
  /* Kept tight: every pixel here is one the order row beside it goes without. */
  padding: 3px 6px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm, 4px);
  background: var(--surface-alt);
  color: var(--text-muted);
  font-size: 10px;
  font-weight: 600;
  white-space: nowrap;
  text-decoration: none;
}

.metric-scroll-shopify:hover {
  border-color: var(--brand);
  color: var(--brand-dark);
}

/* --- Shipping reports: change the state per row ------------------------- */

/*
 * `.field-input` gives every control `width: 100%`. In the action column, which takes its width
 * from its content, that drives select and column up against each other - measured 233 px instead
 * of the 120 px the longest entry needs, and the table got a horizontal scrollbar from it. Here
 * the content counts, not the cell.
 */
.shipment-status-select {
  width: auto;
  max-width: 100%;
}

/* ---------------------------------------------------------------- My exports */

/* A little air between heading and form. */
.export-form {
  margin-top: 18px;
}

.downloads-panel {
  padding: 18px 20px;
}

.downloads-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 14px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border);
}

.downloads-title {
  font-size: 17px;
  font-weight: 600;
}

.downloads-head-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

.downloads-empty {
  margin: 0;
  color: var(--text-muted);
}

.downloads-table-scroll {
  overflow-x: auto;
}

.downloads-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

.downloads-table th {
  padding: 8px 10px;
  background: var(--surface-alt);
  color: var(--text-muted);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .04em;
  text-align: left;
  text-transform: uppercase;
  white-space: nowrap;
}

.downloads-table td {
  padding: 9px 10px;
  border-top: 1px solid var(--border);
  vertical-align: middle;
  white-space: nowrap;
}

.downloads-table .downloads-file {
  max-width: 220px;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* If the cell carries a note it may grow - otherwise the sentence falls off the table. */
.downloads-table .downloads-file:has(.downloads-note) {
  white-space: normal;
  overflow: visible;
}

/*
 * What a run did, under the file name.
 *
 * A line of its own and quieter than the name above it: it is an aside, not what one came for. The
 * cell cuts overlong names off with an ellipsis - this note must not be cut, so it gets its own
 * line and is allowed to wrap.
 */
.downloads-table .downloads-note {
  display: block;
  margin-top: 2px;
  color: var(--text-muted);
  font-size: 11px;
  white-space: normal;
}

/* The name may wrap - that keeps room on the right for the buttons. */
.downloads-table .downloads-name {
  max-width: 230px;
  white-space: normal;
}

.downloads-table td,
.downloads-table th {
  padding-right: 8px;
}

.downloads-actions {
  display: flex;
  gap: 6px;
  justify-content: flex-end;
}

/*
 * Feedback at the bottom right. It lies over the page but takes no clicks - one should be able to
 * go on working while it is still standing.
 */
.meldungskarte {
  position: fixed;
  right: 20px;
  bottom: 20px;
  z-index: 10002;
  max-width: min(420px, calc(100vw - 40px));
  padding: 12px 16px;
  background: var(--surface);
  border-left: 4px solid var(--brand);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  opacity: 0;
  transform: translateY(8px);
  pointer-events: none;
  transition: opacity .18s ease, transform .18s ease;
}

.meldungskarte.is-open {
  opacity: 1;
  transform: translateY(0);
}

.meldungskarte.is-error {
  border-left-color: var(--bad);
}

/*
 * The foot of the line card: the changes on the left, the total on the right.
 *
 * Both used to stand one below the other, which put the button and the amount it recomputes half a
 * card height apart. In one row both stay in view.
 *
 * Aligned to the top and not centred: the total is three lines high, the button one - centred, the
 * button hung next to the tax line instead of next to the first one.
 */
.review-foot {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 24px;
  margin-top: 28px;
  flex-wrap: wrap;
}

.review-foot > .review-actions {
  margin: 0;
}

/*
 * The total stands on the right - all of it, hint included.
 *
 * The gap of the row carries the block there, but only as long as the button beside it leaves
 * room. margin-left: auto holds it at the right edge even when it does not - and the list inside
 * lines up with the same edge as the amounts in it.
 */
.review-foot > .review-summary {
  margin: 0 0 0 auto;
  text-align: right;
}

.review-foot > .review-summary dl {
  margin-left: auto;
  min-width: 260px;
}

@media (max-width: 700px) {
  .review-foot {
    flex-direction: column;
  }

  .review-foot > .review-summary {
    width: 100%;
  }
}

/*
 * The unrecognised rows - collapsed, with the heading as the handle.
 *
 * They stand on top, because the first thing one wants to know is whether something is missing;
 * expanded they pushed the order screens further down on a large file. So only how many there are
 * stands there, and whoever wants to see them clicks.
 *
 * The pointer on the whole head and not only on the triangle: the heading is the handle, and a
 * handle one can only grasp at one corner does not look like one.
 */
.review-rejected > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  cursor: pointer;

  /*
   * Our own arrow sits on the right; the built-in one would sit on the left.
   *
   * On the left it falls in front of the heading and indents it - and the hand looks for it on
   * the right when the head takes the whole width. Both list-style and the Webkit marker have to
   * go: the browsers bring it along by two different routes.
   */
  list-style: none;
}

.review-rejected > summary::-webkit-details-marker {
  display: none;
}

.review-rejected > summary::after {
  content: '';
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border-right: 2px solid var(--text-muted);
  border-bottom: 2px solid var(--text-muted);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform 0.15s ease;
}

.review-rejected[open] > summary::after {
  transform: rotate(-135deg) translate(-2px, -2px);
}

.review-rejected > summary:hover::after,
.review-rejected > summary:focus-visible::after {
  border-color: var(--brand);
}

.review-rejected > summary:hover .card-title,
.review-rejected > summary:focus-visible .card-title {
  color: var(--brand);
}

/* Heading and hint stand in one column on the left, the arrow beside them on the right. */
.review-rejected > summary > div {
  margin-right: auto;
}

.review-rejected > summary .card-title {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0;
}

.review-rejected > summary .card-hint {
  margin: 4px 0 0;
}

/* Expanded, the content needs air from the head - otherwise the hint sticks to the heading. */
.review-rejected[open] > summary {
  margin-bottom: 14px;
}

/* -------------------------------------------------------- Order confirmation */

/*
 * The confirmation as a grid: details on the left, the total on the right and the closing below it.
 *
 * Details, total and buttons stood as three cards one below the other - between the address and
 * the button lay two card edges and a screen. What belongs to the decision belongs in one field
 * of view.
 *
 * The right column has a fixed width: it carries amounts, and those need no width that grows with
 * the window. The left takes what is left over.
 */
/*
 * The confirmation page's bottom part - two cards side by side.
 *
 * On the left what makes up the order, on the right what it costs and the way out. The cards drop
 * their own bottom margin here, the gap sets the distance - two spacings on top of one another
 * would pull the right column out of line with the left one.
 */
.confirm-grid {
  display: grid;
  /* Two to one - the right column holds figures and two buttons, not a fixed strip. */
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  gap: 22px;
  align-items: start;
  margin-bottom: 22px;
}

.confirm-grid > .card {
  margin-bottom: 0;
}

/* Both cards the same height, however long the details run. */
.confirm-grid > .card {
  align-self: stretch;
}

/*
 * In its own card the total has the whole width. The auto margin is there for the review page,
 * where the figures stand under a wide table; here it would push them off their own card.
 */
.confirm-grid .review-summary dl {
  max-width: none;
  margin-left: 0;
}

/* Figures above, the way out below - with air between them, not a card edge. */
.confirm-grid > .confirm-close .review-summary {
  margin-bottom: 22px;
}

/*
 * Buttons and warning to the right, like the figures above them. The card's right edge is the line
 * everything in it lines up on; a button starting at the left would be the only thing out of it.
 */
.confirm-grid > .confirm-close .form-actions {
  justify-content: flex-end;
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
  flex-wrap: wrap;
}

/*
 * Nothing below the buttons any more.
 *
 * `form > .form-actions:last-child` adds 22px, so that a form closing with its button row does not
 * stick to whatever follows. By its own comment it is not meant for a row inside a card - that one
 * closes with the card's padding - but the selector cannot see the card. Since the warning moved
 * above the buttons they are the last child here and took the margin along with them.
 */
.confirm-grid > .confirm-close form > .form-actions:last-child {
  margin-bottom: 0;
}

/* The warning stands above the button and keeps its distance from it. */
.confirm-grid > .confirm-close .field-hint {
  margin: 0 0 12px;
  text-align: right;
}

/*
 * Narrow, the grid becomes one column, and the order is that of the markup: details first, then
 * total and button - one reads what is ordered, what it costs, and decides then.
 */
@media (max-width: 860px) {
  .confirm-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .confirm-grid > .card {
    grid-column: 1;
  }
}

/*
 * After sending - the page the order is confirmed on.
 *
 * Three figures at the top, underneath what happens next and, where it is asked for, the rating.
 * The metric cards of the house are laid out for a monitoring board and take 400px each; here they
 * hold an order number, a count and an amount, and three of those fit in a row on a laptop.
 */
.page-done .metric-grid {
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  margin-bottom: 22px;
}

/*
 * The two ways on, at the foot of the page.
 *
 * Without the line above them: form-actions draws one where it closes a form, and here there is no
 * form - the line would run under the cards and separate nothing.
 */
.done-actions {
  padding-top: 0;
  border-top: 0;
}

/*
 * Delivery, payment and note - three details that do not stand in the table.
 *
 * As a row and not as a list one below the other: they are skimmed, not read, and side by side one
 * sees with a single glance whether everything is right.
 */
.confirm-details {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 18px 24px;
  margin: 0;
}

.confirm-details dt {
  margin: 0 0 4px;
  color: var(--text-muted);
  font-size: 12px;
}

.confirm-details dd {
  margin: 0;
  line-height: 20px;
}

/* -------------------------------------------------------- Customer interface */

/*
 * The rhythm of the developer page.
 *
 * Headings carry no margin in this house, and the building blocks of this page - filter bar,
 * table, example block, field - bring none along either. On other pages a paragraph that has one
 * stands in between; here they butt against each other. So the spacing is written out here once
 * instead of arising from whatever happens to stand beside it.
 *
 * Limited to .page-developer: the same building blocks stand on many pages of the house, and what
 * looks right there cannot be checked from here.
 */
.page-developer .card > h2 {
  margin-bottom: 14px;
}

.page-developer .card > .filter-bar {
  margin-bottom: 14px;
}

/*
 * The figures of "Wie es laeuft".
 *
 * A moment in time is not a number: set bold at 26 pixels it burst the card, while the three
 * counters beside it live on exactly that. Hence a second size for the one case.
 */
.page-developer .card > .metric-grid {
  margin-bottom: 18px;
}

.page-developer .metric-value.is-text {
  font-size: 17px;
  font-weight: 600;
}

.page-developer .card > .table-scroll,
.page-developer .card > .data-table {
  margin-top: 0;
}

/* The head of a detail below the list - it otherwise sits on the table. */
.page-developer .card > h3 {
  margin: 22px 0 8px;
}

/* The field with the freshly generated key. */
.page-developer .card > .field {
  margin-top: 14px;
}

/* The hint below the button. */
.page-developer .card > .form-actions + .field-hint {
  margin-top: 10px;
}

/*
 * The freshly generated key - the only moment in which it is readable.
 *
 * Field and warning stand in one block because they belong together: the key to take away and
 * right below it the sentence that there is no second time. The border lifts it out of the card
 * without shouting - what stands here is important, but it is not an error.
 */
.page-developer .key-fresh {
  margin-bottom: 18px;
  padding: 16px;
  border: 1px solid var(--border);
  border-left: 3px solid var(--brand);
  border-radius: var(--radius);
  background: var(--surface);
}

.page-developer .key-fresh .field-label {
  display: block;
  margin-bottom: 6px;
}

.page-developer .key-fresh .field-code {
  width: 100%;
}

.page-developer .key-fresh-warning {
  margin: 10px 0 0;
  color: var(--text-muted);
  font-size: 12px;
  line-height: 18px;
}

/*
 * The key in force: itself first, large enough to read off, with its state beside it. The dates
 * stand as a hint line below - they answer a question one has afterwards.
 */
.page-developer .key-current {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.page-developer .key-current-prefix {
  font-family: ui-monospace, "SFMono-Regular", "Menlo", monospace;
  font-size: 15px;
  letter-spacing: 0.3px;
}

.page-developer .key-current + .field-hint {
  margin-top: 6px;
}

/* No key there yet - then the sentence carries the card until the button is pressed. */
.page-developer .key-none {
  margin: 0;
}

/*
 * The row whose detail is currently standing below.
 *
 * Without it one no longer knows after expanding which of fifty rows the block belongs to - the
 * jump there is short, and the list above keeps looking the same.
 */
.page-developer .data-table tr.is-open > td {
  background: var(--highlight-bg);
}

.page-developer .data-table tr.is-open > td:first-child {
  box-shadow: inset 3px 0 0 var(--brand);
}

.page-developer #request-detail[hidden] {
  display: none;
}

/*
 * The unfolded row.
 *
 * It pushes itself in as a row of its own under the one it belongs to - hence no border all
 * around but only padding inside: the table should not fall apart over it. The line on the left
 * ties it to the row above.
 */
.page-developer .request-detail-row > td {
  padding: 14px 12px 16px;
  background: var(--highlight-bg);
  box-shadow: inset 3px 0 0 var(--brand);
}

.page-developer .request-detail-row h3 {
  margin: 0 0 8px;
  font-size: 14px;
}

.page-developer .request-detail-row p {
  margin: 10px 0 0;
}


/*
 * One section of the interface description: heading, explanation, example.
 *
 * They used to stand one below the other without spacing - .mail-preview-text carries margin: 0,
 * because in the mail preview it stands alone in its card. Here six of them follow one another,
 * and without air in between the card reads as a single block.
 *
 * The spacing sits on the section and not on the example: that way it also applies where a list or
 * a table stands instead of an example.
 */
.api-section {
  margin-top: 24px;
}

.api-section:first-of-type {
  margin-top: 16px;
}

.api-section h3 {
  margin: 0 0 6px;
}

.api-section .field-hint {
  margin: 0 0 10px;
}

.api-section .data-table {
  margin-top: 4px;
}

/*
 * The way in: four steps in the order one takes them.
 *
 * The numbers are the point - so they stay visible and do not slide under the text of the previous
 * line.
 */
.api-steps {
  margin: 0;
  padding-left: 20px;
}

.api-steps li {
  margin-bottom: 6px;
  line-height: 20px;
}

.api-steps li:last-child {
  margin-bottom: 0;
}

/*
 * An input field inside a table cell.
 *
 * The fields of the house bring a bottom margin along so that they can stand one below the other.
 * In a cell that is a hole under the row, and with ten rows the table grows ten holes longer. The
 * width holds the column together: a price column as wide as the field permits pushes everything
 * beside it out of view.
 */
.field-in-table {
  margin-bottom: 0;
  max-width: 110px;
}

.field-in-table .field-input {
  padding-top: 5px;
  padding-bottom: 5px;
}

/*
 * Paging below a table.
 *
 * Back, where you are, forward. A list of page numbers would be three hundred links for a supplier
 * with fifteen thousand articles; whoever looks for an article searches for it, and whoever pages
 * goes one page further.
 *
 * The position in the middle, so that the two buttons stand at the edges and do not wander when
 * one of them is missing - on the first page there is no back.
 */
.table-paging {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 16px;
}

.table-paging .field-hint {
  margin: 0 auto;
}

/* -------------------------------------------------------------------- Manual */

/*
 * The manual of the two portals.
 *
 * A page of text and not a user interface: narrower than the rest, so the lines do not run over
 * half the width of a screen. Beyond about seventy-five characters the eye loses the next line on
 * the way back.
 */
.page-manual .manual {
  max-width: 760px;
}

.manual-head {
  margin-bottom: 28px;
}

.manual-print {
  margin-top: 12px;
}

/*
 * The table of contents.
 *
 * Two levels, the second indented and quieter. Without bullets - the numbers of the ordered list
 * are enough, and a bullet and a number side by side are two marks for one statement.
 */
.manual-toc {
  margin-bottom: 36px;
  padding: 18px 22px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface-alt);
}

.manual-toc h2 {
  margin-bottom: 10px;
  font-size: 15px;
}

.manual-toc ol {
  margin: 0;
  padding-left: 20px;
}

.manual-toc ol ol {
  margin: 2px 0 6px;
  font-size: 13px;
  color: var(--text-muted);
}

.manual-toc a {
  color: inherit;
  text-decoration: none;
}

.manual-toc a:hover {
  text-decoration: underline;
}

/*
 * Chapter and section.
 *
 * The number stands in front of the heading and not inside it: that way it stays on its line when
 * a long heading wraps, and the heading below it does not slide under the number.
 */
.manual-chapter {
  margin-bottom: 40px;
  scroll-margin-top: 80px;
}

.manual-chapter > h2 {
  padding-bottom: 8px;
  border-bottom: 2px solid var(--border);
  font-size: 22px;
}

.manual-section {
  margin-top: 22px;
  scroll-margin-top: 80px;
}

.manual-section > h3 {
  margin-bottom: 6px;
  font-size: 16px;
}

.manual-number {
  display: inline-block;
  min-width: 2.4em;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.manual-lead {
  margin-top: 8px;
  color: var(--text-muted);
}

.manual p,
.manual ul,
.manual ol {
  margin: 8px 0;
  line-height: 1.6;
}

.manual ul,
.manual-steps {
  padding-left: 22px;
}

.manual li {
  margin-bottom: 4px;
}

/* Something one has to know before reading on - hence the line on the left. */
.manual-note {
  margin: 12px 0;
  padding: 10px 14px;
  border-left: 3px solid var(--brand);
  background: var(--surface-alt);
}

/*
 * A template line, unchanged.
 *
 * Fixed pitch, so that semicolon stands under semicolon - whoever types the line off then sees
 * where an empty column is meant.
 */
.manual-code {
  margin: 12px 0;
  padding: 12px 14px;
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface-alt);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  line-height: 1.7;
  white-space: pre;
}

.manual-table-scroll {
  margin: 12px 0;
  overflow-x: auto;
}

.manual-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
}

.manual-table th,
.manual-table td {
  padding: 7px 10px;
  border-bottom: 1px solid var(--border);
  text-align: left;
  vertical-align: top;
}

.manual-table th {
  font-weight: 700;
}

/*
 * Printing.
 *
 * The manual should be storable as a PDF without a file being built for it. What has no business
 * on paper falls away: the bar, the print button itself, the footer. The links of the table of
 * contents stay - in a PDF they are anchors.
 */
@media print {
  #top-nav-bar,
  .manual-print,
  .portal-foot,
  footer {
    display: none !important;
  }

  .page-manual .manual {
    max-width: none;
  }

  /*
   * A chapter begins on a new page, the first one does not.
   *
   * `~` says "every chapter preceded by another chapter", which is all of them but the first - the
   * same form as .month-list-employee below, and for the same reason. The exemption it replaced
   * matched by TAG: it held only while no other <section> stood before the chapters (EX-1516).
   */
  .manual-chapter ~ .manual-chapter {
    break-before: page;
  }

  /* A heading alone at the foot of a page is no heading. */
  .manual-chapter > h2,
  .manual-section > h3 {
    break-after: avoid;
  }

  .manual-note,
  .manual-code,
  .manual-table-scroll {
    break-inside: avoid;
  }

  .manual-toc {
    background: none;
  }

  /*
   * ---------- Monatsliste: one page per employee ----------
   *
   * Stefanie via EX-1460: "Die Uebersicht pro Mitarbeiter sollte bitte auf eine Seite passen, also
   * pro DN eine Seite." The page carried no print rules at all before this - not a line.
   *
   * `break-before` and not `break-after`: breaking AFTER the last employee leaves a blank sheet.
   *
   * THE SIBLING COMBINATOR RATHER THAN A FIRST-OF-TYPE EXEMPTION, AND THAT IS THE WHOLE POINT OF THE
   * RULE'S SHAPE. The obvious version puts `break-before: page` on every employee and exempts the first
   * one by its type position - and that worked, for a reason living in another directory: a type
   * position matches by TAG among siblings, so it only picks the first employee while no other
   * `<section>` shares the container. FormRenderer renders its card as a `<div>` (CARD_WRAP), and that
   * is the only reason it held.
   *
   * (The exempting selector is deliberately NOT spelled out here: a test asserts it is absent from this
   * file, and prose quoting it would fail that test. Same trap as a mutation probe matching its own
   * explanation.)
   *
   * Somebody making that card a `<section>` - a plausible semantic tidy-up - would leave
   * `:first-of-type` matching nothing, every employee would get a break before it, and EVERY PRINTOUT
   * WOULD START WITH A BLANK SHEET. Silently, in print, where nobody is looking.
   *
   * `~` says "every employee preceded by another employee", which is all of them but the first. It
   * needs no exemption, it cares about nothing but this class, and it survives both a changed tag
   * elsewhere and anything inserted between the cards. Found by review-2, which traced every element
   * in the container rather than reading the rule.
   */
  .month-list-employee ~ .month-list-employee {
    break-before: page;
  }

  /*
   * The chrome, and SCOPED TO THIS PAGE on purpose. `.page-head` and `.filter-card` are the whole
   * module's, so hiding them unscoped would change what every other page prints - a change nobody
   * asked for. `page-month-list` was added to the page's own div for exactly this.
   */
  .page-month-list .page-head,
  .page-month-list .filter-card,
  .page-month-list .login-as-banner {
    display: none;
  }

  /*
   * A day row must not be split across two sheets, and the employee's name has to stay with their
   * table - a heading alone at the foot of a page is the defect the manual rules name above.
   */
  .month-list-employee > h2 {
    break-after: avoid;
  }

  .month-list-employee tr {
    break-inside: avoid;
  }

  /*
   * A4 PORTRAIT, AND FOR THIS PAGE ONLY - EX-1583, Stefanie Kelz 09.10.2026: "Hochformat". A named
   * page rather than a bare @page, which would turn every printout of the admin to portrait; only the
   * month list's own div asks for it. Possible since the month list shows ten columns instead of
   * fourteen (dayTable.php, $dayTableHideBalance).
   */
  .page-month-list {
    page: month-list;
  }

  @page month-list {
    size: A4 portrait;
  }
}

/* --------------------------------------------------------------- Photo studio */

/*
 * A remark in the course of an issue.
 *
 * Several of them stand one below the other - in the list inside a table cell, on the article page
 * inside a card. The line on the left keeps them apart without each needing a box of its own; the
 * last one carries no bottom margin, otherwise the cell would sit crooked.
 */
.issue-note {
  border-left: 2px solid var(--border);
  padding-left: 10px;
  margin-bottom: 10px;
}

.issue-note:last-child {
  margin-bottom: 0;
}

.issue-note .field-hint {
  margin-top: 2px;
}

/*
 * A settled issue on the article page.
 *
 * It stands there so that the second time round one reads why it did not go easily the first time
 * - but it is no longer a task, and should not look like one either. Hence paler than the open
 * case above it and without a border of its own: a second box next to the first would double the
 * weight of the matter.
 *
 * The line on the left separates several of them. The last one carries no bottom margin, otherwise
 * the card would sit crooked at the bottom.
 */
.issue-settled {
  opacity: .75;
  padding-bottom: 12px;
  margin-bottom: 12px;
  border-bottom: 1px solid var(--border);
}

.issue-settled:last-of-type {
  padding-bottom: 0;
  margin-bottom: 0;
  border-bottom: 0;
}

/* --- Releasing images ---------------------------------------------------- */

/*
 * One block per image: the view on the left, the type on the right. Large enough to tell front
 * from back - on a postage stamp that is impossible, and it is exactly the question here.
 */
.release-image {
  display: grid;
  grid-template-columns: minmax(180px, 320px) 1fr;
  gap: 20px;
  margin-bottom: 16px;
  padding: 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--border);
  border-radius: var(--radius);
}

/* What is open catches the eye; what already has a type is only there to look at. */
.release-image.is-open {
  border-left-color: var(--brand);
}

/*
 * An old image the release is about to delete - see ImageReleaseService::deleteReplacedImages().
 * Shown so whoever releases sees what goes, and set apart so nobody mistakes it for a new one.
 */
.release-image.is-replaced {
  border-left-color: var(--bad);
  background: var(--bad-bg);
}

.release-image-view {
  display: flex;
  align-items: flex-start;
  justify-content: center;
}

.release-image-view img {
  max-width: 100%;
  max-height: 320px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-alt);
}

.release-image-side {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.release-image-head .field-hint {
  display: block;
  margin-top: 4px;
}

.release-types {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

/*
 * The type as a row of buttons. Radio fields lie beneath them: that way the page gets by without
 * a script, the keyboard operates it by itself, and the browser holds the choice together.
 */
.release-type input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.release-type span {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 9px 14px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  color: var(--text);
  font-size: 13px;
  cursor: pointer;
}

.release-type span:hover {
  border-color: var(--brand);
}

.release-type input:checked + span {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
}

/* Visible where the keyboard points - without an outline the field would be invisible. */
.release-type input:focus-visible + span {
  outline: 2px solid var(--brand-dark);
  outline-offset: 2px;
}

@media (max-width: 720px) {
  .release-image {
    grid-template-columns: 1fr;
  }
}


.image-overlay-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
}

.image-overlay-name {
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.image-overlay-body {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  overflow: auto;
}

/* What is left after the head bar and the padding - see .image-overlay-head. */
.image-overlay-body img {
  max-width: 100%;
  max-height: calc(90vh - 90px);
  object-fit: contain;
}

.image-thumb {
  display: block;
  width: 72px;
  height: 72px;
  object-fit: contain;
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

.image-table .image-name {
  font-weight: 600;
  word-break: break-word;
}

.image-position {
  width: 84px;
}

/* ------------------------------------------------------ Orders and finances */

.pii-state {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
}

.pii-changed {
  color: var(--text-muted);
  font-size: 12px;
}

/* ==========================================================================
   Incoming payments: recognised order number and notice
   ========================================================================== */

/*
 * The order number recognised in the payment reference - see OrderReferenceHighlighter.
 *
 * Deliberately not green. Green on this screen means the amount matches the order, and the
 * recognised number says nothing of the kind - it only points at what was found. It wore the brand
 * green before, close enough to the green of a matching amount that the two read as one statement.
 */
mark.order-ref {
  padding: 1px 5px;
  border-radius: 4px;
  background: var(--highlight-bg);
  color: var(--highlight);
  font-weight: 600;
  white-space: nowrap;
  cursor: help;
}

/*
 * Whether the amount matches is carried by the whole row, not only by the badge at its end, so the
 * exceptions can be seen without reading a single number.
 *
 * The tint sits on the row rather than on the cells: hovering paints a translucent grey onto the
 * cells, and that only composites into something readable if it has a colour underneath.
 */
table.data-table tbody tr.amount-match {
  background: var(--good-row);
}

table.data-table tbody tr.amount-mismatch {
  background: var(--bad-row);
}

/* ------------------------------------------------------------------ Articles */

/*
 * The channel prices stand collapsed on the article page - they are lengthy and not wanted on
 * every visit. <details> brings the opening and closing along, and needs no script for it.
 */
.price-details > .price-summary {
  display: flex;
  align-items: baseline;
  gap: 10px;
  cursor: pointer;
  list-style: none;
}

.price-details > .price-summary::-webkit-details-marker {
  display: none;
}

.price-details > .price-summary::before {
  content: '▸';
  color: var(--text-muted);
}

.price-details[open] > .price-summary::before {
  content: '▾';
}

.price-details[open] > .price-summary {
  margin-bottom: 6px;
}

.price-group + .price-group {
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1px solid var(--border);
}

.price-group-title {
  margin: 0 0 10px;
  font-size: 15px;
  font-weight: 600;
}

/* Article card of the channel prices: image on the left, details beside it. */
.item-card {
  display: flex;
  align-items: flex-start;
  gap: 16px;
}

.item-card-image {
  flex: 0 0 auto;
  width: 96px;
  height: 96px;
  object-fit: contain;
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

.item-card-image-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 4px;
  color: var(--text-muted);
  font-size: 11px;
  text-align: center;
}

.item-card-text {
  min-width: 0;
}

/* ----------------------------------------------------- Time tracking: status */

/* --- Status page -------------------------------------------------------- */

.time-tracking-status {
  /* At a desk there is room - the requests per type need it. */
  max-width: 60rem;
  margin: 0 auto;
}

.time-tracking-status .status-header {
  display: grid;
  grid-template-columns: 1fr auto;
  /* The day navigation stretches to the height of the name column, so no gap is left on the right. */
  align-items: stretch;
  gap: .5rem;
  margin-bottom: 1rem;
}

.time-tracking-status .status-header .employee .name {
  font-size: 1.9rem;
  font-weight: bold;
  line-height: 1.2;
}

.time-tracking-status .date-navigation {
  display: flex;
  align-items: stretch;
  gap: .3rem;
}

.time-tracking-status .date-navigation .date {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 9rem;
  padding: 0 .5rem;
  border: 1px solid var(--ze-grey);
  border-radius: .3rem;
  background-color: var(--ze-light-grey);
  text-align: center;
}

.time-tracking-status .date-navigation .date.today {
  font-weight: bold;
}

.time-tracking-status .status-card {
  padding: 1.5rem;
  border: 1px solid var(--ze-grey);
  border-radius: .5rem;
  background-color: var(--ze-light-grey);
  text-align: center;
}

.time-tracking-status .status-card.active {
  border-color: var(--ze-success);
  background-color: var(--ze-success-bg);
}

.time-tracking-status .status-card .state {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  color: var(--ze-dark-grey);
}

.time-tracking-status .status-card .state .indicator {
  display: inline-block;
  width: .7rem;
  height: .7rem;
  border-radius: 50%;
  background-color: var(--ze-grey);
}

.time-tracking-status .status-card.active .state {
  color: var(--ze-success);
  font-weight: bold;
}

.time-tracking-status .status-card.active .state .indicator {
  background-color: var(--ze-success);
  animation: timeTrackingPulse 2s ease-in-out infinite;
}

.time-tracking-status .status-card .current {
  margin-top: .5rem;
  font-size: 3.5rem;
  font-weight: bold;
  line-height: 1.1;
  /* So the digits do not jump while counting up. */
  font-variant-numeric: tabular-nums;
}

.time-tracking-status .status-card .current-label {
  color: var(--ze-dark-grey);
}

.time-tracking-status .status-card .todo-select {
  margin-top: 1.5rem;
  text-align: left;
}

.time-tracking-status .status-card .todo-select label {
  margin-bottom: .2rem;
  font-weight: bold;
}

/* Notices above the card - absence, home office, a forgotten clock-out. */
.time-tracking-status .status-banner {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin-bottom: .5rem;
  padding: .6rem 1rem;
  border-radius: .3rem;
  background-color: var(--ze-blue-bg);
}

.time-tracking-status .status-banner.freetime {
  background-color: rgba(255, 119, 0, .15);
}

.time-tracking-status .status-banner.stale {
  background-color: rgba(238, 255, 110, .3);
}

/* The task of the running booking stands below the clock. */
.time-tracking-status .status-card .todo-current {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .4rem;
  margin-top: 1rem;
  color: var(--ze-dark-grey);
}

.time-tracking-status .status-card .todo-select .no-todo {
  padding: .5rem;
  border-radius: .3rem;
  background-color: rgba(238, 255, 110, .3);
}

.time-tracking-status .worklog-list {
  margin-top: 1.5rem;
}

/* Progress and deviation of the status card - in the tool part of .status-card. */
.time-tracking-status .status-card .progress-bar-container {
  overflow: hidden;
  height: .6rem;
  margin: 1rem 0 .5rem;
  border-radius: .3rem;
  background-color: var(--ze-grey);
}

.time-tracking-status .status-card .progress-bar-container .progress-bar {
  height: 100%;
  background-color: var(--ze-blue);
  transition: width .5s linear;
}

.time-tracking-status .status-card .progress-bar-container .progress-bar.reached {
  background-color: var(--ze-success);
}

.time-tracking-status .status-card .difference {
  font-weight: bold;
}

.time-tracking-status .status-card .difference.positive {
  color: var(--ze-success);
}

.time-tracking-status .status-card .difference.negative {
  color: var(--ze-orange);
}

/* The bookings carry their button to the right of the heading. */
.time-tracking-status .worklog-list {
  margin-top: 1.5rem;
}

.time-tracking-status .worklog-list .sub-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: .3rem;
  font-size: 1.4rem;
  font-weight: bold;
}

/* Note about the breaks below the bookings. */
.time-tracking-status .break-info {
  margin-top: .3rem;
  color: var(--ze-dark-grey);
  font-size: .9rem;
}

/* The delete buttons stand as forms of their own below the table. */
.time-tracking-status .worklog-delete {
  display: inline-block;
  margin: .3rem .3rem 0 0;
}

/* The delete forms of the bookings carry only the data now - the button sits in the row. */
.time-tracking-status .worklog-delete {
  display: none;
}

/* --- Bookings: read first, then edit ------------------------------------ */

/*
 * Both versions stand in the markup, only ever one of them is visible. The fields are disabled on
 * top of that as long as nothing is being edited - hiding alone would still submit them.
 */
.worklog-list .worklog-edit {
  display: none;
}

.worklog-list.is-editing .worklog-edit {
  display: inline-block;
}

.worklog-list.is-editing .worklog-view {
  display: none;
}

/* The buttons of the bookings stand as a group to the right of the heading. */
.worklog-list .worklog-actions {
  display: flex;
  align-items: center;
  gap: .4rem;
}

/* ------------------------------------------------- Time tracking: overview */

/* --- Absence and holiday calendar --------------------------------------- */

.time-tracking-calendar .calendar-filter,
.time-tracking-department .department-filter {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: .75rem;
  margin: 1rem 0;
  padding: .75rem;
  border: 1px solid #dee2e6;
  border-radius: .25rem;
  background: var(--ze-light-grey);
}

.time-tracking-calendar .calendar-filter-actions,
.time-tracking-department .department-filter-actions {
  display: flex;
  align-items: center;
  gap: .5rem;
}

/* --- Department overview ------------------------------------------------- */

.time-tracking-department .department-group {
  margin-bottom: 1.5rem;
  border: 1px solid #dee2e6;
  border-radius: .25rem;
}

.time-tracking-department .department-headline {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  padding: .5rem .75rem;
  border-bottom: 1px solid #dee2e6;
  background: var(--ze-light-grey);
}

.time-tracking-department .department-name {
  display: flex;
  align-items: center;
  gap: .4rem;
  font-weight: bold;
}

.time-tracking-department .department-name .count {
  padding: 0 .4rem;
  border-radius: .6rem;
  background: var(--ze-grey);
  color: #fff;
  font-size: .75rem;
}

.time-tracking-department .department-figures {
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem;
}

.time-tracking-department .department-figure label,
.time-tracking-department .user-card label {
  display: block;
  color: var(--ze-dark-grey);
  font-size: .75rem;
}

.time-tracking-department .department-users {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr));
  gap: .75rem;
  padding: .75rem;
}

.time-tracking-department .user-card {
  padding: .6rem;
  border: 1px solid #dee2e6;
  border-radius: .25rem;
  font-size: .85rem;
}

.time-tracking-department .user-card-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: .4rem;
  margin-bottom: .5rem;
}

.time-tracking-department .user-card-head .name {
  font-weight: bold;
}

.time-tracking-department .user-card-head .working-hours {
  color: var(--ze-dark-grey);
  font-size: .75rem;
}

.time-tracking-department .user-card-balances,
.time-tracking-department .user-card-figures {
  display: grid;
  gap: .3rem .75rem;
  margin-bottom: .5rem;
}

.time-tracking-department .user-card-balances {
  grid-template-columns: repeat(3, 1fr);
  padding-bottom: .5rem;
  border-bottom: 1px solid #eee;
}

.time-tracking-department .user-card-figures {
  grid-template-columns: repeat(2, 1fr);
}

.time-tracking-department .user-card-week .week-days {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: .2rem;
  margin-bottom: .5rem;
}

.time-tracking-department .user-card-future {
  margin-bottom: .5rem;
}

.time-tracking-department .user-card-note .note-input {
  width: 100%;
  min-height: 3.5rem;
  margin-bottom: .35rem;
  font-size: .8rem;
  resize: vertical;
}

/* -------------------------------------------- Time tracking: year overview */

/* The metric tiles - .time-tracking-analysis-summary in the tool. */
.time-tracking-analysis-summary {
  display: grid;
  /* At a desk three tiles stand side by side, below that it becomes fewer. */
  grid-template-columns: repeat(3, 1fr);
  gap: .8rem;
  margin-top: 1.5rem;
  margin-bottom: 1.5rem;
}

.time-tracking-analysis-summary .summary-tile {
  padding: 1rem;
  border: 1px solid var(--ze-grey);
  border-left-width: .4rem;
  border-radius: .4rem;
  background-color: var(--ze-light-grey);
}

.time-tracking-analysis-summary .summary-tile .label {
  display: flex;
  align-items: center;
  gap: .4rem;
  color: var(--ze-dark-grey);
}

.time-tracking-analysis-summary .summary-tile .value {
  margin: .2rem 0;
  font-size: 2rem;
  font-weight: bold;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}

.time-tracking-analysis-summary .summary-tile .hint {
  color: var(--ze-dark-grey);
  font-size: .85rem;
}

.time-tracking-analysis-summary .summary-tile.positive {
  border-left-color: var(--ze-success);
}

.time-tracking-analysis-summary .summary-tile.positive .value {
  color: var(--ze-success);
}

.time-tracking-analysis-summary .summary-tile.negative {
  border-left-color: var(--ze-error);
}

.time-tracking-analysis-summary .summary-tile.negative .value {
  color: var(--ze-error);
}

.time-tracking-analysis-summary .summary-tile.neutral {
  border-left-color: var(--ze-blue);
}

/* Saturday and Sunday step back - there is rarely anything there anyway. */
.time-tracking-time-table .analysis-days tr.weekend td {
  color: var(--ze-dark-grey);
  background: #fbfbfb;
}

/* What is still to come has not happened yet. */
.time-tracking-time-table .analysis-days tr.future td {
  color: #a8a8a8;
}

/*
 * EX-1583, Stefanie Kelz 09.10.2026, on both pages that show the day table: the Ist stands out, and a
 * line under each Sunday sets the weeks apart. The line is a border of the Sunday's cells, so it
 * prints as well - and with border-collapse it is the row's lower edge, not a gap.
 */
.time-tracking-time-table .analysis-days td.actual {
  font-weight: 700;
}

.time-tracking-time-table .analysis-days tr.sunday td {
  border-bottom: 2px solid var(--ze-dark-grey);
}

/* A shortfall catches the eye - the tool holds it that way too. */
.time-tracking-time-table .analysis-days td.minus {
  background: rgba(255, 59, 59, .25);
}

/*
 * And a surplus does the same - EX-1459, Melanie Haider: "alle Plusstunden gruen".
 *
 * The same .25 alpha as the shortfall above, over the green the module already uses for a good state
 * (--ze-success). Two colours of the same weight: a day that differs is marked, in whichever
 * direction, and a day that comes out even stays plain.
 */
.time-tracking-time-table .analysis-days td.plus {
  background: rgba(40, 167, 69, .25);
}

/*
 * The days scroll in a box of their own, under a header row that stays - EX-1524, see dayTable.php.
 *
 * As tall as the window below the two fixed navigation bars, less a margin, so once the box is in
 * view the page stands still and only the days move. It scrolls both ways, which makes it the
 * scrollport the sticky header sticks to - the reason EX-1459 could not have one.
 */
.time-tracking-time-table .analysis-days-scroll {
  max-height: calc(100vh - var(--nav-height) - var(--sub-nav-height) - 24px);
  overflow: auto;
  margin-bottom: 1rem;
}

.time-tracking-time-table .analysis-days-scroll .analysis-days {
  margin-bottom: 0;
}

/*
 * The header cells stick. With border-collapse the cell borders belong to the table, not the cell,
 * and would stay behind while the row moves - the inset shadow draws the lower line on the cell.
 */
.time-tracking-time-table .analysis-days thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  box-shadow: inset 0 -1px 0 #dee2e6;
}

/*
 * ON PAPER THE BOX IS GONE AGAIN - review-1 on EX-1524. A scroll container does not break across
 * pages: printed, the box moved whole to the next sheet (the Monatsliste, one page per employee since
 * EX-1460, put each name alone on a page) and clipped everything below its height (the year printed
 * about 32 of its days). Without the box the table flows over the pages as before, and the browser
 * repeats the <thead> on each one - which the sticky header inside the box would not.
 */
@media print {
  .time-tracking-time-table .analysis-days-scroll {
    max-height: none;
    overflow: visible;
  }

  .time-tracking-time-table .analysis-days thead th {
    position: static;
    box-shadow: none;
  }
}

/*
 * Urlaub, Krankenstand and Zeitausgleich in a colour each - EX-1524, Robin 07.10.2026: the colours the
 * overview gave them before a29afabce (badge-primary, -danger, -info, admin.css:3021-3023 then) and the
 * old tool before that. A light tint of the whole row and a stripe of the full colour on the first
 * cell, so the figures stay as legible as on any other day.
 *
 * The difference cell keeps its own red or green (td.minus / td.plus above) - that is the other thing
 * this table says about a day, and a tint over it would hide it.
 */
.time-tracking-time-table .analysis-days tr.absence-vacation td:not(.minus):not(.plus) {
  background: rgba(0, 123, 255, .10);
}

.time-tracking-time-table .analysis-days tr.absence-sick td:not(.minus):not(.plus) {
  background: rgba(220, 53, 69, .10);
}

.time-tracking-time-table .analysis-days tr.absence-timeCompensation td:not(.minus):not(.plus) {
  background: rgba(23, 162, 184, .12);
}

.time-tracking-time-table .analysis-days tr.absence-vacation td:first-child {
  box-shadow: inset 4px 0 0 #007bff;
}

.time-tracking-time-table .analysis-days tr.absence-sick td:first-child {
  box-shadow: inset 4px 0 0 #dc3545;
}

.time-tracking-time-table .analysis-days tr.absence-timeCompensation td:first-child {
  box-shadow: inset 4px 0 0 #17a2b8;
}

/* ------------------------------------------------- Time tracking: requests */

/* The eight request types - in the tool this was a drop-down above the form. */
.request-type-links {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 14px;
}

/* --- Request in the overlay -------------------------------------------- */

.request-overlay-content {
  max-width: min(760px, 96vw);
}

/* The fields of the chosen type stand below the selection, not beside it. */
.request-overlay-content .request-fields {
  margin-top: 14px;
}

.request-overlay-content .request-fields[hidden] {
  display: none;
}

/* The proofs on file in the request overlay - one button each. */
.request-files {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 6px;
}

/* --------------------------------------------------- Time tracking: calendar */

/* The totals row stands above the table and is exactly as wide - as in the tool. */
.time-tracking-time-table .freetime-detail {
  width: 100%;
}

.time-tracking-calendar .calendar-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: .5rem;
  margin-bottom: .5rem;
}

.time-tracking-calendar .calendar-title {
  font-weight: bold;
}

.time-tracking-calendar .calendar-legend {
  display: flex;
  flex-wrap: wrap;
  gap: .75rem;
  font-size: .85rem;
}

.time-tracking-calendar .calendar-legend-entry {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
}

/* One row per calendar week, beside it the six working days. */
.time-tracking-calendar .calendar-row {
  display: flex;
  align-items: stretch;
  margin-bottom: -1px;
}

.time-tracking-calendar .calendar-week-name {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 2.5rem;
  border: 1px solid #dee2e6;
  background: var(--ze-light-grey);
  font-size: .8rem;
  font-weight: bold;
}

.time-tracking-calendar .calendar-days {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  flex: 1;
  min-width: 0;
}

.time-tracking-calendar .calendar-week-day {
  padding: .3rem .5rem;
  border: 1px solid #dee2e6;
  margin-left: -1px;
  background: var(--ze-light-grey);
  font-size: .8rem;
  font-weight: bold;
}

.time-tracking-calendar .calendar-day {
  min-height: 4.5rem;
  padding: .3rem .4rem;
  border: 1px solid #dee2e6;
  margin-left: -1px;
}

.time-tracking-calendar .calendar-day.weekend,
.time-tracking-calendar .calendar-day.holiday {
  background: #fbfbfb;
}

.time-tracking-calendar .calendar-day.today {
  box-shadow: inset 0 0 0 2px var(--ze-orange);
}

.time-tracking-calendar .calendar-day-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: .3rem;
  margin-bottom: .25rem;
  font-size: .78rem;
}

.time-tracking-calendar .calendar-day-head .day {
  color: var(--ze-dark-grey);
}

.time-tracking-calendar .calendar-day.today .calendar-day-head .day {
  color: inherit;
  font-weight: bold;
}

.time-tracking-calendar .calendar-day-head .holiday-name {
  overflow: hidden;
  color: var(--ze-error);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.time-tracking-calendar .calendar-day-head .count {
  flex: none;
  padding: 0 .3rem;
  border-radius: .6rem;
  background: var(--ze-grey);
  color: #fff;
  font-size: .7rem;
}

.time-tracking-calendar .calendar-day-entries {
  display: flex;
  flex-direction: column;
  gap: .15rem;
}

/*
 * The colour says the type, the word stands in the title. Unlike on the overview, every entry
 * carries a colour of its own here - in the calendar the type is the actual statement.
 */
.time-tracking-calendar .calendar-entry {
  display: block;
  overflow: hidden;
  padding: .1rem .3rem;
  border-radius: .2rem;
  font-size: .75rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.time-tracking-calendar .calendar-legend-entry .calendar-entry {
  width: .9rem;
  height: .9rem;
  padding: 0;
}

.time-tracking-calendar .calendar-entry.vacation { background: rgba(0, 123, 255, .18); }

.time-tracking-calendar .calendar-entry.timeCompensation { background: rgba(23, 162, 184, .18); }

.time-tracking-calendar .calendar-entry.sick { background: rgba(220, 53, 69, .18); }

.time-tracking-calendar .calendar-entry.nursing { background: rgba(220, 53, 69, .1); }

.time-tracking-calendar .calendar-entry.homeOffice { background: rgba(40, 166, 69, .18); }

/* -------------------------------------------------------------- Notice board */

/* Notice board: the areas as a tab bar, the switches on the right. */
.board-tabs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-bottom: 16px;
}

.board-tab-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-left: auto;
}

/*
 * The headings stand side by side at a fixed width, their entries below one another - as in the
 * tool. Fixed width, so the columns do not end up differently wide depending on their content.
 *
 * A row that scrolls sideways instead of wrapping: the columns of an area belong together, and a
 * wrapped column slips underneath another one and is easily overlooked. The room at the bottom
 * belongs to the scrollbar.
 */
.board-headlines {
  display: flex;
  flex-wrap: nowrap;
  align-items: stretch;
  gap: 12px;
  /*
   * The height is set by board.php to whatever is left of the window - that way the board reaches
   * the bottom edge instead of making the whole page scroll. The fallback value takes hold as
   * long as the script has not run yet.
   */
  height: var(--board-height, 70vh);
  overflow-x: auto;
  overflow-y: hidden;
  padding-bottom: 10px;
}

.board-headline {
  display: flex;
  flex: 0 0 20rem;
  flex-direction: column;
  gap: .4rem;
  max-width: 20rem;
  /* Every column reaches the bottom edge; only the entry list inside it scrolls. */
  max-height: 100%;
  padding: .6rem;
  border: 1px solid var(--ze-grey);
  border-radius: .4rem;
  background-color: var(--ze-light-grey);
}

/* The heading stays put while the entries below it run. */
.board-headline .headline-title {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: .3rem;
  margin: 0;
  font-size: .95rem;
  font-weight: bold;
}

.board-headline .headline-title .count {
  padding: .05rem .45rem;
  border-radius: 1rem;
  background-color: var(--ze-grey);
  font-size: .8rem;
  font-weight: normal;
}

.board-headline .headline-title .headline-actions {
  display: flex;
  gap: .2rem;
  margin-left: auto;
}

.board-headline .headline-title .headline-actions form {
  margin: 0;
}

.board-empty {
  margin: 0;
  color: var(--ze-dark-grey);
  font-size: .85rem;
}

.board-entries {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: .5rem;
  /*
   * min-height: 0 is the decisive part: without it a flex child grows to its content and
   * overflows instead of scrolling.
   */
  min-height: 0;
  overflow-y: auto;
  /* Room for the scrollbar, so it does not lie on the cards. */
  padding-right: 4px;
}

/* Every entry is a card of its own; the colour on the left names its type. */
.board-entry {
  display: flex;
  flex-direction: column;
  gap: .3rem;
  padding: .45rem .55rem;
  border: 1px solid var(--ze-grey);
  border-left-width: .3rem;
  border-radius: .4rem;
  background-color: #fff;
}

.board-entry.board-entry-1 {
  border-left-color: var(--ze-blue);
}

.board-entry.board-entry-2 {
  border-left-color: var(--ze-orange);
}

.board-entry.board-entry-3 {
  border-left-color: var(--ze-success);
}

/* Whoever is signed up themselves should find the entry at a glance. */
.board-entry.registered {
  background-color: var(--ze-success-bg);
}

/*
 * An entry whose date has passed steps back. Deliberately NOT opacity: a faded card reads as
 * disabled, and these stay fully clickable and editable - Robin chose the visual treatment over a
 * read-only one. Placed after the type and registration rules so it wins the border and the
 * background at equal specificity.
 */
.board-entry.past {
  border-left-color: var(--ze-grey);
  background-color: var(--ze-light-grey);
  color: var(--ze-dark-grey);
}

/*
 * Past AND attended: muted with the rest, but still recognisably green - Robin asked for both, so
 * greying must not swallow the fact that the person was there. Three classes, so it beats .past.
 */
.board-entry.past.registered {
  background-color: rgba(40, 166, 69, .08);
}

.board-entry .entry-head {
  display: flex;
  justify-content: space-between;
  gap: .5rem;
  color: var(--ze-dark-grey);
  font-size: .8rem;
}

.board-entry .entry-date {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

.board-entry .entry-title {
  margin: 0;
  font-size: .95rem;
}

.board-entry .entry-description,
.board-entry .entry-info {
  margin: 0;
  font-size: .85rem;
  line-height: 1.35;
}

.board-entry .entry-info {
  display: flex;
  align-items: center;
  gap: .4rem;
  color: var(--ze-dark-grey);
}

.board-entry .entry-registrations {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .25rem;
  margin: 0;
}

/* One circle per sign-up, with the initials - the full name stands in the title. */
.board-entry .entry-registrations .avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.45rem;
  height: 1.45rem;
  border-radius: 50%;
  color: #fff;
  font-size: .65rem;
  font-weight: bold;
}

.board-entry .entry-registrations .avatar.own {
  box-shadow: 0 0 0 2px var(--text);
}

.board-entry .entry-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .3rem;
  margin-top: auto;
  padding-top: .3rem;
}

/*
 * THE BUTTONS ARE ALL 25px TALL AND TWO OF THEM STILL SIT LOW - `display: flex` is the proposed
 * cure and the cause behind it is a HYPOTHESIS, not a measurement. Read the whole of this.
 *
 * What IS measured: all three buttons compute to 25px. `box-sizing: border-box` is global, the
 * board rule sets min-height: 25px, the icon button is 17px icon + 6 padding + 2 border = exactly
 * 25, and the text button is 16.8 + 6 + 2 = 24.8 which min-height lifts to 25. So the heights are
 * equal and height was never the cause - Robin confirms equal height with a visible OFFSET.
 *
 * THE HYPOTHESIS: the three are unequally WRAPPED. Anmelden and the bin are <button>s inside a
 * <form>; the pencil is a bare <button> and a direct flex child. A <form> is display: block, so
 * the inline-flex button inside it sits on a LINE BOX - strut from the inherited line-height 1.4,
 * on the baseline, with descender space beneath. The form is therefore taller than its button, and
 * `align-items: center` on .entry-actions centres the FORM, not the button inside it. Making the
 * form a flex container removes the line box, so the form's height becomes the button's height.
 *
 * NOBODY HAS SEEN THIS WORK. There is no browser in this environment and no test renders it.
 * CONFIRMED IF the offset disappears; REFUTED IF it stays - and if it stays, the hypothesis is
 * wrong and wants replacing, not supplementing with a second guess.
 *
 * ---- CONFIRMED 30.09.2026. The paragraph above stands as it was written. ----
 *
 * Robin looked at it on local.delicando.com and said "now its fixed". The offset is gone, so the
 * unequal-wrapping cause is the right one and this line is a fix rather than a proposal.
 *
 * HOW IT WAS DECIDED BEFORE THE MERGE, because that is the part worth copying: the single property
 * was put into Robin's own checkout UNCOMMITTED and marked as a probe, he reloaded and judged, and
 * it was then taken back and verified byte-identical against a backup taken first. This branch was
 * never involved. That is why a change living only on an unmerged branch could be adjudicated at
 * all - local.delicando.com serves the main checkout, not this worktree.
 *
 * THE LIMIT HAS NOT MOVED: the instrument was Robin's eye, not a run. Nothing in this environment
 * renders, and nobody here has measured a rendered pixel - not before and not now.
 *
 * The height arithmetic above, the withdrawn `line-height` idea and the NOT-ESTABLISHED note about
 * the old docblock are all unchanged. Only the unseen part is overtaken.
 */
.board-entry .entry-actions form {
  display: flex;
  margin: 0;
}

.board-entry .entry-deadline {
  width: 100%;
  color: var(--ze-dark-grey);
  font-size: .76rem;
}

/* The images of a board entry - thumbnail, name, delete. */
.board-entry .entry-images {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.board-entry .entry-images img {
  max-width: 100%;
  max-height: 9rem;
  border-radius: .3rem;
}

.board-image-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 6px;
}

.board-image-list .board-image {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.board-image-list .board-image img {
  width: 4rem;
  height: 4rem;
  object-fit: cover;
  border-radius: .3rem;
}

.board-image-list .board-image .name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.board-image-list .board-image form {
  margin-left: auto;
}

/* Those signed up, in the editing overlay - a click on the circle signs them off. */
.registration-avatars {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 6px;
}

.registration-avatars .avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: none;
  border-radius: 50%;
  color: #fff;
  font: inherit;
  font-size: .75rem;
  font-weight: bold;
}

.registration-avatars .avatar.removable {
  cursor: pointer;
}

.registration-avatars .avatar.removable:hover {
  box-shadow: 0 0 0 2px var(--bad);
}

/* The form carries only the data - it is submitted from the overlay. */
.board-registration-remove {
  display: none;
}

/* --------------------------------------------------------------------- Tasks */

/*
 * One row per task. The tool presented each as a tall block with a permanently open text area -
 * with twenty tasks that becomes a page one only scrolls through.
 */
.todo-overlay-content .todo-row {
  padding: .45rem .6rem;
  border-bottom: 1px solid #eee;
}

.todo-overlay-content .todo-row:last-child {
  border-bottom: none;
}

.todo-overlay-content .todo-new {
  margin-bottom: .6rem;
  border: 1px solid var(--border);
  border-radius: .25rem;
  background: var(--ze-light-grey);
}

/* Title, due date and buttons side by side; the title takes what is left over. */
.todo-overlay-content .todo-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .35rem;
}

.todo-overlay-content .todo-title {
  flex: 1 1 12rem;
  min-width: 8rem;
}

.todo-overlay-content .todo-deadline {
  flex: 0 0 9.5rem;
  width: 9.5rem;
}

/* EX-1694: the "Grundaufgabe" box beside the due date. */
.todo-overlay-content .todo-base {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: .25rem;
  font-size: .85rem;
  white-space: nowrap;
}

/* The state as a dot - the same language as on the overview. */
.todo-overlay-content .todo-state {
  flex: none;
  width: .5rem;
  height: .5rem;
  border-radius: 50%;
  background: var(--ze-success);
}

.todo-overlay-content .todo-row.overdue .todo-state {
  background: var(--ze-error);
}

.todo-overlay-content .todo-row.finished .todo-state {
  background: var(--ze-grey);
}

/* What is done steps back but stays legible. */
.todo-overlay-content .todo-row.finished .todo-title {
  color: var(--ze-dark-grey);
  text-decoration: line-through;
}

/*
 * Only the icon in the button - the label stands in the title attribute.
 *
 * The three buttons sit in two different containers (save in the editing form, the other two
 * beside it). So the icons still sit on one line, all of them get the same height as an input,
 * and the icon is centred within it instead of hanging on the font baseline - `vertical-align`
 * otherwise depends on where the icon is drawn inside its image.
 */
.todo-overlay-content .todo-icon-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 14px font, 9px padding top and bottom, 1px border - like .field-input. */
  height: 40px;
  padding: 0 .6rem;
}

/*
 * Only the existing tasks: the editing form on the left, the two actions on the right. When
 * creating one there are no actions beside it - there, row and description would otherwise stay
 * side by side instead of below one another.
 */
.todo-overlay-content .todo-row:not(.todo-new) {
  display: flex;
  align-items: flex-start;
  gap: .35rem;
}

.todo-overlay-content .todo-row:not(.todo-new) > form:first-child {
  flex: 1;
  min-width: 0;
}

.todo-overlay-content .todo-row-actions {
  display: flex;
  flex: none;
  align-items: center;
  gap: .35rem;
}

/* The description lies collapsed below and opens only when there is something in it. */
.todo-overlay-content .todo-description {
  margin-top: .3rem;
}

.todo-overlay-content .todo-description summary {
  color: var(--ze-dark-grey);
  cursor: pointer;
  font-size: .78rem;
}

.todo-overlay-content .todo-description textarea {
  width: 100%;
  min-height: 3rem;
  margin-top: .25rem;
  font-size: .82rem;
  resize: vertical;
}

/* --- Bulk action in lists ----------------------------------------------- */

.todo-bulk {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  margin-bottom: .6rem;
  padding: .5rem .75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--ze-light-grey);
}

.todo-bulk-label {
  font-weight: bold;
}

.todo-bulk-count {
  color: var(--ze-dark-grey);
  font-size: .85rem;
}

/* --- Working note on the task ------------------------------------------- */

.todo-work-note {
  margin-top: .35rem;
}

.todo-work-note > summary {
  color: var(--ze-dark-grey);
  cursor: pointer;
  font-size: .78rem;
}

.todo-work-note textarea {
  width: 100%;
  min-height: 2.6rem;
  margin: .25rem 0;
  font-size: .82rem;
  resize: vertical;
}

/* Delete stands below the form, set apart - it is not a save. */
.todo-form-delete {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .6rem;
  padding: 14px 20px;
  border-top: 1px solid var(--border);
}

.todo-status-pill {
  margin-top: .3rem;
}

/* ------------------------------------------------------------- Module grants */

.module-block {
  margin-bottom: 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.module-block-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 16px;
  background: var(--surface-alt);
  border-bottom: 1px solid var(--border);
  font-weight: 600;
}

.module-block-body {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 8px 18px;
  padding: 14px 16px;
}

.module-block-body:empty {
  display: none;
}

/* ----------------------------------------------------------- Navigation bars */

/* Navigation: see navigation.css (taken over from the warehouse).
   Only additions that have no counterpart there stand here. */

/*
 * The warehouse reckons firmly with bars 50.4px tall - that is where its
 * "#sub-nav-bar { top: 50.4px }" and "padding-top: 7rem" on the content come
 * from. That height follows from its base font; here the base font is a
 * different one, so the height is set explicitly. With that the values taken
 * over are right again.
 */
#top-nav-bar,
#sub-nav-bar {
  height: 50.4px;
  padding-top: 0;
  padding-bottom: 0;
  /* In px instead of rem: on the reporting pages Bootstrap 3 sets html{font-size:10px}. */
  font-family: sans-serif;
  font-size: 16px;
  line-height: 1.15;
}

#top-nav-bar .navbar-brand,
#sub-nav-bar .navbar-brand {
  font-size: 20px;
}

/* Distinctly warmer than the Bootstrap dark grey (#343a40): a dark orange-brown,
   and to match it the highlight of the active page. */
#top-nav-bar.bg-dark {
  background-color: #4a3320 !important;
}

#top-nav-bar .nav-item .nav-link.active {
  background: #8a5a22;
}

/*
 * The order portal carries the colours of its own wordmark.
 *
 * Both front ends share this file and this bar - the customer and the member of staff see the
 * same house. The colour is the one difference, and it is the fastest one: whoever has both
 * windows open sees without reading which one they are in.
 *
 * Both values come from the gradient the word "grosshandel" is set in within the logo
 * (delicano-grosshandel.svg): #4B0D41 is its dark end, #880060 its light one. Not guessed but
 * read off - that way the bar stays in the tone of the brand, even when nobody remembers why
 * this particular violet.
 */
#top-nav-bar.nav-customer.bg-dark {
  background-color: #4B0D41 !important;
}

#top-nav-bar.nav-customer .nav-item .nav-link.active {
  background: #880060;
}

/*
 * The house colour of the order portal.
 *
 * Over in the administration it is green (--brand: #57a900). In the portal that was a foreign
 * body: the bar carries the violet from the wordmark "grosshandel", and beside it stood a green
 * button. Two brands on one page, and neither of them wins.
 *
 * So the same two values as in the bar stand here, as variables: everything that uses --brand -
 * buttons, progress bars, the outline around a focused field - follows along by itself. One rule
 * per kind of button would be the same change in thirty-one places.
 */
body.customer-portal {
  --brand: #880060;
  --brand-dark: #4B0D41;
  --brand-light: #f9edf5;
}

/*
 * The supplier portal, in a colour of its own.
 *
 * A third area on the same host name, and whoever has more than one open should see from the shade
 * which one they are standing in - the administration is green, the order portal violet. Here the
 * crimson out of the middle of the wordmark: the "delicando" lettering runs as a gradient from
 * violet to yellow-orange, and #DD0044 is the stop at offset 0.5 - see
 * delicano-grosshandel.svg.
 *
 * That the order portal takes its violet from the same gradient is not a clash but the point: both
 * are the house, and they are still told apart at a glance.
 *
 * The bar carries white writing (navbar-dark), so it takes a darker shade of the same colour. How
 * much darker is not a matter of taste: the entries one is not standing on are drawn in --nav-link,
 * half-transparent white, so what one reads there is fifty percent white blended with the bar
 * itself. The lighter the bar, the closer the two are - on a #A60033 bar the blend came out at
 * 2.7:1 and the entries went dim. #520018 puts them at 4.5:1, which is where the order portal
 * stands too (4.6:1), and lifts plain white to 15.3:1.
 *
 * The highlighted entry sits on that bar in the wordmark crimson and is told apart from it by
 * 3.0:1 - the order portal has 2.9:1. That is what says which page one is on, so it may not shrink.
 *
 * The same reckoning as with the order portal above: the two values stand as variables, so
 * everything that uses --brand follows along by itself - buttons, progress bars, the outline
 * around a focused field. The buttons keep the crimson out of the wordmark unchanged; only the
 * bar goes dark, exactly as the violet one does.
 */
#top-nav-bar.nav-supplier.bg-dark {
  background-color: #520018 !important;
}

#top-nav-bar.nav-supplier .nav-item .nav-link.active {
  background: #DD0044;
}

body.supplier-portal {
  --brand: #DD0044;
  --brand-dark: #520018;
  --brand-light: #fdecf1;
}

/*
 * The person beside the house.
 *
 * A supplier can have several contacts here, and both names stand in the bar: the house says whose
 * prices these are, the person says under whose login one is about. The second one steps back -
 * it answers a question one only asks now and then.
 */
.nav-person {
  opacity: .7;
}

/*
 * Somebody else's session - a member of staff looking into the portal as a customer.
 *
 * Only the notice, not the colour of the bar: that should stay what it is, so the portal looks
 * like the portal even with one of us inside it. The notice therefore carries the conspicuousness
 * alone - warm yellow on the violet, bold, with a symbol.
 */
.nav-acting-hint {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: #ffd48a !important;
  font-weight: 600;
}

/*
 * The bars have no vertical padding here (fixed height), or the highlight
 * would fill the whole bar. This way it sits freely within it - as in the
 * time tracking.
 */
#top-nav-bar .navbar-nav .nav-item {
  display: flex;
  align-items: center;
}

/*
 * The submodule list scrolls horizontally (rule from the warehouse). It gets
 * a height of its own here, so the thin scrollbar stays inside the bar and
 * does not stick out past its bottom edge. The spacing on the right makes
 * sure the last entry does not end up under the cogwheel.
 */
/*
 * Only the navigation rules were taken over from Bootstrap, not its reboot -
 * so the lists keep the browser's default margin (1em at the top) and the
 * entries sat 16px too low in both bars.
 */
#top-nav-bar .navbar-nav,
#sub-nav-bar .navbar-nav {
  margin-top: 0;
  margin-bottom: 0;
}

#sub-nav-bar .navbar-collapse {
  height: 100%;
  align-items: center;
}

/*
 * The list scrolls horizontally (rule from the warehouse). The thin scrollbar
 * takes height from the list while doing so - which made the entries sit
 * higher than the module name beside them. So it is hidden, as the upper bar
 * already does anyway; scrolling happens with the wheel or by swiping.
 */
#sub-nav-bar .navbar-nav.mr-auto {
  height: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
}

#sub-nav-bar .navbar-nav.mr-auto::-webkit-scrollbar {
  display: none;
}

/*
 * On the reporting pages Bootstrap 3 lies in the document as well; its
 * ".navbar-nav > li > a" is more specific than ".nav-link" of Bootstrap 4 and
 * inflates the entries to 15px of padding. So it is pinned down here - in px,
 * because Bootstrap 3 additionally sets html{font-size:10px}.
 */
#top-nav-bar .navbar-nav > .nav-item > .nav-link,
#sub-nav-bar .navbar-nav > .nav-item > .nav-link {
  padding-top: 8px;
  padding-bottom: 8px;
}

.navbar .badge {
  padding: 1px 7px;
  border-radius: 99px;
  background: var(--brand);
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .5px;
  text-transform: uppercase;
  vertical-align: middle;
}

/*
 * The count beside a submodule entry - zep-a5.
 *
 * UNSCOPED, AND THAT IS DELIBERATE: the badge has two homes, the bar of template/subNavigation.php
 * and the start-page buttons of moduleMain.php. `.navbar .badge` above cannot serve either half -
 * it is scoped to the bar, so it would do nothing on the buttons, and its uppercase letter-spacing
 * is cut for the word "Admin" rather than for a number.
 *
 * No `display: none` default here, unlike `.navigation-icon-count`: that one has to fight a rule
 * navigation.css brings along from the warehouse, and this class is new, so nothing renders it
 * unless subModuleCount.php writes it - which it does only above zero.
 */
.submodule-count {
  display: inline-block;
  min-width: 18px;
  margin-left: 4px;
  padding: 0 5px;
  border-radius: 99px;
  background: var(--brand);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  line-height: 18px;
  text-align: center;
  vertical-align: middle;
}

/* The full-screen button is a button - Bootstrap only styles links. */
#top-nav-bar button.nav-link,
#sub-nav-bar button.nav-link {
  background: none;
  border: none;
  cursor: pointer;
  font: inherit;
  color: rgba(255, 255, 255, .5);
}

#top-nav-bar button.nav-link:hover {
  color: rgba(255, 255, 255, .75);
}

/* Counter on the button - like .navigation-icon-count in the warehouse. */
#top-nav-bar .navigation-icon {
  position: relative;
  display: inline-flex;
  align-items: center;
}

/*
 * navigation.css brings
 * ".navbar .navigation-icon .navigation-icon-count { display: flex }" along
 * from the warehouse - and that beats a single-class rule. So the same depth
 * here, or the empty circle would stand there always.
 */
.navbar .navigation-icon .navigation-icon-count {
  position: absolute;
  top: 2px;
  right: -2px;
  display: none;
  min-width: 16px;
  width: auto;
  height: 16px;
  margin-left: 0;
  padding: 0 4px;
  border-radius: 99px;
  background: none;
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
}

/* Only with exports does the counter get colour and room. */
.navbar .navigation-icon .navigation-icon-count.active {
  display: flex;
  align-items: center;
  justify-content: center;
  background: #c9762b;
}

.navbar .navigation-icon .navigation-icon-count[hidden] {
  display: none;
}

/* --------------------------------------------------- Time tracking: terminal */

/* While the time tracking runs the symbol stands out - otherwise it stays quiet. */
#time-tracking-state.active {
  color: var(--brand);
}

/*
 * "We are here" on the icon - the same backing as on the entries of the bar.
 *
 * A class of its own and not `active`: on the icon that has always meant "a booking is running"
 * and colours it green. Both can hold at the same time, and then both should be visible.
 *
 * The rule of the entries does not take hold here - it demands `.nav-item .nav-link`, and the icon
 * stands outside the collapsible bar, without a list item around it.
 */
#top-nav-bar .nav-link.is-current {
  background: #8a5a22;
}

/*
 * Inside the module the icon is white - even while a booking is running.
 *
 * The green says "a booking is running" and is hard to read on the backed ground. That a booking
 * is running is said by the shape at this spot anyway: a triangle stands for running, two bars for
 * stopped. Two ids and one class - that beats the line with `.active` above without anything
 * having to change there.
 */
#top-nav-bar #time-tracking-state.is-current {
  color: #fff;
}

/* --- Station ------------------------------------------------------------ */

.time-tracking-terminal {
  max-width: 32rem;
  margin: 2rem auto;
}

.time-tracking-terminal .title {
  margin-bottom: 1rem;
  font-size: 1.6rem;
  font-weight: bold;
  text-align: center;
}

.time-tracking-terminal #time-tracking-terminal-form {
  display: grid;
  gap: .5rem;
  padding: 1rem;
  border: 1px solid var(--ze-grey);
  border-radius: .4rem;
  background: var(--ze-light-grey);
}

.time-tracking-terminal #time-tracking-terminal-form input {
  padding: .6rem;
  border: 1px solid var(--ze-grey);
  border-radius: .3rem;
  font-size: 1.4rem;
  text-align: center;
}

.time-tracking-terminal .terminal-result {
  margin-top: 1rem;
  padding: 1rem;
  border: 1px solid var(--ze-grey);
  border-left-width: .4rem;
  border-radius: .4rem;
  background: #fff;
}

.time-tracking-terminal .terminal-result .headline {
  font-size: 1.2rem;
  font-weight: bold;
}

.time-tracking-terminal .terminal-result .name {
  margin: .2rem 0 .6rem 0;
  font-size: 1.6rem;
  font-weight: bold;
}

.time-tracking-terminal .terminal-result .detail {
  display: flex;
  justify-content: space-between;
  gap: .5rem;
  padding: .15rem 0;
  border-bottom: 1px dotted var(--ze-light-grey);
}

.time-tracking-terminal .terminal-result .detail .label {
  color: var(--ze-dark-grey);
}

.time-tracking-terminal .terminal-result .detail .value {
  font-variant-numeric: tabular-nums;
}

.time-tracking-terminal .terminal-result.started {
  border-left-color: var(--ze-success);
}

.time-tracking-terminal .terminal-result.started .headline {
  color: var(--ze-success);
}

.time-tracking-terminal .terminal-result.stopped {
  border-left-color: var(--ze-blue);
}

.time-tracking-terminal .terminal-result.stopped .headline {
  color: var(--ze-blue);
}

.time-tracking-terminal .terminal-result.error {
  border-left-color: var(--ze-error);
}

.time-tracking-terminal .terminal-result.error .headline {
  color: var(--ze-error);
}

/*
 * The choice of task at the station (EX-1419).
 *
 * Nothing is booked while this stands there. The buttons are deliberately large and full width:
 * the station is a screen in the warehouse and is operated with gloves on, so there is nothing
 * small to hit and no list to open first.
 */
.time-tracking-terminal .terminal-result.choose {
  border-left-color: var(--ze-blue);
}

.time-tracking-terminal .terminal-result.choose .headline {
  color: var(--ze-blue);
}

.time-tracking-terminal .terminal-todo-choice {
  display: flex;
  flex-direction: column;
  gap: .6rem;
  margin-top: .4rem;
}

.time-tracking-terminal .terminal-todo-button {
  width: 100%;
  min-height: 4rem;
  padding: 1rem;
  font-size: 1.3rem;
  /* A long task name wraps rather than being cut off - the person has to read which is which. */
  white-space: normal;
  word-break: break-word;
}

.time-tracking-terminal .terminal-todo-cancel {
  width: 100%;
  min-height: 3rem;
  margin-top: .4rem;
}

/*
 * The station with the latest bookings beside it (EX-1370). The station keeps its width in the
 * middle column; the history is narrower and smaller, and moves below it on a narrow screen.
 */
.time-tracking-terminal-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 32rem) minmax(0, 1fr);
  gap: 2rem;
  align-items: start;
}

.time-tracking-terminal-layout .time-tracking-terminal {
  grid-column: 2;
  width: 100%;
}

.time-tracking-terminal-history {
  grid-column: 3;
  max-width: 18rem;
  margin-top: 2rem;
  font-size: .85rem;
}

.time-tracking-terminal-history .history-title {
  margin-bottom: .5rem;
  font-weight: bold;
  color: var(--ze-dark-grey);
}

.time-tracking-terminal-history .history-empty {
  margin: 0;
  color: var(--ze-dark-grey);
}

.time-tracking-terminal-history .history-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.time-tracking-terminal-history .history-entry {
  display: grid;
  grid-template-columns: auto auto 1fr;
  gap: .5rem;
  padding: .2rem 0 .2rem .4rem;
  border-left: .2rem solid var(--ze-grey);
  border-bottom: 1px dotted var(--ze-light-grey);
}

.time-tracking-terminal-history .history-entry.started {
  border-left-color: var(--ze-success);
}

.time-tracking-terminal-history .history-entry.stopped {
  border-left-color: var(--ze-blue);
}

.time-tracking-terminal-history .history-time {
  font-variant-numeric: tabular-nums;
  color: var(--ze-dark-grey);
}

.time-tracking-terminal-history .history-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@media (max-width: 64rem) {
  .time-tracking-terminal-layout {
    grid-template-columns: minmax(0, 1fr);
  }

  .time-tracking-terminal-layout .time-tracking-terminal,
  .time-tracking-terminal-history {
    grid-column: 1;
    max-width: 32rem;
    margin-left: auto;
    margin-right: auto;
  }

  .time-tracking-terminal-history {
    width: 100%;
    margin-top: 0;
  }
}

/* --------------------------------------- Time tracking: overview (table) */

/*
 * The table of the tool. There the borders came from Bootstrap; here they are written out, so no
 * whole library has to be loaded along.
 */
.time-tracking-status .content-table {
  width: 100%;
  margin: 0;
  border-collapse: collapse;
}

.time-tracking-status .content-table tr th,
.time-tracking-status .content-table tr td {
  padding: .2rem .5rem;
  border: 1px solid #dee2e6;
  font-size: .9rem;
}

.time-tracking-status .content-table tr th {
  background: #f2f2f2;
  font-size: 1rem;
  text-align: left;
}

.time-tracking-status .content-table .text-right {
  text-align: right;
}

/* The running booking stands out in the list. */
.time-tracking-status .content-table tr.running td {
  background-color: var(--ze-success-bg);
}

.time-tracking-status .content-table tr.running .state-text {
  color: var(--ze-success);
  font-weight: bold;
}

.time-tracking-status .content-table .field-input,
.time-tracking-status .content-table .field-select {
  padding: .1rem .3rem;
  font-size: .9rem;
}

/* --- Overview: who is in the house today ------------------------------- */

.time-tracking-overview .overview-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: .5rem;
  margin: 1rem 0 .5rem 0;
}

.time-tracking-overview .overview-summary .count {
  font-size: 1.6rem;
  font-weight: bold;
}

.time-tracking-overview .overview-summary .hint {
  margin-left: .5rem;
  color: var(--ze-dark-grey);
  font-size: .85rem;
}

.time-tracking-overview .overview-actions {
  display: flex;
  align-items: center;
  gap: .3rem;
}

/*
 * Opening is done with <details>. In the tool jQuery took care of that; this way it works without
 * a script and the browser remembers what is open.
 */
.time-tracking-overview .overview-department {
  margin-bottom: -1px;
  border: 1px solid #dee2e6;
}

/*
 * The department row and the staff rows share the same three columns - in the tool they stand in
 * a table, here every department opens for itself.
 */
.time-tracking-overview .overview-head-row,
.time-tracking-overview .department-row {
  display: grid;
  grid-template-columns: 41% 37% auto;
  align-items: center;
  gap: .5rem;
  padding: .4rem .5rem;
}

.time-tracking-overview .overview-head-row {
  border: 1px solid #dee2e6;
  background: var(--ze-light-grey);
  font-weight: bold;
}

.time-tracking-overview .department-row {
  background: var(--ze-light-grey);
  cursor: pointer;
}

.time-tracking-overview .department-row .department-name {
  font-weight: bold;
}

.time-tracking-overview .department-row .department-status {
  color: var(--ze-dark-grey);
  font-size: .85rem;
}

/* The figure carries the row, the rest is trimming. */
.time-tracking-overview .department-row .department-status strong {
  color: #333;
}

/*
 * One's own department stands out - that is where one looks first. A stripe at the side is enough
 * for it; a coloured-in row used to draw more attention than the list itself.
 */
.time-tracking-overview .own-department > .department-row {
  border-left: 3px solid var(--ze-orange);
}

.time-tracking-overview .own-row td {
  font-weight: bold;
}

/*
 * Whoever is currently running stands out in the name column already - there is no need to look
 * at the status column first. The stripe takes the same colour as the dot beside it, so the page
 * sticks to the one statement: green means reachable.
 */
.time-tracking-overview tr.is-present td:first-child {
  border-left: 3px solid var(--ze-success);
  background: rgba(40, 166, 69, .06);
  font-weight: 600;
}

/* The other way round, whoever is not there steps back - or the difference would be half visible. */
.time-tracking-overview .content-table tr:not(.is-present) td:first-child {
  color: var(--ze-dark-grey);
}

.time-tracking-overview .content-table {
  width: 100%;
  margin: 0;
  table-layout: fixed;
  border-collapse: collapse;
}

.time-tracking-overview .content-table td:first-child {
  width: 41%;
}

.time-tracking-overview .content-table td:nth-child(2) {
  width: 37%;
}

.time-tracking-overview .content-table tr th,
.time-tracking-overview .content-table tr td {
  padding: .2rem .5rem;
  border: 1px solid #dee2e6;
  font-size: .9rem;
  text-align: left;
}

.time-tracking-overview .content-table tr th {
  background: #f2f2f2;
  font-size: 1rem;
}

/*
 * The status: a dot and the word for it.
 *
 * The tool used filled Bootstrap badges here, one colour per kind of absence. With sixty members
 * of staff every row carried a coloured block, and the question this page is about - who is here
 * - drowned in it. Colour now carries only this one distinction, the reason is stated by the word.
 */
.time-tracking-overview .status {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  margin-right: .75rem;
  font-size: .85rem;
  white-space: nowrap;
}

.time-tracking-overview .status::before {
  content: "";
  width: .5rem;
  height: .5rem;
  flex: none;
  border-radius: 50%;
  background: var(--ze-grey);
}

/* In the house: a filled dot. */
.time-tracking-overview .status-present {
  font-weight: 600;
}

.time-tracking-overview .status-present::before {
  background: var(--ze-success);
}

/* Reachable but not in the house: the same dot, only hollow. */
.time-tracking-overview .status-remote::before {
  background: #fff;
  box-shadow: inset 0 0 0 2px var(--ze-success);
}

.time-tracking-overview .status-away {
  color: var(--ze-dark-grey);
}

.time-tracking-time-table .content-table {
  width: 100%;
  margin: 0 0 1rem 0;
  border-collapse: collapse;
}

.time-tracking-time-table .content-table tr th,
.time-tracking-time-table .content-table tr td {
  padding: .2rem .5rem;
  border: 1px solid #dee2e6;
  font-size: .85rem;
  text-align: left;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.time-tracking-time-table .content-table tr th {
  background: #f2f2f2;
  font-size: .9rem;
}

.time-tracking-status .content-table td.column-action {
  width: 1%;
  white-space: nowrap;
  text-align: right;
}

/* The three links in the head of the overview. */
.time-tracking-overview .overview-links {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
}

/* Without fields the rows are distinctly lower - the figures then stand in line. */
.worklog-list:not(.is-editing) .content-table td {
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------ Loading and progress */

.steps {
  margin: 0 0 18px;
  padding-left: 20px;
  color: var(--text);
  font-size: 13px;
  line-height: 1.7;
}

.steps code {
  padding: 1px 5px;
  border-radius: 4px;
  background: var(--surface-alt);
  font-size: 12px;
}

/* Progress bar like .progress-line in the warehouse tool. */
.progress-line {
  position: relative;
  min-width: 110px;
  height: 16px;
  overflow: hidden;
  border-radius: 99px;
  background: var(--surface-alt);
  border: 1px solid var(--border);
}

.progress-line .active-progress {
  height: 100%;
  background: #c9762b;
  transition: width .3s ease;
}

.progress-line .progress-title {
  position: absolute;
  inset: 0;
  font-size: 10px;
  font-weight: 700;
  line-height: 14px;
  text-align: center;
}

/*
 * The veil while something is running - shown by Admin.initBusy.
 *
 * Above everything: 10001 are the overlays, 10002 the messages and the progress bar. If a request
 * runs out of the downloads list, the list should not look operable while it runs - and a message
 * from earlier does not belong above the veil.
 */
#loading {
  position: fixed;
  inset: 0;
  z-index: 10005;
  display: none;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  gap: 14px;
  background: rgba(244, 246, 249, .78);
}

#loading[style*="flex"],
#loading[style*="block"] {
  display: flex !important;
}

#loading .loading-spinner {
  width: 42px;
  height: 42px;
  border: 3px solid rgba(87, 169, 0, .22);
  border-top-color: var(--brand);
  border-radius: 50%;
  animation: admin-spin .8s linear infinite;
}

/* Whoever turned motion off gets the ring standing still - it says as much either way. */
@media (prefers-reduced-motion: reduce) {
  #loading .loading-spinner {
    animation: none;
  }
}

#loading .loading-text {
  color: var(--text-muted);
  font-size: 13px;
  font-weight: 500;
}

/* ------------------------------------------------------------ Image uploads */

/* History of the image uploads: month, bar, figure - one grid row per line. */
.upload-history {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 6px 12px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.upload-history li {
  display: contents;
}

.upload-history-label {
  color: var(--text-muted);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}

.upload-history-track {
  height: 14px;
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: 7px;
  overflow: hidden;
}

.upload-history-bar {
  display: block;
  height: 100%;
  background: var(--brand);
  border-radius: 7px;
}

/* A month without an upload keeps its row but shows no bar. */
.upload-history-bar.is-empty {
  background: none;
}

.upload-history-count {
  min-width: 48px;
  text-align: right;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* ---------------------------------------------------- Reporting messages */

/* A message above a form - the same role as in the reporting. */
.report-alert {
  display: none;
  margin-bottom: 18px;
  padding: 12px 16px;
  border: 1px solid var(--bad);
  border-left-width: 4px;
  border-radius: var(--radius-sm);
  background: var(--bad-bg);
  color: #8c1c1c;
}

.report-alert.is-visible {
  display: block;
}

.report-alert-title {
  font-weight: 600;
}

.report-alert-detail {
  margin-top: 4px;
  font-size: 12px;
  word-break: break-word;
}

/*
 * Overlays of the time tracking. The tool brought five along; three were missing here - the
 * message dialogue, the error dialogue and the confirmation when copying. The loading veil is
 * called #loading here and is already present.
 */
.modal-overlay-container {
  max-width: 900px;
  margin: 40px auto;
  padding: 20px;
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}

#copy-blink-container {
  position: fixed;
  z-index: 1200;
  padding: 6px 12px;
  background: var(--brand);
  border-radius: var(--radius-sm);
  color: #fff;
  font-size: 12px;
  pointer-events: none;
  opacity: 0;
}

/* -------------------------------------------------------- Shipping tiles */

/*
 * One section per provider group: the channel tiles on the left, the list of the most recently
 * received orders on the right. The list stands in a column of its own, so it stays beside the
 * tiles and does not wrap behind them.
 */
.segment {
  display: grid;
  /*
   * The list gets a fixed, frugal width - enough for order number, customer and amount - and the
   * tiles take all of the rest. That way the room for the metrics grows with the window, and the
   * list does not become needlessly wide.
   */
  grid-template-columns: minmax(0, 1fr) 420px;
  gap: 16px;
  margin-bottom: 28px;
}

/* Sections without a list keep the full width. */
.segment.segment-plain {
  grid-template-columns: minmax(0, 1fr);
}

/* The spacing comes from the section, not from the grid inside it. */
.segment > .metric-grid {
  margin-bottom: 0;
}

/*
 * The height follows the tiles beside it - the same mechanism as in the overview: flex-basis 0
 * keeps the list out of the natural card height, the grid stretches the card to the height of the
 * tile column, and the list fills the rest. The lower bound takes hold when the list stands alone
 * in its row.
 */
.segment > .metric-card-list .metric-scroll-list {
  flex: 1 1 0;
  min-height: 320px;
  max-height: none;
}

/* The shipping state - the question one has first on this page. */
.shipping-state {
  margin-bottom: 4px;
  padding: 12px 14px;
  border-radius: var(--radius-sm);
}

.shipping-state.clear {
  background: var(--good-bg);
  color: #1e7a33;
}

.shipping-state.waiting {
  background: #fdf1e2;
  color: #8a5320;
}

.shipping-headline {
  display: flex;
  align-items: baseline;
  gap: 10px;
}

.shipping-count {
  font-size: 30px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.shipping-text {
  font-size: 14px;
  font-weight: 600;
}

.shipping-state .metric-rows {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid rgba(0, 0, 0, .1);
}

.shipping-state .metric-rows dt {
  color: inherit;
  opacity: .8;
}

.package-storno {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 190px;
}

.package-storno .field-textarea {
  min-height: 0;
}

/* ------------------------------------------------------------- Miscellaneous */

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

@container (min-width: 520px) {
  .metric-card-overall .metric-rows {
    grid-template-columns: max-content minmax(0, 1fr) max-content minmax(0, 1fr);
    column-gap: 10px;
  }

  .metric-card-overall .metric-rows dt::after {
    content: ':';
  }

  .metric-card-overall .metric-rows dd {
    /* The value follows on the label instead of wandering to the edge. */
    text-align: left;
    padding-right: 18px;
    font-weight: 600;
  }
}

/*
 * The image view stands centred and takes at most half the window - in both directions. The image
 * should be large enough to make something out, but not cover the page behind it.
 *
 * The padding of the base rule is taken back, or the spacing for the fixed head bar would push
 * the card out of the centre.
 */
#image-overlay {
  align-items: center;
  padding: 16px;
}

@keyframes admin-spin {
  to {
    transform: rotate(360deg);
  }
}

@keyframes timeTrackingPulse {
  0% { opacity: 1; }
  50% { opacity: .3; }
  100% { opacity: 1; }
}

/* --- Year overview ------------------------------------------------------ */

/*
 * The cells do not wrap (white-space: nowrap further down), so times and totals stay legible.
 * That gives the table a minimum width - on a narrow window it scrolls within itself here instead
 * of running out of the card.
 */
.time-tracking-time-table {
  margin-top: 1.5rem;
  overflow-x: auto;
}

/*
 * ON PAPER THE WHOLE TABLE, NOT ITS LEFT TWO THIRDS - EX-1537.
 *
 * The horizontal scrolling above is a screen remedy. Printed, a scroll box is cut at its edge: the
 * Monatsliste one sheet per employee (EX-1460) and the Jahresuebersicht both stopped after
 * "Fehlzeiten", and Differenz, Saldo, Home-Office and Info never reached the paper - measured with
 * Chrome print-to-PDF, on master as much as with EX-1524.
 *
 * So for print the box lets the table through, and the table is made to fit an A4 sheet in portrait:
 * tighter cells, a smaller type, and the Info column - the only long one - may wrap. The figures keep
 * nowrap; a time broken over two lines is worse than a narrow column.
 */
@media print {
  .time-tracking-time-table {
    overflow: visible;
  }

  .time-tracking-time-table .content-table tr th,
  .time-tracking-time-table .content-table tr td {
    padding: .05rem .2rem;
    font-size: 7pt;
    line-height: 1.2;
  }

  .time-tracking-time-table .analysis-days td:last-child {
    white-space: normal;
    font-size: 6.5pt;
  }

  /*
   * INFO KEEPS A FIXED SHARE AND WRAPS - EX-1539, Robin 08.10.2026: nothing cut off, the other columns
   * keep their width. Without a width the column took what was left, and one unbreakable token (a
   * link) made it as wide as the token: measured, the table 845px on a 720px sheet, its right edge
   * lost. `anywhere` lets such a token break so it cannot widen the column. The figures stay nowrap.
   */
  .time-tracking-time-table .analysis-days th:last-child,
  .time-tracking-time-table .analysis-days td:last-child {
    width: 24%;
    overflow-wrap: anywhere;
  }
}

@keyframes adminMessageIn {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: none; }
}

.time-tracking-calendar .hint,
.time-tracking-department .hint {
  margin-left: .5rem;
  color: var(--ze-dark-grey);
  font-size: .85rem;
  font-weight: normal;
}

/* Firmenkalender - EX-1695: what lies before today stays in the list, grey. */
.company-calendar tr.is-past td,
.company-calendar tr.is-past td a {
  color: var(--ze-dark-grey);
}

.time-tracking-department .value {
  font-variant-numeric: tabular-nums;
}

.time-tracking-department .value.negative { color: var(--ze-error); }

.time-tracking-department .value.positive { color: var(--ze-success); }

.time-tracking-department .week-day {
  padding: .2rem 0;
  border: 1px solid #eee;
  border-radius: .2rem;
  text-align: center;
  font-size: .72rem;
}

.time-tracking-department .week-day.free {
  color: var(--ze-grey);
}

.time-tracking-department .week-day .name {
  display: block;
  color: var(--ze-dark-grey);
}

.time-tracking-department .future-freetime {
  display: flex;
  justify-content: space-between;
  gap: .5rem;
  font-size: .78rem;
}

.time-tracking-department .future-freetime .info {
  color: var(--ze-dark-grey);
}

/*
 * The row above the list of short passwords: on the left when it was last asked for, on the right
 * the button for it. Both stand inside the reloaded area, so the time is right.
 */
.otp-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
  margin: 0 0 12px;
  color: var(--text-muted);
  font-size: 13px;
}

/*
 * The code is read off and typed onward - digit by digit. Fixed character width and a little air
 * between them, so 0 and 8 do not look alike.
 */
.otp-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 16px;
  letter-spacing: 1px;
  white-space: nowrap;
}

/* ------------------------------------------------------------ Narrow windows */

@media (max-width: 1200px) {
  .modules-container {
    grid-template-columns: 1fr;
    gap: .5rem;
  }
}

/*
 * Below this width too little would be left beside the 420px list for a tile (minimum width
 * 400 px plus spacing). From here on the list stands below the tiles.
 */
@media (max-width: 900px) {
  .segment {
    grid-template-columns: minmax(0, 1fr);
  }
}

@media (max-width: 1700px) {
  .metric-grid.overall {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

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

@media (max-width: 600px) {
  .metric-card-overall .metric-split {
    grid-template-columns: 1fr;
  }
}

@media (prefers-reduced-motion: reduce) {
  #loading .loading-spinner {
    animation-duration: 2.4s;
  }
}

@media (max-width: 860px) {
  .embed-body {
    height: auto;
    overflow: visible;
  }

  .embed-frame {
    height: 70vh;
  }

  .embed-bar {
    padding: 7px 16px;
  }
}

@media (max-width: 991px) {
  /* Opened out, the bars need more room than the fixed height. */
  #top-nav-bar,
  #sub-nav-bar {
    height: auto;
    min-height: 50.4px;
    padding-top: .5rem;
    padding-bottom: .5rem;
  }

  #top-nav-bar {
    position: static;
  }

  #sub-nav-bar {
    position: static;
    top: auto;
  }

  .page,
  body.no-sub-navigation .page {
    padding-top: 26px;
    padding-left: 16px;
    padding-right: 16px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .meldungskarte {
    transition: none;
  }
}

@media (max-width: 900px) {
  .metric-grid.upload-grid-two {
    grid-template-columns: minmax(0, 1fr);
  }
}

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

@media (max-width: 768px) {
  .time-tracking-status {
    max-width: 40rem;
  }
}

@media (max-width: 768px) {
  .time-tracking-analysis-summary {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 480px) {
  .time-tracking-analysis-summary {
    grid-template-columns: 1fr;
  }
}



/*
 * The sign-in of the order portal - the same form as /_admin/login, with two additions.
 *
 * A heading: the administration front end has exactly one sign-in page, here there are three
 * (sign in, forgotten password, set password), and without a title they would look the same.
 *
 * And the language switch, which does not exist over there. It stands at the very bottom, because
 * nobody looking to sign in is searching for it.
 */
.login-title {
  margin: 0 0 18px;
  font-size: 17px;
  font-weight: 600;
}

.login-languages {
  margin-top: 22px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
  text-align: center;
  font-size: 12px;
  color: var(--text-muted);
}

.login-languages a {
  color: var(--text-muted);
  text-decoration: none;
}

.login-languages a:hover {
  color: var(--brand-dark);
  text-decoration: underline;
}

.login-languages strong {
  color: var(--text);
}

.login-languages > * + * {
  margin-left: 10px;
}

/*
 * A page under only one bar.
 *
 * `page` keeps room at the top for both bars of the administration front end. The customer area
 * has only the upper one - four pages need no submodule bar - and without this correction its
 * content would begin with a hand's breadth of emptiness.
 */
.page.page-single-nav {
  padding-top: 74px;
}

/*
 * A response in the middle of the page.
 *
 * The administration front end has Admin.js draw its messages as a toast at the bottom right. The
 * customer area does not load that script - four pages need no toast mechanism - and a message
 * nobody sees is none. So it stands where the thing it reports happened.
 */
.page-notice,
.page-error {
  /*
   * A row with spacing, not text and button glued together. When a button stands beside it -
   * "Adresse anlegen", say - only the whitespace from the source separated it otherwise, and the
   * browser collapses that.
   */
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin-bottom: 18px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-left-width: 4px;
  border-radius: var(--radius-sm);
  font-size: 13px;
  line-height: 1.5;
}

.page-notice {
  background: var(--good-bg);
  border-color: var(--good);
  color: #1e7a33;
}

.page-error {
  background: var(--bad-bg);
  border-color: var(--bad);
  color: #8c1c1c;
}

/*
 * A progress bar in the veil.
 *
 * The house's veil shows a spinner and a sentence - right, as long as nobody knows how long it
 * will take. With the matching of an uploaded file one does know: the lines are counted. Then a
 * bar is the more honest answer, because it makes the difference between "working" and "stuck"
 * visible.
 */
#loading .loading-progress {
  width: min(280px, 60vw);
  height: 6px;
  border-radius: 99px;
  background: rgba(87, 169, 0, .18);
  overflow: hidden;
}

#loading .loading-progress-bar {
  height: 100%;
  border-radius: 99px;
  background: var(--brand);
  transition: width .25s ease-out;
}

/* When something stands still the spinner stops too - it would otherwise be a promise. */
#loading.is-failed .loading-spinner {
  animation: none;
  border-top-color: var(--bad);
}

#loading.is-failed .loading-progress-bar {
  background: var(--bad);
}

/*
 * The review view of an uploaded file.
 *
 * The price stays on one line. The column is narrow, "1.234,56 EUR" otherwise wraps after the
 * amount there, and a figure standing on two lines reads as two figures.
 */
.review-price {
  white-space: nowrap;
}

/* Less in stock than ordered - no refusal, but it should catch the eye. */
.review-stock-short {
  color: var(--warning);
  font-weight: 600;
}

/*
 * The thumbnail in the review view.
 *
 * The class had always stood in the markup but never in the CSS - the image lived on width and
 * height in the attribute alone. A portrait bottle shot was squeezed into a square by that.
 */
.review-image {
  width: 56px;
  height: 56px;
  object-fit: contain;
  border-radius: 4px;
  background: #fff;
}

/* The quantity field in the row - narrow, but wide enough for four-digit quantities. */
.review-quantity {
  width: 5.5em;
  min-width: 5.5em;
}

/*
 * The button below the table.
 *
 * Inside the card, with spacing above: the table ends with a line, and a button following
 * straight on it looks like one more row.
 */
.review-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
  margin-top: 16px;
}

.review-actions .field-hint {
  margin: 0;
}

/*
 * A row that flies out on saving.
 *
 * Struck through and pale, but still there - whoever ticks a box by mistake should be able to
 * find the row again and take the tick back. If it vanished at once, the only answer would be
 * that something is missing.
 */
tr.review-row-removed {
  opacity: .45;
}

tr.review-row-removed td:not(.column-select) {
  text-decoration: line-through;
}

/*
 * The total below the table.
 *
 * Until September 2026 net, tax and gross stood as three bare paragraphs straight under the table
 * - without spacing, left-aligned, and so the most important amount on the page read like a
 * footnote. Now a card of its own, the amounts right-aligned below one another, so the digits
 * line up.
 */
.review-summary dl {
  margin: 0 0 4px;
  max-width: 340px;
  margin-left: auto;
}

.review-summary dl > div {
  display: flex;
  justify-content: space-between;
  gap: 24px;
  padding: 6px 0;
}

.review-summary dt {
  margin: 0;
  color: var(--text-muted);
}

.review-summary dd {
  margin: 0;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  /* Amounts to the right - that way the decimal places line up under one another. */
  text-align: right;
}

.review-summary .review-summary-total {
  margin-top: 4px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
  font-size: 17px;
  font-weight: 600;
}

.review-summary .review-summary-total dt {
  color: var(--text);
}

.review-summary .card-hint {
  margin: 0;
  text-align: right;
}

/*
 * The instructions on the start page of the order portal.
 *
 * Four steps below one another, the number set off from the text. No grid and no icons - this is
 * read once and then never again.
 */
.guide-steps {
  margin: 0;
  padding-left: 20px;
  list-style: decimal;
}

.guide-steps > li {
  margin-bottom: 12px;
}

.guide-steps > li:last-child {
  margin-bottom: 0;
}

.guide-steps .field-hint {
  display: block;
}

/*
 * The sentence that says what to do on a page.
 *
 * Stands between the title row and the content and may touch neither - a hint stuck to the title
 * reads as its continuation.
 */
.page-lead {
  margin: 0 0 22px;
  max-width: 62ch;
  line-height: 1.55;
}

.page-lead .field-hint {
  display: block;
  margin-top: 6px;
}

/*
 * Head row of an image card in the photo studio: name, state, date.
 *
 * Name and marking stood side by side without spacing - flex with a gap instead of spaces, which
 * the browser collapses anyway. The date gets a row of its own, it does not belong in the same
 * line as the state.
 */
.release-image-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}

.release-image-head .field-hint {
  flex-basis: 100%;
  margin-top: 0;
}

/*
 * Heading and text within a card.
 *
 * h2 carries the browser's default - much spacing above, much below. In a card that has padding
 * of its own already, the heading therefore sits too low and the first sentence too far away. The
 * last paragraph gets no spacing below, or the card would have it twice.
 */
.card > h2:first-child,
.card > h3:first-child {
  margin-top: 0;
  margin-bottom: 10px;
}

.card > p:last-child,
.card > ul:last-child,
.card > ol:last-child {
  margin-bottom: 0;
}

/* Between a paragraph and the list below it, less is needed than between two paragraphs. */
.card > p + ul,
.card > p + ol {
  margin-top: -4px;
}

/*
 * The delete button of an image card in the photo studio.
 *
 * Set apart from the rest of the card, at the bottom - what throws an image away should not stand
 * beside what merely files it. The spacing above it is the safety distance.
 */
.release-image-actions {
  margin-top: auto;
  padding-top: 12px;
}

.release-image-actions form {
  margin: 0;
}

/*
 * The score scale of the rating in the order portal.
 *
 * Six buttons, one decision - the same reasoning as with the image release: one click instead of
 * open, choose, close. Radio fields lie beneath, so the page gets by without a script and the
 * keyboard follows along by itself.
 */
.rating-scale {
  margin: 0 0 18px;
  padding: 0;
  border: 0;
}

.rating-scale legend {
  padding: 0;
  margin-bottom: 8px;
  font-weight: 600;
}

.rating-scale-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.rating-point input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.rating-point span {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
  font-size: 17px;
  font-weight: 600;
  cursor: pointer;
}

.rating-point span:hover {
  border-color: var(--brand);
}

.rating-point input:checked + span {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
}

.rating-point input:focus-visible + span {
  outline: 2px solid var(--brand-dark);
  outline-offset: 2px;
}

/*
 * The spread of the ratings.
 *
 * One bar per step, scaled to the widest value and not to the total: with eleven ratings all six
 * bars would otherwise be dashes, and a spread in which nothing can be told apart is none.
 */
.rating-spread th {
  width: 90px;
  white-space: nowrap;
}

.rating-spread .field-hint {
  display: inline;
  margin-left: 4px;
}

.rating-spread-bar {
  width: 100%;
}

.rating-spread .column-number {
  width: 70px;
}

.rating-bar {
  display: block;
  min-width: 2px;
  height: 14px;
  border-radius: 3px;
  background: var(--brand);
}

/* Weak and good take the colours of the pills next to them - same statement, same colour. */
.rating-bar-weak {
  background: var(--bad, #b3261e);
}

.rating-bar-good {
  background: var(--good, #1e7a45);
}

/*
 * EX-1650: the overview of the duty schedule as a calendar (scheduleOverview.php) - two thirds of days
 * on the left, one third for the clicked area and day on the right, as warehouse's calendar.php had it.
 * A day is a column of blocks, one per area: allocated / target, people, exceptions. Short of the target
 * is marked on the left edge.
 */
.schedule-calendar {
  display: grid;
  grid-template-columns: 2fr 1fr;
  gap: 22px;
  align-items: start;
}

/* EX-1871: the card below the calendar ("Abwesend in dieser Woche") keeps the calendar's own gap. */
.schedule-calendar + .card {
  margin-top: 22px;
}

.schedule-calendar-grid {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}

.schedule-calendar-day {
  min-width: 0;
  border-left: 1px solid var(--border);
}

.schedule-calendar-day:first-child {
  border-left: none;
}

.schedule-calendar-head {
  padding: 10px 8px;
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  color: var(--text-muted);
  background: var(--surface-alt);
  border-bottom: 1px solid var(--border);
}

.schedule-calendar-area {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 2px 8px;
  padding: 8px;
  font-size: 12px;
  line-height: 1.4;
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid var(--border);
}

.schedule-calendar-area:hover {
  background: var(--surface-alt);
}

.schedule-calendar-area .schedule-calendar-name {
  grid-column: 1 / -1;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.schedule-calendar-area .schedule-calendar-people {
  text-align: right;
  color: var(--text-muted);
}

.schedule-calendar-area .pill {
  grid-column: 1 / -1;
  justify-self: start;
}

.schedule-calendar-area.short {
  box-shadow: inset 3px 0 0 var(--bad, #b3261e);
  background: var(--bad-row);
}

.schedule-calendar-area.short .schedule-calendar-sum {
  color: var(--bad, #b3261e);
  font-weight: 600;
}

.schedule-calendar-area.empty {
  color: var(--text-muted);
}

.schedule-calendar-area.selected {
  background: var(--highlight-bg);
  box-shadow: inset 3px 0 0 var(--highlight);
}

.schedule-calendar-detail > .section-head:first-child {
  margin-top: 0;
}

.schedule-calendar-figure {
  margin: 0 0 12px;
}

/*
 * EX-1658: the planning week (scheduleWeek.php) in the same calendar. Its blocks carry the planner's
 * figures - target, allocated, available, exceptions - one per line as in the EX-1627 grid, and the
 * right-hand third holds the day page's blocks; changing an entry and adding one happen in overlays
 * (Robin, 13.10.2026), so an entry row carries only its two buttons.
 */
.schedule-calendar-area .schedule-figure {
  grid-column: 1 / -1;
  display: flex;
  justify-content: space-between;
  gap: 8px;
}

.schedule-calendar-area.short .schedule-allocated {
  color: var(--bad, #b3261e);
  font-weight: 600;
}

/* EX-1694: the share of the target beside "Verteilt" - quieter than the hours it explains. */
.schedule-share {
  font-weight: 400;
  opacity: 0.85;
}

.schedule-planning .schedule-calendar-detail {
  min-width: 0;
}

.schedule-entry-actions {
  display: inline-flex;
  gap: 6px;
  align-items: center;
  justify-content: flex-end;
}

.schedule-day-actions {
  margin: 0 0 12px;
}

/* EX-1708: an entry's note under the name - quieter than the name, wrapping inside the narrow third. */
.schedule-entry-note {
  margin-top: 2px;
  font-size: 0.85em;
  font-weight: 400;
  color: var(--text-muted);
  overflow-wrap: anywhere;
}

.schedule-planning .schedule-calendar-detail > .page-actions {
  margin-top: 12px;
}

/*
 * Narrow windows - a member's phone (Robin, 13.10.2026): the days stand one under another. The detail
 * of a clicked block moves ABOVE the calendar: below six stacked days it would sit some 3000px down,
 * and the click reloads the page at its top. Empty before a click, it would still cost the grid's row
 * gap, so the gap is dropped here and the detail carries its own margin only when it has content.
 * Here and not in the gathered @media rules near the top: those come before the calendar's own rules
 * above, which would win the cascade.
 */
@media (max-width: 900px) {
  .schedule-calendar {
    grid-template-columns: minmax(0, 1fr);
    row-gap: 0;
  }

  .schedule-calendar-detail {
    order: -1;
  }

  .schedule-calendar-detail:has(*) {
    margin-bottom: 22px;
  }

  .schedule-calendar-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .schedule-calendar-day {
    border-left: none;
  }

  .schedule-calendar-day + .schedule-calendar-day .schedule-calendar-head {
    border-top: 1px solid var(--border-strong);
  }
}

/* EX-1694: the evaluations per person - one header row per person, their lines beneath. */
.data-table tr.row-group th {
  background: var(--ze-light-grey, #f3f4f6);
  text-align: left;
}

.data-table tr.row-group th.column-number {
  text-align: right;
}

/* --- EX-1797: competitor prices (item module) ------------------------------------------------- */

/* An amount and its difference stay on one line: "38,90 €" must not break before the euro sign. */
table.data-table th.amount,
table.data-table td.amount {
  white-space: nowrap;
  text-align: right;
}

/* The address of a competitor's page is long; the field takes the room the column has. */
table.competitor-table td .field-input {
  min-width: 22rem;
  width: 100%;
}

.competitor-add {
  margin-top: 18px;
}

/* EX-1813: the row the keyboard is on, on the competitor suggestions page (j/k move, a/r decide). */
table.suggestion-table tbody tr:focus {
  outline: 2px solid var(--highlight);
  outline-offset: -2px;
}

table.suggestion-table tbody tr:focus td {
  background: var(--highlight-bg);
}
