/* ══════════════════════════════════════════════════
   v2 VERWALTUNG

   Was nur die Verwaltung hat: die dichte Kopfzeile, das wiederkehrende Muster
   einer Uebersichts- und einer Bearbeitungsmaske, die Zeitleiste der
   Baugruppen und die Halte-Bestaetigung des Sicherungsbereichs.

   Alles andere kommt aus den gemeinsamen Dateien — Farben und Radien aus
   tokens.css, Masse aus tokens-dense.css, Raster aus grid.css, Tabellen und
   Baender aus data.css, Knoepfe und Felder aus buttons.css/forms.css.
   ══════════════════════════════════════════════════ */

/* ── Kopfzeile ──
   Dieselbe Kopfzeile wie im Kundenbereich, nur niedriger. Die Verwaltung hat
   mehr Menuepunkte und weniger senkrechten Platz; 56px statt 72px geben eine
   Zeile Tabelle mehr, ohne dass die Navigation gedraengt wirkt. */
.header--dense { padding-top: var(--ms-space-2); padding-bottom: var(--ms-space-2); }
.header--dense .header-logo img { height: 28px; }

/* ── Inhaltsbereich ──
   Kein graues Band um den Inhalt: Verwaltungsmasken bestehen aus Karten auf
   Grau, nicht aus abwechselnden Baendern. Das Grau kommt vom <body>. */
.admin-main {
  background: var(--ms-bg-secondary);
  min-height: calc(100vh - var(--ms-topbar-h));
  padding: var(--ms-space-6) 0 var(--ms-space-10);
}

/* Kundenansicht: echte Kundenseite in einem eindeutig beschrifteten Rahmen. */
.customer-preview-header { margin-bottom: var(--ms-space-3); }
.customer-preview-picker { padding: var(--ms-space-3) var(--ms-space-4); }
.customer-preview-picker-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr) minmax(0, 1.35fr) auto;
  align-items: end;
  gap: var(--ms-space-3);
}
.customer-preview-picker-grid .form-group { min-width: 0; }
.customer-preview-load { white-space: nowrap; }
.customer-preview-stage {
  border: 1px solid var(--ms-border-light);
  border-radius: var(--ms-radius-lg);
  overflow: hidden;
  background: var(--ms-bg-primary);
  box-shadow: var(--ms-shadow-sm);
}
.customer-preview-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ms-space-4);
  padding: var(--ms-space-3) var(--ms-space-4);
  background: var(--ms-panel-head);
  color: var(--ms-panel-head-on);
}
.customer-preview-bar span {
  display: block;
  color: var(--ms-panel-head-muted);
  font-size: var(--ms-text-small);
}
.customer-preview-bar .btn-outline {
  color: var(--ms-panel-head-on);
  border-color: var(--ms-panel-head-muted);
}
.customer-preview-bar .btn-outline:hover {
  color: var(--ms-panel-head);
  background: var(--ms-panel-head-on);
  border-color: var(--ms-panel-head-on);
}
.customer-preview-frame {
  display: block;
  width: 100%;
  min-height: 780px;
  border: 0;
  background: var(--ms-bg-primary);
}
.customer-preview-modal { padding: 3vh 2.5vw 2vh; }
.customer-preview-modal-dialog {
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  margin: 0;
}
.customer-preview-modal-header {
  padding: var(--ms-space-3) var(--ms-space-4);
  background: var(--ms-panel-head);
  color: var(--ms-panel-head-on);
  border-bottom-color: var(--ms-panel-head);
}
.customer-preview-modal-header .modal-subtitle {
  margin: var(--ms-space-1) 0 0;
  color: var(--ms-panel-head-muted);
  font-size: var(--ms-text-small);
}
.customer-preview-modal-header .modal-close { color: var(--ms-panel-head-on); }
.customer-preview-modal-header .modal-close:hover { color: var(--ms-panel-head-muted); }
.customer-preview-modal-body { padding: 0; overflow: hidden; }
.customer-preview-modal-frame {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
  background: var(--ms-bg-primary);
}
@media (max-width: 760px) {
  .customer-preview-picker-grid { grid-template-columns: minmax(0, 1fr); }
  .customer-preview-load { justify-self: start; }
  .customer-preview-bar { align-items: flex-start; flex-direction: column; }
  .customer-preview-frame { min-height: 640px; }
  .customer-preview-modal { padding: var(--ms-space-2); }
}

/* ── Kopf einer Maske ──
   Titel links, Handlungen rechts, auf schmalen Schirmen untereinander. Loest
   das wiederkehrende `<div class="row"><div class="col"><h1>…` samt
   `<div class="col text-end"><a class="btn">` der Alt-Vorlagen ab. */
.admin-header {
  /* Baut auf .split-row auf (base.css) und ergaenzt nur den Abstand nach
     unten. Beide Klassen zusammen im Markup: `class="split-row admin-header"`
     waere doppelt gemoppelt — deshalb wiederholt der Kopf die Zeilenangaben
     hier und tritt allein auf. */
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--ms-space-4);
  margin-bottom: var(--ms-space-5);
}
.admin-title { font-size: var(--ms-text-h3); font-weight: var(--ms-weight-semibold); letter-spacing: -0.01em; margin: 0; }
.admin-subline { font-size: var(--ms-text-small); color: var(--ms-text-secondary); margin: var(--ms-space-1) 0 0; }
/* Ein Verweis in der Beizeile — die Firma unter dem Namen eines Kontakts.
   Er braucht eine eigene Regel, weil das System KEINE globale `a`-Regel hat
   (base.css: jedes Bauteil holt seine Verweisfarbe vom eigenen Klassennamen).
   Ohne sie greift der Browserstandard, und die Firma stand blau UND
   unterstrichen unter der Ueberschrift.

   Ruhig wie in der Tabellenzelle (`.cell-line > a`, data.css): die Beizeile ist
   eine Angabe zum Datensatz, kein Angebot zum Wegklicken. Dass sie eines ist,
   sagt der Zeiger und die Farbe beim Ueberfahren. */
.admin-subline a {
  display: inline-flex; align-items: center; gap: var(--ms-space-1);
  color: inherit; text-decoration: none;
}
.admin-subline a:hover { color: var(--ms-accent); }
.admin-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ms-space-2); }

/* ── Werkzeugleiste einer Uebersicht ──
   Suche, Filter und Zaehler ueber der Tabelle. Wie .toolbar der
   Produktuebersicht, aber an der Unterkante ausgerichtet, weil hier Felder mit
   und ohne Beschriftung nebeneinander stehen. */
.admin-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--ms-space-3);
  margin-bottom: var(--ms-space-5);
}
.admin-count { font-size: var(--ms-text-small); color: var(--ms-text-secondary); margin-left: auto; }

/* ── Aktionsspalte einer Tabellenzeile ──
   Symbolknoepfe am rechten Rand. Dieselbe Gestalt wie .detail-file-actions der
   Bestelldetailseite; hier eigener Name, weil sie in JEDER Uebersicht steht. */
.admin-row-actions { display: flex; align-items: center; justify-content: flex-end; gap: var(--ms-space-1); }
/* Eine Handlung, die SCHREIBT, braucht ein <form> um ihren Symbolknopf
   („Erneut bestellen", 2026-09-17). `display: contents` nimmt dem Formular
   seinen Kasten, damit der Knopf in der Reihe steht wie die Verweise daneben —
   sonst braeche die Zeile an dieser Stelle. */
.admin-row-actions > form { display: contents; }

/* ── Zeitleiste der Baugruppen ──
   Ersetzt den einzigen <style>-Block des Backends
   (admin/product_assembly/timeline.html.twig). */
.timeline { position: relative; padding-left: var(--ms-space-6); }
/* Die durchgehende Linie liegt hinter den Punkten, nicht zwischen ihnen: so
   bleibt sie luecken los, auch wenn Eintraege verschieden hoch sind. */
.timeline::before {
  content: '';
  position: absolute;
  left: 7px; top: 4px; bottom: 4px;
  width: 2px;
  background: var(--ms-border-light);
}
.timeline-item { position: relative; padding-bottom: var(--ms-space-5); }
.timeline-item::before {
  content: '';
  position: absolute;
  left: calc(-1 * var(--ms-space-6) + 2px);
  top: 6px;
  width: 12px; height: 12px;
  border-radius: 50%;
  background: var(--ms-bg-primary);
  /* Gliederung, kein Bedienelement — deshalb die Trennlinie und nicht
     --ms-border-control (das ist ihr Alias fuer Felder, forms.css). */
  border: 2px solid var(--ms-border-divider);
}
.timeline-item--accent::before { border-color: var(--ms-accent); }
.timeline-date { font-size: var(--ms-text-caption); color: var(--ms-text-secondary); font-variant-numeric: tabular-nums; }
.timeline-title { font-size: var(--ms-text-small); font-weight: var(--ms-weight-medium); margin: var(--ms-space-1) 0 0; }

/* ── Halte-Bestaetigung ──
   Fuer Handlungen, die nichts rueckgaengig macht (Sicherung einspielen,
   Bestand loeschen). Der Knopf muss gedrueckt GEHALTEN werden; der Fortschritt
   fuellt ihn von links. Kein Bestaetigungsdialog: wer einen Dialog dreimal
   gesehen hat, klickt ihn weg, ohne ihn zu lesen — Halten laesst sich nicht
   nebenbei tun.

   Der Fuellstand kommt aus --ms-fill, wie bei allen anderen Anteilsbauteilen
   (siehe base.css, .fill-*). */
.hold-confirm { position: relative; overflow: hidden; }
.hold-confirm::before {
  content: '';
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--ms-fill, 0%);
  background: rgba(var(--ms-error-rgb), 0.25);
  transition: width 80ms linear;
  pointer-events: none;
}
.hold-confirm > * { position: relative; }
/* DER FORTSCHRITT WAR NIE ZU SEHEN. `--ms-fill` setzte niemand, und die vier
   Klassen des Countdown-Rings (`.hold-confirm-countdown`, `-ring-track`,
   `-ring`, `-label`) hatten in keiner Stilkette eine Regel — im
   Sicherungszentrum stand deshalb ein nackter Kreis-SVG neben einer Zahl. Ein
   Halteknopf ohne sichtbaren Fortschritt ist eine Zumutung: man weiss nicht,
   ob es zaehlt, und laesst zu fruech los. Seit dem 2026-09-07 treibt
   `hold-to-confirm_controller.js` beides — die Fuellung UND den Ring. */
.hold-confirm-countdown {
  display: inline-flex; align-items: center; justify-content: center;
  position: relative; flex: none;
  width: var(--ms-icon-size-s); height: var(--ms-icon-size-s);
  font-size: var(--ms-text-caption); font-variant-numeric: tabular-nums;
  line-height: 1;
}
/* Gedreht, damit der Ring oben beginnt und nicht rechts. */
.hold-confirm-countdown svg {
  position: absolute; inset: 0; width: 100%; height: 100%;
  transform: rotate(-90deg);
}
.hold-confirm-ring-track,
.hold-confirm-ring { fill: none; stroke-width: 3; }
.hold-confirm-ring-track { stroke: var(--ms-border-light); }
.hold-confirm-ring { stroke: currentColor; stroke-linecap: round; }
.hold-confirm-label { white-space: nowrap; }
/* Waehrend des Haltens: der Knopf tritt hervor, damit die Geste eine Antwort
   bekommt. Die Fuellung allein reicht dafuer nicht — sie beginnt bei 0%. */
.hold-confirm.is-holding { border-color: var(--ms-error); }

/* ── Einrichtung des zweiten Faktors ── */
.twofactor-qr {
  display: block;
  margin: var(--ms-space-5) auto;
  /* Rand statt Schatten: der Code muss sich sauber vom Weiss absetzen, damit
     die Kamera die Kanten findet. */
  border: 1px solid var(--ms-border-light);
  border-radius: var(--ms-radius-md);
  padding: var(--ms-space-3);
  background: var(--ms-bg-primary);
}
/* Das Feld fuer den sechsstelligen Code: so breit wie sein Inhalt. Vorher
   steckte es in einer `col-2` — auf schmalen Schirmen wenige Millimeter breit
   fuer sechs Ziffern. Ueber der Zeilenbreite sieht es umgekehrt aus, als
   erwarte es einen Satz.

   NUR MIT DEM ZUSATZ `--pin`, nicht an `.auth-code` allgemein (berichtigt am
   2026-09-22): dasselbe Bauteil steht auch im Anmeldeformular, und DORT nimmt
   es wahlweise einen Reserve-Code (`HK4M2-PQ7RT`). Elf Zeichen mit der
   Sperrung von 0.4em passen in zwoelf Zeichen Breite nicht — gemessen war
   schon die sechste Ziffer angeschnitten. */
.auth-code--pin { max-width: 12ch; }
/* Und die Fassung fuer ein Feld, das BEIDE Formen nimmt: sechs Ziffern ODER
   einen Reserve-Code (elf Zeichen).

   24ch UND NICHT 18ch, obwohl elf Zeichen dort hineinpassen muessten: `ch`
   misst die Zeichenbreite, NICHT die Sperrung. `.auth-code` setzt 0.4em, und
   die kommt je Zeichen dazu. Im Browser durchgemessen (18/20/22/24/26ch): erst
   bei 24ch stand `HK4M2-PQ7RT` vollstaendig da, bei 22ch fehlten die letzten
   zwei Zeichen. Ueber die volle Zeilenbreite sieht das Feld umgekehrt aus, als
   erwarte es einen Satz. */
.auth-code--any { max-width: 24ch; }
/* Die Einrichtungsseite setzt denselben Code MITTIG — dort steht er allein in
   einer schmalen Karte unter dem QR-Bild, und links buendig haenge er an der
   Kante statt unter dem Bild. In der Kontokarte gilt das nicht: sie ist eine
   Maske wie jede andere. */
.twofactor-check .auth-code { margin: 0 auto var(--ms-space-4); }
.twofactor-check .button-row { justify-content: center; }

/* ── Rollbereich fuer lange Protokolle ──
   Begrenzt die Hoehe, damit ein Artikel mit dreihundert Aenderungen nicht die
   ganze Maske in die Laenge zieht. Ersetzt ein
   `style="max-height: 300px; overflow-y: auto;"` in der Artikelmaske. */
.log-scroll { max-height: 320px; overflow-y: auto; }
/* Der Tabellenkopf bleibt beim Rollen stehen — ohne ihn weiss man nach zehn
   Zeilen nicht mehr, welche Spalte „vorher" und welche „nachher" ist. */
.log-scroll thead th {
  position: sticky; top: 0; z-index: 1;
  background: var(--ms-bg-primary);
}

/* Hilfesymbol neben einer Feldbeschriftung (templates/v2/_help.html.twig). */
/* Groesse aus der Skala (`ms-ico--sm` am Aufruf in v2/_help.html.twig) —
   hier stand vorher ein hartes `font-size: 0.9em`. */
.help-icon {
  margin-left: var(--ms-space-1);
  color: var(--ms-text-tertiary);
  cursor: help;
}
.help-icon:hover, .help-icon:focus-visible { color: var(--ms-accent); }

/* ── Paketliste ──
   Zeile je Paket: Bild, Firma/Name/Stueckliste, buchbare Menge, Handlungen.
   Keine Tabelle, weil die mittlere Spalte selbst eine Liste ist (die
   Stueckliste mit Bestand je Artikel) — verschachtelte Tabellen waeren hier
   schwerer zu lesen als ein Raster.

   Vorher ein Bootstrap-Raster mit einer Kopfzeile in `bg-gray text-light`:
   graue Schrift auf grauem Grund. */
.assembly-list { display: flex; flex-direction: column; }
.assembly-head,
.assembly-row {
  display: grid;
  grid-template-columns: 72px minmax(0, 5fr) minmax(0, 2fr) auto;
  gap: var(--ms-space-4);
  align-items: center;
  padding: var(--ms-space-3) 0;
  border-bottom: 1px solid var(--ms-border-light);
}
.assembly-head {
  font-size: var(--ms-text-caption);
  font-weight: var(--ms-weight-semibold);
  color: var(--ms-text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  border-bottom: 1.5px solid var(--ms-border-divider);
}
.assembly-row:last-child { border-bottom: 0; }
.assembly-row:hover { background: var(--ms-bg-secondary); }
.assembly-img {
  /* 54x54 statt 72x54 — quadratisch wie die Bildkette (2026-09-09). */
  width: 54px; height: 54px;
  object-fit: contain;
  border-radius: var(--ms-radius-sm);
  background: var(--ms-bg-secondary);
}
.assembly-company { font-size: var(--ms-text-caption); color: var(--ms-text-secondary); }
.assembly-name { font-weight: var(--ms-weight-semibold); margin: var(--ms-space-1) 0; }
.assembly-id { color: var(--ms-text-secondary); font-weight: var(--ms-weight-regular); }
/* Die Stueckliste: Artikel links, Bestand rechts, damit die Bestaende
   untereinander stehen und ein zu niedriger sofort auffaellt. */
.assembly-parts { list-style: none; margin: 0; padding: 0; font-size: var(--ms-text-caption); color: var(--ms-text-secondary); }
.assembly-parts li { display: flex; justify-content: space-between; gap: var(--ms-space-3); }
.assembly-parts li > span:last-child { font-variant-numeric: tabular-nums; white-space: nowrap; }
.assembly-parts .is-low { color: var(--ms-error-text); font-weight: var(--ms-weight-medium); }
.assembly-qty { text-align: right; font-size: var(--ms-text-lg); font-weight: var(--ms-weight-semibold); font-variant-numeric: tabular-nums; }
@media (max-width: 767px) {
  .assembly-head { display: none; }
  .assembly-row { grid-template-columns: 72px minmax(0, 1fr); row-gap: var(--ms-space-2); }
  .assembly-qty { text-align: left; }
}

/* ── Bestell-Zeitleiste eines Pakets ──
   Ersetzt den einzigen <style>-Block des Backends (125 Zeilen im Kopf von
   admin/product_assembly/timeline.html.twig) mit hartcodierten Farben
   (#dc3545, rgba(13,202,240,…), rgba(13,110,253,…)) aus Bootstraps Palette.

   Die Balken sitzen als RASTERSPALTEN im selben Raster wie die Tagesleiste
   darueber, nicht als Prozentwerte: so trifft ein Balken genau die Tage, die
   er meint. Das war vorher `style="left: 12.9032%; width: 9.6774%"`, also
   Inline-Style UND eine Rundung, die je nach Monatslaenge um Bruchteile eines
   Tages danebenlag. */
.timeline-scroll { overflow-x: auto; }
.assembly-timeline { min-width: 980px; }
.assembly-timeline__header,
.assembly-timeline__row {
  display: grid;
  grid-template-columns: 260px 1fr;
  gap: var(--ms-space-4);
}
.assembly-timeline__days,
.assembly-timeline__track {
  display: grid;
  grid-template-columns: repeat(var(--ms-days, 31), minmax(26px, 1fr));
}
.assembly-timeline__day {
  border-left: 1px solid var(--ms-border-light);
  font-size: var(--ms-text-caption);
  padding: var(--ms-space-1) 2px;
  text-align: center;
  color: var(--ms-text-secondary);
}
.assembly-timeline__day:last-child { border-right: 1px solid var(--ms-border-light); }
.assembly-timeline__weekend { background: var(--ms-bg-secondary); }
/* Heute: ein Rahmen INNEN, damit die Spaltenbreite gleich bleibt. */
.assembly-timeline__today { box-shadow: inset 0 0 0 2px rgba(var(--ms-accent-rgb), 0.4); }
.assembly-timeline__row {
  border-top: 1px solid var(--ms-border-light);
  min-height: 86px;
  padding: var(--ms-space-3) 0;
  align-items: center;
}
.assembly-timeline__label { font-size: var(--ms-text-small); line-height: 1.4; }
.assembly-timeline__track { position: relative; min-height: 52px; align-content: center; }
.assembly-timeline__track-day {
  border-left: 1px solid var(--ms-border-light);
  grid-row: 1;
  min-height: 52px;
}
.assembly-timeline__track-day:last-child { border-right: 1px solid var(--ms-border-light); }
/* Beide Balken liegen in derselben Rasterzeile wie die Tagesspalten und
   ueberdecken sie deshalb. Das Sperrfenster liegt darunter, die Bestellung
   darueber. */
.assembly-timeline__segment { grid-row: 1; border-radius: var(--ms-radius-sm); align-self: center; }
.assembly-timeline__segment--buffer {
  background: rgba(var(--ms-warning-rgb), 0.18);
  border: 1px solid rgba(var(--ms-warning-rgb), 0.5);
  height: 40px;
  z-index: 1;
}
.assembly-timeline__segment--order {
  display: flex; align-items: center; justify-content: center;
  background: var(--ms-accent);
  color: var(--ms-on-accent);
  font-size: var(--ms-text-caption);
  font-weight: var(--ms-weight-semibold);
  height: 20px;
  min-width: 10px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  z-index: 2;
}
.assembly-timeline__legend { display: inline-flex; gap: var(--ms-space-2); align-items: center; margin-right: var(--ms-space-4); font-size: var(--ms-text-small); }
.assembly-timeline__legend-marker { border-radius: var(--ms-radius-sm); display: inline-block; height: 14px; width: 32px; }
.assembly-timeline__legend-marker--order { background: var(--ms-accent); }
.assembly-timeline__legend-marker--buffer { background: rgba(var(--ms-warning-rgb), 0.18); border: 1px solid rgba(var(--ms-warning-rgb), 0.5); }

/* Monatslaenge und Balkenlage als Klassen — die Werte kommen aus Daten. */
.days-28 { --ms-days: 28; }
.days-29 { --ms-days: 29; }
.days-30 { --ms-days: 30; }
.days-31 { --ms-days: 31; }
.seg-start-1 { grid-column-start: 1; }
.seg-start-2 { grid-column-start: 2; }
.seg-start-3 { grid-column-start: 3; }
.seg-start-4 { grid-column-start: 4; }
.seg-start-5 { grid-column-start: 5; }
.seg-start-6 { grid-column-start: 6; }
.seg-start-7 { grid-column-start: 7; }
.seg-start-8 { grid-column-start: 8; }
.seg-start-9 { grid-column-start: 9; }
.seg-start-10 { grid-column-start: 10; }
.seg-start-11 { grid-column-start: 11; }
.seg-start-12 { grid-column-start: 12; }
.seg-start-13 { grid-column-start: 13; }
.seg-start-14 { grid-column-start: 14; }
.seg-start-15 { grid-column-start: 15; }
.seg-start-16 { grid-column-start: 16; }
.seg-start-17 { grid-column-start: 17; }
.seg-start-18 { grid-column-start: 18; }
.seg-start-19 { grid-column-start: 19; }
.seg-start-20 { grid-column-start: 20; }
.seg-start-21 { grid-column-start: 21; }
.seg-start-22 { grid-column-start: 22; }
.seg-start-23 { grid-column-start: 23; }
.seg-start-24 { grid-column-start: 24; }
.seg-start-25 { grid-column-start: 25; }
.seg-start-26 { grid-column-start: 26; }
.seg-start-27 { grid-column-start: 27; }
.seg-start-28 { grid-column-start: 28; }
.seg-start-29 { grid-column-start: 29; }
.seg-start-30 { grid-column-start: 30; }
.seg-start-31 { grid-column-start: 31; }
.seg-span-1 { grid-column-end: span 1; }
.seg-span-2 { grid-column-end: span 2; }
.seg-span-3 { grid-column-end: span 3; }
.seg-span-4 { grid-column-end: span 4; }
.seg-span-5 { grid-column-end: span 5; }
.seg-span-6 { grid-column-end: span 6; }
.seg-span-7 { grid-column-end: span 7; }
.seg-span-8 { grid-column-end: span 8; }
.seg-span-9 { grid-column-end: span 9; }
.seg-span-10 { grid-column-end: span 10; }
.seg-span-11 { grid-column-end: span 11; }
.seg-span-12 { grid-column-end: span 12; }
.seg-span-13 { grid-column-end: span 13; }
.seg-span-14 { grid-column-end: span 14; }
.seg-span-15 { grid-column-end: span 15; }
.seg-span-16 { grid-column-end: span 16; }
.seg-span-17 { grid-column-end: span 17; }
.seg-span-18 { grid-column-end: span 18; }
.seg-span-19 { grid-column-end: span 19; }
.seg-span-20 { grid-column-end: span 20; }
.seg-span-21 { grid-column-end: span 21; }
.seg-span-22 { grid-column-end: span 22; }
.seg-span-23 { grid-column-end: span 23; }
.seg-span-24 { grid-column-end: span 24; }
.seg-span-25 { grid-column-end: span 25; }
.seg-span-26 { grid-column-end: span 26; }
.seg-span-27 { grid-column-end: span 27; }
.seg-span-28 { grid-column-end: span 28; }
.seg-span-29 { grid-column-end: span 29; }
.seg-span-30 { grid-column-end: span 30; }
.seg-span-31 { grid-column-end: span 31; }

/* ── Ersteinrichtung ──
   Die drei Karten stehen nebeneinander und brauchen mehr Platz als eine
   Anmeldekarte. */
.auth-card--wide { max-width: 1080px; }
/* Fortschritt eines Uploads. <progress> sieht in jedem Browser anders aus;
   hier auf die Form des Systems gebracht. */
.upload-progress {
  appearance: none;
  /* Die Breite gehoert AN DAS BAUTEIL und nicht an seine Aufrufstellen: ein
     Fortschrittsbalken ist immer so breit wie das, dessen Fortschritt er
     zeigt. Vorher trug er in der Sicherungszentrale `w-100` — eine
     Hilfsklasse aus grid.css, die die Aufrufstelle mitdenken musste, und die
     in der Ersteinrichtung fehlte (dort war der Balken 150px, die
     Standardbreite des Browsers). */
  width: 100%;
  height: 6px;
  border: 0;
  border-radius: var(--ms-radius-pill);
  background: var(--ms-bg-secondary);
  margin: var(--ms-space-3) 0;
}
.upload-progress::-webkit-progress-bar { background: var(--ms-bg-secondary); border-radius: var(--ms-radius-pill); }
.upload-progress::-webkit-progress-value { background: var(--ms-accent); border-radius: var(--ms-radius-pill); }
.upload-progress::-moz-progress-bar { background: var(--ms-accent); border-radius: var(--ms-radius-pill); }

/* Zahlenfeld mit Einheit in den Einstellungen — es braucht nicht die volle
   Breite, eine Laufzeit in Werktagen ist zweistellig. */
.settings-narrow { max-width: 220px; }

/* ── Griff zum Umsortieren (Katalogmaske) ─────────────────────────────────
   Die Hand sagt, dass man hier anfassen kann; das Symbol allein sagt es
   nicht. Farbe eine Stufe leiser als der Text, weil der Griff Werkzeug ist,
   nicht Inhalt. */
.sort-handle {
  display: inline-flex;
  cursor: grab;
  color: var(--ms-text-tertiary);
  padding: var(--ms-space-1);
}
.sort-handle:active { cursor: grabbing; }

/* ── Die Kachel des Katalogs, waehrend man sie schreibt ──────────────────
   Links die drei Angaben, die sie ausmachen (Bild und Satz), rechts die Kachel
   selbst. Die Breite der Vorschau ist nicht geraten: in Schritt 1 stehen die
   Kacheln in `.categories-grid` — 900px Hoechstbreite, drei Spalten,
   --ms-space-5 (24px) Abstand, also (900 − 48) / 3 = 284px. Genau das ist
   17.75rem. Waere die Vorschau breiter, versprae­che sie Platz fuer einen
   laengeren Satz, den die echte Kachel nicht hat.

   Unter 900px — derselbe Bruch wie bei `.form-grid-3` — untereinander. */
.tile-edit {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 17.75rem;
  gap: var(--ms-space-5);
  align-items: start;
}
@media (max-width: 900px) { .tile-edit { grid-template-columns: minmax(0, 1fr); } }
/* Die Vorschau ist keine Bedienflaeche: kein Zeiger, keine Schwebewirkung,
   kein Text zu markieren. Sie zeigt nur. */
.tile-preview { display: grid; gap: var(--ms-space-2); }
.tile-preview .card { pointer-events: none; user-select: none; overflow: hidden; }

/* MAILTEXTE: Felder links, die Mail rechts — dasselbe Muster wie .tile-edit
   darueber, nur mit einer breiteren rechten Spalte. Eine Mail ist rund 600px
   breit; in 17.75rem (der Kachelbreite) waere sie in jeder Zeile umgebrochen
   und damit keine Vorschau mehr, sondern ein anderes Aussehen.

   Der Umbruchpunkt liegt hoeher als bei der Kachel (1200px statt 900px): hier
   stehen links die Felder und rechts 600px, dazu die Anlassliste der
   Master-Detail-Maske — unter 1200px bliebe fuer die Felder zu wenig. */
/* HIER STAND `.mail-edit` — ein Raster, das die Felder links und die Mail
   rechts nebeneinander stellte. Es ist am 2026-09-21 entfallen: der Betreiber
   hat es abbestellt („neben jedem Formularfeld wird ein entsprechendes PDF
   eingeblendet — das moechte ich ausdruecklich nicht"), und es ging ohnehin
   nicht auf. Die Arbeitsspalte dieser Maske hat bei einem 1920px-Fenster
   933px; nebeneinander blieben rund 430px je Spalte. Die alte Abfrage
   `@media (max-width: 1200px)` half nicht, weil sie das FENSTER misst und
   nicht die Spalte. Wer hier ein zweispaltiges Raster braucht: den BEHAELTER
   messen (`auto-fit` + `minmax`), nicht das Fenster. */

/* ── Die Mailvorschau ─────────────────────────────────────────────────────
   DIE VORSCHAU IST ALS VORSCHAU AUSGEZEICHNET (2026-09-22, Weisung des
   Betreibers). Drei Mittel wirken zusammen, und keines davon ist ein Rahmen,
   der wie ein Dokument aussieht — genau das hatte er am Tag davor abbestellt:

     1. eine ABGESETZTE Flaeche (`--ms-bg-secondary`) innerhalb der Karte. Sie
        liest sich als „Ausgabe des Obenstehenden"; ein weisses, gerahmtes
        Blatt laese sich als Beilage.
     2. eine KOPFZEILE mit Symbol und Wort, in der gedaempften Schrift des
        Systems.
     3. die BETREFFZEILE als Kopf einer Mail und nicht als weiteres Feld —
        abgesetzt durch eine Trennlinie, mit der Beschriftung davor.

   Alle Werte aus Tokens. */
/* ── Mailfenster: Bestandshinweis vorab (2026-10-07) ──
   Wie in einem Mailprogramm: Beschriftung links (An, Bcc, Betreff), Feld
   rechts; darunter der Text und die Signatur, die nicht mitbearbeitet wird.
   Ein Empfaengerfeld sieht aus wie ein Eingabefeld — dieselben Tokens fuer
   Rand, Radius, Grund und Fokusring —, traegt aber Chips und ein randloses
   Eingabefeld dahinter (`mail-compose_controller.js`). */
/* EINE BESCHRIFTUNGSSPALTE FUER ALLE ZEILEN (2026-10-08): An, Bcc und Betreff
   teilen sich die Spalten des Fensters (`subgrid`), damit die Felder gleich
   breit sind — vorher war jede Zeile ihr eigenes Raster, und „Betreff" schob
   sein Feld weiter nach rechts als „An". Alles, was keine Zeile ist (Text,
   Signatur, Fusszeile), geht ueber beide Spalten. */
.mail-compose {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  column-gap: var(--ms-space-3);
  row-gap: var(--ms-space-3);
}
.mail-compose > * { grid-column: 1 / -1; }
.mail-compose__row {
  display: grid;
  grid-template-columns: subgrid;
  align-items: center;
}
.mail-compose__label {
  margin: 0;
  font-size: var(--ms-text-small);
  color: var(--ms-text-secondary);
}
.mail-compose__field {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ms-space-1);
  min-width: 0;
  padding: var(--ms-space-1) var(--ms-space-2);
  border: var(--ms-control-border-w) solid var(--ms-border-control);
  border-radius: var(--ms-control-radius);
  background: var(--ms-control-bg);
  transition: border-color var(--ms-transition-base), box-shadow var(--ms-transition-base);
}
.mail-compose__field:focus-within {
  border-color: var(--ms-accent);
  box-shadow: var(--ms-control-focus-ring);
}
.mail-compose__input {
  /* Keine eigene Breite: das Feld nimmt den Rest der Zeile und rutscht
     unter die Chips, wenn seine Grundbreite nicht mehr daneben passt. */
  flex: 1 1 auto;
  min-width: 0;
  padding: var(--ms-space-2);
  border: 0;
  outline: none;
  background: transparent;
  color: var(--ms-text-primary);
  font-size: var(--ms-control-font);
}
.mail-compose__text { resize: vertical; }
.mail-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--ms-space-1);
  max-width: 100%;
  padding: var(--ms-space-05) var(--ms-space-1) var(--ms-space-05) var(--ms-space-2);
  border-radius: var(--ms-radius-pill);
  background: var(--ms-bg-secondary);
  font-size: var(--ms-text-small);
}
.mail-chip__name { font-weight: var(--ms-weight-medium); white-space: nowrap; }
.mail-chip__addr {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ms-text-secondary);
}
.mail-chip__remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--ms-space-05);
  border: 0;
  border-radius: var(--ms-radius-pill);
  background: transparent;
  color: var(--ms-text-secondary);
  cursor: pointer;
}
.mail-chip__remove:hover,
.mail-chip__remove:focus-visible { color: var(--ms-text-primary); }
.mail-compose__signature {
  padding: var(--ms-space-4);
  border-radius: var(--ms-radius-md);
  background: var(--ms-bg-secondary);
  color: var(--ms-text-secondary);
  /* DIE KLEINE STUFE, WIE IN DER MAIL (Betreiber, 2026-10-08): die Mail setzt
     ihre Signatur in 12px — hier dieselbe Stufe der Skala. */
  font-size: var(--ms-text-caption);
}
/* Absaetze der Signatur: Abstand nur zwischen ihnen, aus der Skala. */
/* `font-size` UND `color` ERBEN, nicht die Grundregel: `p` in base.css setzt
   `--ms-text-body` (die fluide Stufe der Verkaufsseite, ~18px) und die volle
   Textfarbe — damit stand die Signatur groesser als der Text darueber
   (gemessen 2026-10-08). */
.mail-compose__signature p { margin: 0 0 var(--ms-space-2); font-size: inherit; color: inherit; }
.mail-compose__signature p:last-child { margin-bottom: 0; }
.mail-compose__signature a { color: var(--ms-link); }
.mail-compose__rule {
  margin: var(--ms-space-3) 0;
  border: 0;
  border-top: 1px solid var(--ms-border-divider);
}

.mail-preview {
  display: grid;
  gap: var(--ms-space-2);
  margin-top: var(--ms-space-4);
  padding-top: var(--ms-space-4);
  border-top: 1px solid var(--ms-border-divider);
}
.mail-preview-head {
  display: flex;
  align-items: center;
  gap: var(--ms-space-2);
  margin: 0;
  font-size: var(--ms-text-caption);
  font-weight: var(--ms-weight-semibold);
  text-transform: uppercase;
  color: var(--ms-text-secondary);
}

/* Die Flaeche, auf der die Mail liegt. */
.mail-preview-sheet {
  background: var(--ms-bg-secondary);
  border-radius: var(--ms-radius-md);
  padding: var(--ms-space-4);
}
.mail-preview-subject {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--ms-space-2);
  margin: 0 0 var(--ms-space-3);
  padding-bottom: var(--ms-space-3);
  border-bottom: 1px solid var(--ms-border-divider);
}
.mail-preview-label {
  font-size: var(--ms-text-caption);
  color: var(--ms-text-secondary);
}

/* DIE MAIL BRINGT IHRE GESTALTUNG SELBST MIT — Arial, Groessen, Trennlinie,
   alles als `style` am Element (App\Mail\MailHtml). Das muss so sein, damit
   Gmail und Outlook es nicht entfernen, und es hat hier einen zweiten Nutzen:
   die Vorschau zeigt genau das, was der Empfaenger sieht, ohne dass ein
   einziger Wert doppelt gepflegt werden muesste.

   HIER STEHT DESHALB NUR DER RAHMEN — Hoehe, Rollbalken, Umbruch. Keine
   Schrift und keine Farbe: was der Empfaenger sieht, entscheidet die Mail. Ein
   `--ms-text-body` an dieser Stelle hiesse, dass eine Aenderung an der
   Verwaltungsschrift die Vorschau verstellt.

   `word-break` fuer die Adressen: ein Verweis auf eine Bestellung ist laenger
   als die Spalte. */
.mail-preview-body {
  max-height: 60vh;
  overflow: auto;
  word-break: break-word;
}
.mail-preview-body :last-child { margin-bottom: 0; }

/* ── Die Buehne der Katalogmaske ──────────────────────────────────────────
   Bereiche als Bloecke untereinander, darin die Karten in einer Reihe — der
   Katalog in der Anordnung des Kunden. Alle Werte aus Tokens; die Karte ist
   bewusst kleiner als die Kundenkarte (Schritt 3), weil hier zwanzig davon
   nebeneinander stehen und man sie ANORDNET, nicht liest. */
.stage { display: grid; gap: var(--ms-space-4); }
.stage[aria-busy="true"] { opacity: 0.6; pointer-events: none; }

/* GESPERRT: die Vorschau bleibt, die Bedienung tritt zurueck.

   `inert` (gesetzt vom Katalog-Controller, sobald die Stammdatenkarte gesperrt
   ist) nimmt Klick und Tabulator — aber es SIEHT man nicht, und ein Knopf, der
   unveraendert dasteht und nichts tut, ist schlimmer als einer, der fehlt.
   Gedaempft werden deshalb genau die Bedienelemente; die Karten selbst bleiben
   in voller Deckkraft, denn sie sind der Grund, warum man die Maske im
   gesperrten Zustand ueberhaupt aufschlaegt: um zu sehen, was der Kunde sieht.
   Derselbe Wert wie beim gesperrten Aktions-Symbol (`.icon-action:disabled`). */
.stage[inert] .stage-area-controls,
.stage[inert] .stage-cell-tools,
.stage[inert] .stage-drop,
.stage[inert] .stage-add-area,
.stage[inert] .split-row .button-row,
.stage-delete[inert] { opacity: 0.35; }

.stage-check {
  border-radius: var(--ms-radius-md);
  padding: var(--ms-space-3) var(--ms-space-4);
  background: var(--ms-warning-muted);
  color: var(--ms-warning-text);
  font-size: var(--ms-text-small);
}
.stage-check.is-blocked { background: var(--ms-error-muted); color: var(--ms-error-text); }
.stage-check.is-ok {
  display: flex; align-items: center; gap: var(--ms-space-2);
  background: var(--ms-success-muted); color: var(--ms-success-text);
}
.stage-check-title { margin: 0 0 var(--ms-space-1); font-weight: var(--ms-weight-semibold); }
.stage-check-list { margin: 0; padding: 0; list-style: none; display: grid; gap: var(--ms-space-1); }
.stage-check-list li { display: flex; align-items: center; gap: var(--ms-space-2); }
.stage-check-list a { color: inherit; }

/* ── Auswahldialog der Buehne ──
   Ein Eintrag, der in diesem Katalog schon in einer Artikelgruppe liegt,
   bleibt SICHTBAR und ist nicht waehlbar (`disabled` am Haken). Gedaempft wie
   eine stillgelegte Zeile in den Listen — und mit dem Titel seiner
   Artikelgruppe darunter, damit man ihn dort findet statt ihn zu suchen. */
.pick-row.is-used .cell-id,
.pick-row.is-used .cell-line { color: var(--ms-text-secondary); }

.stage-areas { display: grid; gap: var(--ms-space-6); }

/* Ein Bereich im Studio ist eine eigene `.card` (Bauteil des Systems) — beim
   Kunden sind die Bereiche ein Block ohne Trennung, hier bekommt jeder seinen
   Rahmen, weil er einzeln verwaltet wird. `.product-level` bringt einen
   Aussenabstand mit, den der Abstand der Liste bereits leistet. */
.stage-area { margin-bottom: 0; }
.stage-area:target { outline: 2px solid var(--ms-accent); outline-offset: 2px; }
.stage-area-head {
  display: flex; flex-wrap: wrap; align-items: flex-start;
  gap: var(--ms-space-3); margin-bottom: var(--ms-space-4);
}
/* Ueberschrift und Hinweis sind Kundentext und stehen deshalb genau so da wie
   drueben; die Bedienelemente ruecken daneben. Kein Mindestmass und keine
   Flex-Basis in rem: `auto` laesst den Titel so breit sein, wie er ist, und
   die Bedienelemente wandern in die naechste Zeile, wenn es eng wird. */
.stage-area-heading { flex: 1 1 auto; }
.stage-area-heading .product-level-title,
.stage-area-heading .product-level-hint { margin-bottom: 0; }
.stage-area-controls { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ms-space-3); margin-left: auto; }

/* Die Zelle: oben die Karte, wie der Kunde sie sieht, darunter das Werkzeug.

   `subgrid` ist hier der Kern: jede Zelle belegt ZWEI Reihen des Rasters, und
   beide Reihen sind fuer alle Zellen einer Bahn gleich hoch — die obere so
   hoch wie die hoechste Karte, die untere so hoch wie die hoechste
   Werkzeugzeile. Nur so ist die Ablegeflaeche (Zelle ohne Werkzeugzeile)
   exakt so hoch wie eine Karte, ohne dass irgendwo eine Hoehe geraten wird. */
.stage-cell {
  grid-row: span 2;
  display: grid; grid-template-rows: subgrid;
  gap: var(--ms-space-2);
}
/* Steuerelemente MITTIG unter der Karte, in zwei festen Reihen: Kennung bzw.
   Regel oben, Handlungen darunter (Betreiber, 2026-09-06). */
.stage-cell-tools {
  display: grid; justify-items: center; align-content: start;
  gap: var(--ms-space-1);
  font-size: var(--ms-text-caption); color: var(--ms-text-secondary);
  text-align: center;
}
.stage-cell-rule {
  display: inline-flex; align-items: center; gap: var(--ms-space-1);
  max-width: 100%;
}

/* Auf der Buehne stehen die Aktions-Symbole frei auf einer Karte und sind das
   einzige Bedienelement daneben — sie tragen deshalb den vollen Textton statt
   des gedaempften. GROESSE UND RAHMEN STEHEN HIER NICHT MEHR (2026-09-10):
   `.icon-action` hat eine Groesse aus dem Token und gar keinen Rahmen. Der
   fruehere Kontrastbefund vom 2026-09-06 betraf genau diesen Rahmen
   (`--ms-border-light`, 1,24:1 gegen die 3:1 aus WCAG 1.4.11) — ohne Rahmen
   ist er gegenstandslos; das Symbol selbst lag mit 5,07:1 hell und 6,61:1
   dunkel immer darueber. */
/* Die Auswahlart steht neben den Symbolknoepfen und darf nicht kleiner wirken
   als sie: dieselbe Hoehe, dieselbe Kante mit 3:1, der gewaehlte Zustand im
   Akzent. Das Bauteil bleibt `.record-status-*` des Systems. */
.stage-area-controls .record-status-seg { border-color: var(--ms-text-tertiary); }
.stage-area-controls .record-status-opt {
  min-height: var(--ms-icon-btn-h);
  padding-inline: var(--ms-space-4);
  font-size: var(--ms-text-small);
  color: var(--ms-text-primary);
}
.stage-area-controls .record-status-opt + .record-status-opt { border-left-color: var(--ms-text-tertiary); }
.stage-area-controls .record-status-label { font-size: var(--ms-text-small); }

.stage-cell-tools .icon-action,
.stage-area-controls .icon-action { color: var(--ms-text-primary); }

/* Die Karte im Studio ist Anzeige, kein Bedienelement: kein Anheben beim
   Ueberfahren, kein Zeiger, keine Maus- und Tastaturziele (`inert` am Rumpf). */
.product-card.is-preview { cursor: default; }
.product-card.is-preview:hover { transform: none; box-shadow: none; }
.product-card.is-preview > .card { pointer-events: none; }

/* Das leere Feld am Ende eines Bereichs: es oeffnet den Auswahldialog mit
   diesem Bereich als Ziel. Gleiche Hoehe wie eine Karte, damit die Reihe
   nicht ausfranst. */
.stage-drop {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--ms-space-2); width: 100%;
  border: 1px dashed var(--ms-border-control); border-radius: var(--ms-radius-lg);
  background: none; color: var(--ms-text-tertiary);
  font: inherit; font-size: var(--ms-text-small); text-align: center;
  /* KEINE gesetzte Hoehe: als Rasterfeld dehnt sich die Flaeche ohnehin auf
     die Hoehe ihrer Reihe. Nur wenn die Gruppe leer ist, bestimmt die
     Polsterung die Groesse — und die kommt aus den Tokens. */
  padding: var(--ms-space-8); cursor: pointer;
}
.stage-drop:hover { border-color: var(--ms-accent); color: var(--ms-accent); }
.stage-drop:focus-visible { outline: 2px solid var(--ms-accent); outline-offset: 2px; }

/* WOHIN DIE KARTE FAELLT, ZEIGT EINE LINIE — nicht eine blasse Geisterkarte.
   Sortable setzt `.sortable-ghost` an die Stelle, an der eingefuegt wird; die
   Zelle selbst bleibt unsichtbar, und in der Luecke davor steht ein Strich im
   Akzent. Dieselbe Sprache wie in myOFFICE. */
.stage-grid .sortable-ghost { position: relative; opacity: 1; }
.stage-grid .sortable-ghost > * { visibility: hidden; }
.stage-grid .sortable-ghost::before {
  content: "";
  position: absolute; top: 0; bottom: 0;
  left: calc(var(--ms-space-3) * -1);
  width: var(--ms-space-1);
  border-radius: var(--ms-radius-pill);
  background: var(--ms-accent);
}

.stage-add-area {
  display: flex; align-items: center; justify-content: center; gap: var(--ms-space-2);
  width: 100%; margin-top: var(--ms-space-6); padding: var(--ms-space-4);
  border: 1px dashed var(--ms-border-control); border-radius: var(--ms-radius-lg);
  background: none; color: var(--ms-text-secondary); font: inherit; cursor: pointer;
}
.stage-add-area:hover { border-color: var(--ms-accent); color: var(--ms-accent); }

/* ── Das Studio ─────────────────────────────────────────────────────────────
   Der graue Grund der Kundenseite. Auf der Kundenseite ist das ein Band ueber
   die ganze Fensterbreite (`.section-gray`); in der Verwaltung sitzt die
   Flaeche in einer Spalte, deshalb hier als abgesetztes Feld mit Radius. */
.catalog-studio {
  background: var(--ms-bg-secondary);
  border-radius: var(--ms-radius-xl);
  /* SEITLICH KEINE POLSTERUNG: sonst waeren die Artikelgruppen schmaler als
     die Stammdatenkarte darueber, und auf der Seite staenden Karten in zwei
     Breiten (Betreiber, 2026-09-06). Der graue Grund ist ein Band hinter den
     Karten, kein Rahmen um sie. */
  padding: var(--ms-space-5) 0;
  margin-top: var(--ms-space-4);
}
/* DIE POLSTERUNG DER KUNDENSEITE IST FUER EINE GANZE FENSTERBREITE GEDACHT
   (--ms-space-8 links und rechts). In der Verwaltung sitzt dieselbe Flaeche in
   einer Spalte neben der Katalogliste; dort kostet sie eine ganze Kartenspalte.
   Unterhalb von 1600px faellt sie deshalb auf das naechstkleinere Mass — die
   Karten stehen dadurch auch hier nebeneinander statt untereinander, und
   genau darum geht es bei dieser Ansicht. */
@media (max-width: 1600px) {
  .catalog-studio { padding: var(--ms-space-3) 0; }
  .catalog-studio .stage-area { padding: var(--ms-space-4); }
}

.stage-delete { margin-top: var(--ms-space-5); }
/* `.icon-action--danger` faerbt erst beim Hinsehen; auf der Buehne ist der
   Papierkorb dauerhaft rot, weil dort keine Liste steht, in der das zum
   Dauersignal wuerde. */
.stage-cell-tools .icon-action--danger,
.stage-area-controls .icon-action--danger { color: var(--ms-error-text); }

/* ── Lager: Regalraster und Kamerasucher ──────────────────────────────────
   Ein Bauteil, weil es an zwei Stellen steht (Uebersicht und spaeter der
   Kommissionierschritt) — Regel 4 und 9 des Designsystems: wiederkehrende
   Gestalt wird ein Bauteil, und der Name sagt, was es IST.

   DAS REGAL WIRD GEZEICHNET, WIE ES STEHT: ein Fach ist eine senkrechte
   Saeule von Ebenen, die oberste oben. Die Zahlen zaehlen von unten (E01 =
   Bodenhoehe), die Vorlage sortiert deshalb absteigend — sonst stuende das
   Regal auf dem Kopf.

   BELEGUNG ALS FLAECHE, NICHT ALS FARBE. Ein belegtes Fach traegt den
   Tint-Grund, ein leeres bleibt weiss. Farbe waere hier ein Signal, und
   „belegt" ist kein Signal, sondern der Normalfall — dieselbe Ueberlegung wie
   bei den offenen Bestellungen der Firmenuebersicht, die als Zahl und nicht
   als Abzeichen stehen. */
.shelf-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ms-space-3);
  padding: var(--ms-space-3);
}

.shelf-bay {
  display: flex;
  flex-direction: column;
  gap: var(--ms-space-1);
  min-width: var(--ms-shelf-bay-w);
}

/* Das Zahnrad im Fachkopf. Es sitzt IN der Beschriftung, damit es dem Fach
   gehoert und nicht der Reihe — und gedaempft, weil die Beschriftung gelesen
   und nicht bedient wird. Ein Zahnrad und kein Stift: dahinter steht der
   Aufbau des Regals, nicht der Wert eines Feldes. */
.shelf-bay-edit {
  border: 0;
  background: none;
  padding: 0;
  margin-left: var(--ms-space-1);
  color: var(--ms-text-secondary);
  cursor: pointer;
  vertical-align: middle;
}
.shelf-bay-edit:hover,
.shelf-bay-edit:focus-visible { color: var(--ms-accent); }

.shelf-bay-label {
  margin: 0;
  font-size: var(--ms-text-caption);
  font-weight: var(--ms-weight-semibold);
  color: var(--ms-text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.shelf-cell {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ms-space-2);
  min-height: var(--ms-shelf-cell-h);
  padding: var(--ms-space-2) var(--ms-space-3);
  border: var(--ms-control-border-w) solid var(--ms-border-divider);
  border-radius: var(--ms-radius-sm);
  background: var(--ms-card-bg);
  color: var(--ms-text-primary);
  text-decoration: none;
  transition: border-color var(--ms-transition-base), background var(--ms-transition-base);
}

.shelf-cell:hover,
.shelf-cell:focus-visible {
  border-color: var(--ms-border-control-hover);
}

/* EIN STILLGELEGTES FACH — es bleibt im Bild.
   Nicht aus Bequemlichkeit: verschwindet es, ist sein Zahnrad weg und damit
   der einzige Weg zurueck (gemeldet vom Betreiber, 2026-09-07). Und die
   Faecher liegen nebeneinander wie im Regal — fehlt F03, ruecken F04 und
   alles danach nach links, und das Bild zeigt eine Reihe, die es nicht gibt.

   Gedaempft und nicht ausgeblendet, mit dem Wort „Inaktiv" daneben: Farbe
   allein ist keine Auskunft (Designsystem, WCAG 1.4.1). */
/* DIE BESCHRIFTUNG WIRD NICHT GEDAEMPFT. „FACH 7" ist die Auskunft, welches
   Fach das ist, und --ms-text-tertiary erreicht auf Weiss nur 3,62:1 — unter
   den 4,5:1, die WCAG 1.4.3 fuer Text fordert. Gedaempft werden die ZELLEN
   (gestrichelt, grauer Grund); die Aussage traegt das Wort daneben. */
.shelf-bay-state {
  display: inline-block;
  margin-left: var(--ms-space-1);
  font-size: var(--ms-text-caption);
  font-weight: var(--ms-weight-regular);
  color: var(--ms-error-text);
  text-transform: none;
  letter-spacing: 0;
}

/* Die stillgelegte Zelle. KEIN Verweis (im Markup ein <span>): dort ist per
   Regel nichts eingelagert — stilllegen geht nur leer —, und einraeumen kann
   man auch nicht. Gestrichelt statt durchgezogen, damit der Unterschied auch
   ohne Farbe sichtbar ist. */
.shelf-cell--off {
  border-style: dashed;
  background: var(--ms-bg-secondary);
  color: var(--ms-text-secondary);
  cursor: default;
}
/* KEINE RUECKMELDUNG UNTER DEM ZEIGER. `.shelf-cell:hover` gilt auch fuer den
   <span>, und ein Rand, der auf den Zeiger reagiert, verspricht einen Klick,
   den es hier nicht gibt. */
.shelf-cell--off:hover { border-color: var(--ms-border-divider); }
/* --ms-text-secondary und nicht -tertiary: das Zeichen IST hier die Auskunft
   (in einem laufenden Fach mit Filter „Inaktive anzeigen" steht kein Wort
   daneben), und ein Bedeutungstraeger braucht 3:1 gegen seinen Grund
   (WCAG 1.4.11). Tertiaer erreicht auf --ms-bg-secondary nur 2,99:1. */
.shelf-cell--off .shelf-cell-count { color: var(--ms-text-secondary); }

/* EINE EBENE, DIE ES HIER NICHT GIBT — das Fach ist niedriger als die Reihe
   oder die Ebene ist stillgelegt. Sie haelt nur die Hoehe: ohne sie rutschen
   die Ebenen eines niedrigen Fachs nach oben, und E02 stuende auf der Hoehe
   von E03 im Fach daneben. Die Hoehe IST beim Regal die Auskunft, und ein
   Bild, dessen Ebenen nicht auf einer Linie liegen, gibt sie falsch.

   Ohne Rand und ohne Grund: da ist kein Fachboden. Ein gestrichelter Kasten
   waere ein Angebot („hier koennte eine Ebene sein"), und das Angebot steht
   im Zahnrad am Fach. */
.shelf-gap { min-height: var(--ms-shelf-cell-h); }

.shelf-cell--used {
  background: var(--ms-tint);
}

/* SUCHTREFFER — hier liegt einer der gefundenen Artikel (seit 2026-09-07).
   Farbe ist im System die Form eines SIGNALS, und ein Suchtreffer ist eines:
   die Trefferliste sagt WAS, das Regal sagt WO. Ohne Markierung muesste man
   die Schluessel von oben nach unten abgleichen.

   FARBE ALLEIN GENUEGT NICHT (WCAG 1.4.1): die Zelle traegt zusaetzlich ein
   Lupensymbol mit Titel, und die Schluessel stehen in der Trefferliste
   darueber als Text.

   `rgba(var(--ms-accent-rgb), …)` und kein eigenes Farbtripel — die
   Designpruefung verlangt genau diese Form. */
.shelf-cell--hit,
.shelf-zone.shelf-cell--hit {
  background: rgba(var(--ms-accent-rgb), 0.14);
  border-color: var(--ms-accent);
}

/* ZAEHLUNG FAELLIG — die Inventur-Arbeitsliste, im Regal statt als Liste.
   Warnfarbe und nicht Akzent, damit sie sich vom Suchtreffer unterscheidet:
   eine Zelle kann beides sein. Und wieder mit Zeichen, weil Farbe allein keine
   Auskunft ist (WCAG 1.4.1). */
.shelf-cell--due {
  background: rgba(var(--ms-warning-rgb), 0.16);
  border-color: var(--ms-warning);
}

.shelf-cell-due {
  display: inline-flex;
  flex: none;
  color: var(--ms-warning-text);
}

.shelf-cell-hit {
  display: inline-flex;
  flex: none;
  color: var(--ms-accent);
}

/* Der gewaehlte Platz traegt den Akzentrand — 4,65:1 gegen Weiss und damit
   ueber den 3:1, die WCAG 1.4.11 fuer Bedienelement-Umrisse fordert.

   ER MUSS SICH VOM TREFFER UNTERSCHEIDEN, weil eine Zelle beides sein kann:
   der Treffer traegt die Flaeche, die Auswahl den kraeftigeren Rand. */
.shelf-cell--active,
.shelf-zone.shelf-cell--active {
  border-color: var(--ms-accent);
  box-shadow: var(--ms-control-focus-ring);
}

.shelf-cell-level {
  font-size: var(--ms-text-small);
  font-weight: var(--ms-weight-semibold);
  color: var(--ms-text-secondary);
}

.shelf-cell-count {
  font-weight: var(--ms-weight-bold);
  font-variant-numeric: tabular-nums;
}

.shelf-cell-bins {
  display: inline-flex;
  align-items: center;
  gap: var(--ms-space-1);
  font-size: var(--ms-text-caption);
  color: var(--ms-text-secondary);
}

.shelf-zone {
  display: flex;
  flex-direction: column;
  gap: var(--ms-space-1);
  min-width: var(--ms-shelf-zone-w);
  padding: var(--ms-space-3);
  border: var(--ms-control-border-w) solid var(--ms-border-divider);
  border-radius: var(--ms-radius-sm);
  background: var(--ms-card-bg);
  color: var(--ms-text-primary);
  text-decoration: none;
}

.shelf-zone:hover,
.shelf-zone:focus-visible {
  border-color: var(--ms-border-control-hover);
}

/* Der gewaehlte Platz oeffnet sich IN seinem Abschnitt, unter dem Raster, aus
   dem man ihn angeklickt hat (Weisung des Betreibers, 2026-09-07). Vorher stand
   er ueber der ganzen Auswahl — erst die Antwort, dann die Frage.

   ABSTAENDE AUS DEM SYSTEM, keine eigenen Zahlen: seitlich und unten dieselbe
   Polsterung wie `.shelf-row` darueber, damit die Karte auf der Kante des
   Rasters sitzt und nicht daneben. Oben nichts — die Polsterung von
   `.shelf-row` ist der Abstand zwischen Raster und Karte, und zwei Abstaende
   uebereinander waeren doppelt. Die Trennlinie sagt, dass hier etwas anderes
   anfaengt. */
.shelf-detail {
  padding: 0 var(--ms-space-3) var(--ms-space-3);
  border-top: var(--ms-control-border-w) solid var(--ms-border-divider);
  padding-top: var(--ms-space-4);
}

/* KEINE KARTE IM ABSCHNITT (2026-09-07). Hier stand eine Regel, die der
   Karte im Ausschnitt Schatten nahm und einen Rahmen gab — sie hat das
   Grundproblem nur gemildert: der Abschnittsrumpf ist schon eine Flaeche mit
   Rahmen, eine Karte darin ein zweiter Rahmen um denselben Inhalt. Der
   Ausschnitt bringt jetzt gar keinen mit; er gliedert nur (Kopfzeile mit
   Trennlinie, darunter die Tabelle).

   DIE TABELLE ZIEHT SICH BIS AN DIE KANTEN der Polsterung: eine Tabelle mit
   eigenem Einzug innerhalb eines Blocks mit Einzug sieht verrutscht aus. */
.shelf-detail .table-responsive {
  margin-inline: calc(var(--ms-space-3) * -1);
}
.shelf-detail .table th:first-child,
.shelf-detail .table td:first-child { padding-left: var(--ms-space-3); }
.shelf-detail .table th:last-child,
.shelf-detail .table td:last-child { padding-right: var(--ms-space-3); }

/* Kopf des Ortsausschnitts: Angaben links, Handlungen rechts, darunter eine
   Trennlinie — dieselbe Aufteilung wie `.admin-header`, aber ohne Karte um
   sich (siehe unten). */
.storage-head {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--ms-space-3);
  padding-bottom: var(--ms-space-3);
  border-bottom: var(--ms-control-border-w) solid var(--ms-border-divider);
}
.storage-head-actions { flex: none; }

/* ── Vorschlaege beim Tippen („Artikel hinzufuegen") ──
   Eine LISTE und kein Aufklappmenue: sie steht unter dem Feld in der Fusszeile
   der Karte und verdeckt nichts. Ein ueberlagerndes Menue muesste sich um
   Rollen, Umbruchpunkte und den Rand des Dialogs kuemmern — auf dem Handy die
   Sorte Aufwand, die ohne Gewinn Fehler bringt. */
.suggest-wrap { width: 100%; }
.suggest-wrap[hidden] { display: none; }

.suggest-list {
  list-style: none;
  margin: var(--ms-space-2) 0 0;
  padding: 0;
  border: var(--ms-control-border-w) solid var(--ms-border-divider);
  border-radius: var(--ms-radius-sm);
  overflow: hidden;
}
.suggest-list > li + li { border-top: var(--ms-control-border-w) solid var(--ms-border-divider); }

/* ALLE TREFFER, ROLLEND (Weisung des Betreibers, 2026-09-07). Gekuerzt liess
   die Liste den Bediener raten, was darunter noch kommt.

   NUR DIE LISTE ROLLT, nicht der Dialogrumpf: rollte er mit, waere das
   Suchfeld nach drei Zeilen aus dem Bild — und genau dort tippt man weiter.

   `clamp()` UND NICHT `min()`: bevorzugt richtet sich die Hoehe nach dem
   Bildschirm (52vh), am Schreibtisch begrenzt sie der Token — aber nie unter
   die Untergrenze. Mit `min(52vh, …)` allein fiel die Liste in einem Fenster,
   das `100vh` als 0 meldete, auf 0px zusammen und war unsichtbar
   (nachgemessen am 2026-09-07). Eine Liste, die verschwindet, ist schlimmer
   als eine kurze.

   `overscroll-behavior: contain` haelt die Bewegung in der Liste, sonst rollt
   am Ende die Seite dahinter weiter — auf Beruehrungsgeraeten der uebliche
   Aerger. */
.suggest-list--scroll {
  max-height: clamp(var(--ms-suggest-min-h), 52vh, var(--ms-suggest-max-h));
  overflow-y: auto;
  overscroll-behavior: contain;
}

.suggest-item-main { display: flex; flex-direction: column; min-width: 0; }
/* Eine Zeile mit Auslassung. „Corning Optical Communications GmbH & Co. KG"
   brach sonst um, und die Zeile wurde drei Zeilen hoch. */
.suggest-item-main .cell-meta { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.suggest-item-aside {
  display: flex;
  /* Eine SPALTE: Menge oben auf der Titelzeile, der Zustandssatz darunter auf
     der Hoehe der Artikelnummer (Begruendung in `_suggest.html.twig`).

     FESTE BREITE, GERECHNET aus Feld + Abstand + Obergrenze. Waere sie
     inhaltsbreit, bestimmte der laengste Text der Zeile die Breite — und damit
     die Stelle, an der das Feld steht. Mit der festen Breite fuellt die
     Mengenzeile die Spalte genau aus, das Feld steht in allen Zeilen gleich,
     und `flex-start` legt den Zustandssatz auf dieselbe linke Kante. */
  flex-direction: column;
  align-items: flex-start;
  gap: var(--ms-space-05);
  flex: none;
  width: calc(var(--ms-suggest-qty-w) + var(--ms-space-2) + var(--ms-suggest-max-w));
  font-variant-numeric: tabular-nums;
  color: var(--ms-text-secondary);
  font-size: var(--ms-text-small);
}

.suggest-note {
  margin: var(--ms-space-2) 0 0;
  font-size: var(--ms-text-small);
  color: var(--ms-text-secondary);
}

/* ── Eine Zeile der Vorschlagsliste ──
   Drei Dinge in einer Zeile: Auswahl, Zustand, Menge. Das Haekchen und der
   Name gehoeren zusammen (ein `<label>` um beide, damit der Name die
   Trefferflaeche vergroessert — am Regal wird mit dem Daumen getippt), Zustand
   und Mengenfeld stehen rechts. */
.suggest-row {
  display: flex;
  /* OBEN AUSGERICHTET (Weisung des Betreibers, 2026-09-09): das Mengenfeld
     steht damit auf der Zeile des Artikelnamens. Senkrecht mittig schwebte es
     zwischen Name und Nummer und gehoerte optisch zu keinem von beiden. */
  align-items: flex-start;
  gap: var(--ms-space-3);
  /* Bedienmass und Polsterung trug vorher der Knopf, der die Zeile ausfuellte
     (jede Zeile war ein Absenden). Jetzt traegt sie die Zeile selbst — mit dem
     Finger getroffen wird sie genauso. */
  min-height: var(--ms-control-h);
  padding: var(--ms-space-2) var(--ms-space-3);
}
.suggest-pick {
  display: flex;
  /* GRUNDLINIE UND NICHT OBERKANTE: das Haekchen sitzt damit auf der Zeile
     des Namens. Mit `flex-start` stand seine Mitte 5,6px ueber der optischen
     Mitte des Titels (nachgemessen), mit der Grundlinie 1,1px — und ohne einen
     ausgedachten Versatz. */
  align-items: baseline;
  gap: var(--ms-space-3);
  flex: 1 1 auto;
  min-width: 0;
  cursor: pointer;
}
/* GESPERRT HEISST GEDAEMPFT, NICHT UNSICHTBAR: wer „Rollup" sucht und es nicht
   findet, sucht weiter oder legt es doppelt an. Der Satz daneben sagt, woran
   es liegt — Farbe allein waere keine Auskunft (WCAG 1.4.1). */
.suggest-row--off { cursor: default; }
.suggest-row--off .suggest-pick { cursor: default; }
.suggest-row--off .suggest-item-main strong { color: var(--ms-text-secondary); font-weight: var(--ms-weight-regular); }
.suggest-state {
  font-size: var(--ms-text-caption);
  color: var(--ms-text-secondary);
  /* LINKSBUENDIG ZUM MENGENFELD DARUEBER (Weisung des Betreibers,
     2026-09-09). Rechtsbuendig endete der Satz an der Kante der Einheit und
     begann je Laenge woanders — zwei Kanten in einer Spalte, von denen keine
     etwas mit dem Feld zu tun hatte.

     Er darf umbrechen: die Spalte ist fest, und ein langer Satz soll die
     Zeile hoeher machen und nicht das Feld verschieben. */
  text-align: left;
  width: 100%;
}
.suggest-qty-row { display: flex; align-items: baseline; gap: var(--ms-space-2); }
/* Die Obergrenze neben dem Feld: „/ 59 Stueck". Gedaempft, weil sie eine
   Auskunft und kein Eingabewert ist — und mit `nowrap`, damit „/ 7.105" nicht
   zwischen Zahl und Einheit bricht. */
.suggest-max {
  /* FESTE MINDESTBREITE, damit das Mengenfeld in ALLEN Zeilen an derselben
     Stelle steht: „/ 7.105 Stueck" ist dreissig Pixel breiter als „/ 0 Stueck"
     und schob das Feld sonst je Zeile woandershin (nachgemessen). */
  min-width: var(--ms-suggest-max-w);
  white-space: nowrap;
  color: var(--ms-text-secondary);
  font-size: var(--ms-text-small);
}
/* Schmal, weil dort eine Menge steht und kein Betrag — und mit fester Breite,
   damit die Felder aller Zeilen eine Kante bilden. */
.suggest-qty { width: var(--ms-suggest-qty-w); flex: none; }

/* Unter 640px stehen Auswahl und Menge untereinander: in einer Zeile blieben
   fuer den Artikelnamen keine 100px, und der Name ist die Auskunft. */
@media (max-width: 640px) {
  .suggest-row { flex-wrap: wrap; }
  .suggest-item-aside { width: 100%; justify-content: flex-end; }
}

/* Die zwei Wege im Bestandsdialog stehen untereinander, getrennt durch eine
   Linie — Reiter waeren eine Betriebsart, die am Regal niemand waehlen will. */
.stock-dialog-block + .stock-dialog-block {
  margin-top: var(--ms-space-5);
  padding-top: var(--ms-space-5);
  border-top: var(--ms-control-border-w) solid var(--ms-border-divider);
}

/* Der Kamerasucher. Er steht in der Werkzeugleiste und nicht auf einer eigenen
   Maske: gescannt und getippt wird dieselbe Frage gestellt. */
.scan-stage {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--ms-space-3);
  padding: var(--ms-space-4);
  border-top: var(--ms-control-border-w) solid var(--ms-border-divider);
}
/* DIE FALLE, DIE DAS HAUS SCHON KENNT (`.badge[hidden]`, `.tab-pane[hidden]`,
   `.panel-foot--bar[hidden]`): ein `display` an der Klasse schlaegt die
   Browser-Regel fuer `[hidden]`, weil eine Klasse spezifischer ist als ein
   Attribut in der Vorgabe-Stilkette. Der leere Sucher stand deshalb dauerhaft
   als grauer Kasten in der Maske (gemeldet am 2026-09-07) — auf einem
   Rechner ohne Kamera fuer nichts. */
.scan-stage[hidden] { display: none; }

/* Derselbe Grund am Knopf: `.btn-outline` traegt `display: inline-flex`. Der
   Knopf beginnt verborgen und erscheint erst, wenn der Controller eine Kamera
   gefunden hat — auf einem Rechner ohne waere er ein Weg ins Leere. */
.scan-start[hidden] { display: none; }

.scan-video {
  width: 100%;
  max-width: var(--ms-scan-video-w);
  border-radius: var(--ms-radius-lg);
  background: var(--ms-bg-secondary);
}

.scan-hint {
  margin: 0;
  font-size: var(--ms-text-small);
  color: var(--ms-text-secondary);
}

/* ── Ablaufmaske: die Kette als Vorschau (workflow-preview_controller.js) ──
   Abgesetzt, damit klar ist, dass hier nichts einzugeben ist: das ist das
   Ergebnis der Zeilen darueber. Die Kacheln selbst bringen ihre Gestaltung
   aus `.workflow-*` (pages.css) mit — dieselben Klassen wie im Bestelldetail,
   nichts nachgebaut. */
.workflow-preview {
  margin-top: var(--ms-space-6);
  padding-top: var(--ms-space-5);
  border-top: 1px solid var(--ms-border-light);
}
/* Die Vorschau ist Anzeige, keine Bedienung — kein Zeigefinger, kein
   Randwechsel beim Ueberfahren. */
.workflow-preview .workflow-step-body { cursor: default; }
/* Pfeile und Papierkorb einer Sammlungszeile stehen zusammen am Ende. */
/* Groesse und Sperrzustand kommen aus `.icon-action` — hier steht nur noch
   die Reihe selbst. */
.collection-row-actions {
  display: inline-flex; align-items: center; gap: var(--ms-action-gap);
  flex: 0 0 auto;
}

/* ── Ablaufmaske: eine Zeile je Schritt ──
   Vier Spalten in fester Breite, damit die Zeilen eine gemeinsame Kante haben
   und die Spaltenkoepfe darueber passen: Nummer, beide Sprachen, Bedingung,
   Werkzeug. `.collection-row` daneben liefert weiterhin die Huelle, die
   `form-collection_controller.js` sucht — deshalb ueberschreibt die Klasse
   hier dessen `display: flex`.

   Die beiden Sprachen teilen sich den freien Platz zu gleichen Teilen: keine
   ist wichtiger als die andere, und eine ungleiche Breite laese sich als
   Rangordnung. */
.step-row {
  display: grid;
  /* Die letzte Spalte ist FEST und nicht `auto`: der Kopf hat dort nichts
     stehen, und mit `auto` waere sie dort null breit — die Spalten von Kopf
     und Zeile liefen auseinander. 6.5rem sind die drei Symbolknoepfe
     (3 x 2rem) plus ihre beiden Luecken. */
  /* 15rem fuer die Bedingung: die laengste Beschriftung
     („Versandinformationen") braucht bei --ms-text-body rund 160px, dazu die
     Polsterung links (16px) und der Platz fuer den Chevron rechts (40px) —
     bei 13rem stand dort „Versandinformatio…". */
  grid-template-columns: 1.75rem minmax(0, 1fr) minmax(0, 1fr) 15rem 6.5rem;
  align-items: end;
  gap: var(--ms-space-3);
}
/* Der Zeilenabstand kommt aus `.collection-row`; die Kopfzeile braucht ihn
   nicht, sie klebt an der ersten Zeile. */
.step-row--head {
  margin-bottom: var(--ms-space-2);
  font-size: var(--ms-text-caption);
  color: var(--ms-text-secondary);
  align-items: center;
}
/* Die Felder tragen in der Zeile keine eigene Beschriftung mehr (die steht im
   Kopf) und damit auch keinen eigenen Abstand nach unten. */
.step-row .form-row { margin-bottom: 0; }
.step-row-num {
  font-size: var(--ms-text-small);
  color: var(--ms-text-secondary);
  font-variant-numeric: tabular-nums;
  padding-bottom: var(--ms-space-2);
  text-align: right;
}
@media (max-width: 900px) {
  /* Untereinander, sobald vier Spalten nicht mehr lesbar sind. Die Kopfzeile
     faellt dann weg — ohne Spalten benennt sie nichts. */
  .step-row { grid-template-columns: 1fr; align-items: stretch; }
  .step-row--head { display: none; }
  .step-row .form-row { margin-bottom: var(--ms-space-2); }
  .step-row-num { text-align: left; padding-bottom: 0; }
}
/* Die drei festen Kacheln der Vorschau: sie gehoeren zu jeder Bestellung und
   sind hier nicht zu entscheiden. Gedaempft ueber die Deckkraft und nicht
   ueber eigene Farben — es sind dieselben Kacheln, nur nicht Gegenstand
   dieser Maske. */
.workflow-step--fest { opacity: 0.55; }

/* Eine feste Zeile der Schrittliste: sie zeigt einen Schritt, der zu jeder
   Bestellung gehoert, und ist nicht Gegenstand dieser Maske. Der Abstand
   kommt sonst von `.collection-row`, die sie mit Absicht nicht traegt. */
.step-row--gesperrt { margin-bottom: var(--ms-space-3); }

/* Reduzierte Lageroberflaeche nach einem QR-Scan: dieselben Bauteile und
   Token wie die Verwaltung, aber ohne deren Navigationshülle. */
.scan-body {
  min-height: 100dvh;
  background: var(--ms-bg-secondary);
  padding-bottom: env(safe-area-inset-bottom);
}
.scan-topbar {
  position: sticky;
  top: 0;
  z-index: var(--ms-z-header);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ms-space-3);
  min-height: var(--ms-topbar-h);
  padding: env(safe-area-inset-top) max(var(--ms-space-4), env(safe-area-inset-right)) 0 max(var(--ms-space-4), env(safe-area-inset-left));
  background: var(--ms-topbar-bg);
  border-bottom: var(--ms-control-border-w) solid var(--ms-border-divider);
  backdrop-filter: blur(var(--ms-space-3));
}
.scan-brand {
  display: flex; align-items: center; gap: var(--ms-space-2); min-width: 0;
  color: var(--ms-text-primary); font-weight: var(--ms-weight-bold); text-decoration: none;
}
.scan-brand img { height: 28px; width: auto; flex: none; }
.scan-brand-word { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.scan-main { width: min(100%, var(--ms-content-width)); margin-inline: auto; padding: var(--ms-space-5); }
.scan-hero { margin-bottom: var(--ms-space-5); }
/* „Sicherheitsverschluss" passt bei 375px in keine Zeile und lief rechts aus
   dem Bild. `hyphens: auto` trennt es an einer Silbe und setzt einen
   Trennstrich — die Sprache steht am <html>, sonst wuesste der Browser nicht,
   nach welchen Regeln. `overflow-wrap` bleibt als Netz fuer Zeichenketten
   ohne Silben (Artikelnummern). */
.scan-hero h1 { margin: var(--ms-space-2) 0 0; font-size: var(--ms-text-h1); overflow-wrap: break-word; hyphens: auto; }
/* Gleiche Trennung wie in der Ueberschrift: „Wirtschaftspruefungsgesellschaft"
   passt bei 375px in keine Zeile und lief sonst rechts aus dem Bild. */
.scan-hero p { margin: var(--ms-space-2) 0 0; color: var(--ms-text-secondary); overflow-wrap: break-word; hyphens: auto; }
.scan-product-hero { display: flex; align-items: center; gap: var(--ms-space-4); }
.scan-product-titles { min-width: 0; }
/* Die Artikelart, buendig mit Titel und Nummer darunter — siehe Vorlage. */
.scan-kind { margin: 0; font-size: var(--ms-text-caption); color: var(--ms-text-secondary); }
/* JEDES BILDFENSTER IST QUADRATISCH (Designsystem). Die Breite ist eigen und
   kommt NICHT mehr aus `--ms-scan-video-w` — das ist das Mass des Kamerabilds
   und hatte hier nur zufaellig gepasst. Gezeigt wird die mittlere Stufe
   (480px): bei 120 Anzeigepunkten auf einem Retina-Schirm ist die kleine
   (160px) sichtbar weich. */
.scan-product-image {
  flex: none;
  width: 30vw; max-width: 120px;
  /* `height: auto` ist hier PFLICHT und keine Gewohnheit: das
     height-Attribut am <img> wirkt wie eine CSS-Angabe und schlaegt
     `aspect-ratio`. Ohne diese Zeile war der Kasten gemessene 480 Punkte hoch
     und schob alles Uebrige unter die Falz. Die Attribute bleiben trotzdem
     stehen — sie halten das Bildfenster frei, bevor das Bild da ist. */
  height: auto;
  aspect-ratio: 1 / 1; object-fit: contain;
  background: var(--ms-card-bg); border-radius: var(--ms-radius-lg);
}
.scan-description {
  margin: 0 0 var(--ms-space-5);
  color: var(--ms-text-secondary);
  font-size: var(--ms-text-small);
}

/* ── Die Bestandszeile ──
   Eine Karte, die eine Geschichte erzaehlt: was liegt da, was ist davon
   vergeben, was bleibt bestellbar. Vorher standen drei gleichrangige Kacheln
   untereinander — sie beantworteten dieselbe Frage dreimal und schoben die
   Knoepfe unter die Falz. */
.scan-stock {
  margin-bottom: var(--ms-space-5);
  padding: var(--ms-space-4);
  border-radius: var(--ms-radius-lg);
  background: var(--ms-card-bg);
  box-shadow: var(--ms-card-shadow);
}
.scan-stock-main { display: flex; align-items: baseline; gap: var(--ms-space-2); margin: 0; }
.scan-stock-main strong { font-size: var(--ms-text-h1); font-variant-numeric: tabular-nums; }
.scan-stock-main span { color: var(--ms-text-secondary); }
.scan-stock-split {
  display: flex; flex-wrap: wrap; gap: var(--ms-space-1) var(--ms-space-3);
  margin: var(--ms-space-2) 0 0;
  color: var(--ms-text-secondary); font-size: var(--ms-text-small);
}
.scan-stock-part { font-variant-numeric: tabular-nums; }
.scan-stock-orders {
  margin: var(--ms-space-3) 0 0; padding: var(--ms-space-3) 0 0;
  border-top: var(--ms-control-border-w) solid var(--ms-border-divider);
  list-style: none;
  font-size: var(--ms-text-small);
}
.scan-stock-orders li { display: flex; justify-content: space-between; gap: var(--ms-space-3); }
.scan-stock-orders li + li { margin-top: var(--ms-space-1); }
/* Im Fenster steht je Beleg mehr als eine Zeile: Nummer ueber Veranstaltung,
   Menge ueber Liefertermin. */
.modal .scan-stock-orders { margin: 0; padding: 0; border-top: 0; }
.modal .scan-stock-orders li {
  align-items: flex-start;
  padding: var(--ms-space-3) 0;
  border-bottom: var(--ms-control-border-w) solid var(--ms-border-divider);
}
.modal .scan-stock-orders li:last-child { border-bottom: 0; }
.modal .scan-stock-orders li > span { display: flex; flex-direction: column; min-width: 0; }
.modal .scan-stock-orders small { color: var(--ms-text-secondary); }
/* Links darf alles umbrechen, rechts nichts — und rechts nimmt nur den Platz,
   den die Menge braucht. Vorher standen zwei nicht schrumpfbare Spalten
   nebeneinander; zusammen waren sie breiter als das Fenster, und der Rumpf
   bekam eine waagerechte Bildlaufleiste (`overflow-y: auto` erzeugt sie
   mit) — sie las sich als dicker Strich ueber dem Schliessen-Knopf. */
.scan-stock-order-main { flex: 1 1 auto; overflow-wrap: anywhere; }
.scan-stock-order-qty { flex: none; text-align: right; white-space: nowrap; }
.scan-stock-defect {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--ms-space-2);
  margin: var(--ms-space-3) 0 0;
  color: var(--ms-text-secondary); font-size: var(--ms-text-small);
}

/* ── Die Knopfleiste ──
   Ausserhalb des Flusses, damit sie nichts verdecken KANN, und mit einer
   Trefferhoehe von 44px: das ist das Mindestmass fuer einen Daumen. */
.scan-bar {
  position: fixed; inset: auto 0 0 0;
  z-index: var(--ms-z-header);
  display: flex; gap: var(--ms-space-3);
  padding: var(--ms-space-3) max(var(--ms-space-4), env(safe-area-inset-right))
           calc(var(--ms-space-3) + env(safe-area-inset-bottom)) max(var(--ms-space-4), env(safe-area-inset-left));
  background: var(--ms-topbar-bg);
  border-top: var(--ms-control-border-w) solid var(--ms-border-divider);
  backdrop-filter: blur(var(--ms-space-3));
}
/* GLEICH GROSS (Weisung des Betreibers, 2026-09-14). `flex: 1 1 0` allein
   genuegt nicht: ein Flex-Element schrumpft nicht unter seine Mindestbreite
   aus dem Inhalt, und „Wareneingang" ist laenger als „Korrektur" — gemessen
   202 zu 155 Punkte. Erst `min-width: 0` gibt beiden dieselbe Haelfte. Die
   Polsterung faellt dafuer knapper aus als am Schreibtisch; die Trefferhoehe
   von 44 Punkten bleibt unangetastet. */
.scan-bar > * {
  flex: 1 1 0;
  min-width: 0;
  justify-content: center;
  min-height: 44px;
  padding-inline: var(--ms-space-3);
  white-space: nowrap;
}
/* Der Rumpf bekommt mehr Luft, als die Leiste hoch ist — sonst liegt die
   letzte Zeile darunter und ist nicht zu lesen. Gemessen ist die Leiste 70
   Punkte hoch (44 Trefferhoehe plus Polsterung); --ms-space-12 sind 80. */
.scan-body--bar .scan-main { padding-bottom: calc(var(--ms-space-12) + env(safe-area-inset-bottom)); }
.scan-grid { display: grid; grid-template-columns: minmax(0, 3fr) minmax(18rem, 2fr); gap: var(--ms-space-5); align-items: start; }
.scan-list { display: flex; flex-direction: column; }
.scan-list-item { display: flex; align-items: center; gap: var(--ms-space-3); justify-content: space-between; padding: var(--ms-space-3) var(--ms-space-4); border-bottom: var(--ms-control-border-w) solid var(--ms-border-divider); color: var(--ms-text-primary); text-decoration: none; }
/* DIE LETZTE ZEILE TRENNT NICHTS MEHR. Ihre Linie lag genau auf der unteren
   Kante der Karte — zusammen mit dem 1px-Ring aus `--ms-card-shadow` las sich
   das wie ein staerkerer Schatten als an der Nachbarkarte. Gemessen sind die
   Schatten identisch (gleiches Token, gleicher Radius); der Unterschied war
   diese Linie. Ein Trennstrich gehoert ZWISCHEN zwei Zeilen, nicht hinter die
   letzte. */
.scan-list > .scan-list-item:last-child { border-bottom: 0; }
.scan-list-item > span { min-width: 0; display: flex; flex-direction: column; }
.scan-list-item small { color: var(--ms-text-secondary); }
.scan-facts { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--ms-space-3); margin-bottom: var(--ms-space-5); }
.scan-facts > div { padding: var(--ms-space-4); border-radius: var(--ms-radius-lg); background: var(--ms-card-bg); box-shadow: var(--ms-card-shadow); }
.scan-facts small, .scan-facts strong { display: block; }
.scan-facts small { color: var(--ms-text-secondary); }
.scan-actions { display: flex; flex-direction: column; gap: var(--ms-space-3); }
.scan-actions .btn-primary, .scan-actions .btn-outline { width: 100%; justify-content: center; }
.scan-action { border-top: var(--ms-control-border-w) solid var(--ms-border-divider); padding-top: var(--ms-space-3); }
.scan-action summary { cursor: pointer; font-weight: var(--ms-weight-semibold); }
.scan-action form { margin-top: var(--ms-space-3); }
.scan-status-actions { display: grid; gap: var(--ms-space-2); }

@media (max-width: 48rem), (orientation: portrait) {
  .scan-main { padding: var(--ms-space-4); }
  .scan-grid { grid-template-columns: 1fr; }
  /* HIER STAND `position: sticky; bottom: …` an `.scan-actions`. Ein
     sticky-Element mit `bottom` wird nach OBEN gezogen, sobald seine
     natuerliche Lage unter dem Fensterrand liegt — und legt sich dabei ueber
     seinen Vorgaenger. Gemessen am 2026-09-14 bei 375px: Aktionskarte und
     Lagerortliste begannen beide bei y = 543, ein Treffertest auf die erste
     Ortszeile lieferte die Aktionskarte. Die Liste war auf dem Telefon nicht
     erreichbar. Was kleben soll, klebt jetzt als `.scan-bar` ausserhalb des
     Flusses. */
  .scan-facts { grid-template-columns: 1fr; }
  .scan-list-item { align-items: flex-start; flex-wrap: wrap; }
}

@media (orientation: landscape) and (pointer: coarse) {
  .scan-main { width: 100%; }
  .scan-grid { grid-template-columns: minmax(0, 1fr) minmax(20rem, 0.7fr); }
}
