:root {
    --erp-bg: #f4f6f8;
    --erp-surface: #ffffff;
    --erp-border: #d9dee3;
    --erp-text: #1f2933;
    --erp-muted: #5b6b79;
    --erp-brand: #1f6f5c;
    --erp-brand-contrast: #ffffff;
    --erp-danger: #b3261e;
    --erp-info: #1a5fb4;
    --erp-ok: #1f6f5c;
    /* Diagramme (Statistik): zwei Serienfarben aus der Referenzpalette des
       dataviz-Skills, mit dessen Validator gegen #ffffff geprueft (Helligkeit,
       Chroma, CVD-Abstand, Kontrast >= 3:1). Serie 1 = laufender Zeitraum,
       Serie 2 = Vergleich. Das ERP hat kein dunkles Thema; kommt eines, bekommt
       es eigene, gegen seine Flaeche validierte Werte -- keine automatische
       Umkehr. */
    --chart-series-1: #2a78d6;
    --chart-series-2: #eb6834;
    --chart-grid: #e6e9ec;
}

* { box-sizing: border-box; }

body {
    margin: 0;
    font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
    background: var(--erp-bg);
    color: var(--erp-text);
}

.container {
    max-width: 1100px;
    margin: 0 auto;
    padding: 0 1.25rem;
}

.site-header {
    background: var(--erp-surface);
    border-bottom: 1px solid var(--erp-border);
}

.site-header .container {
    display: flex;
    align-items: center;
    gap: 1.5rem;
    padding-top: 0.75rem;
    padding-bottom: 0.75rem;
}

.brand {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    text-decoration: none;
    color: var(--erp-text);
    font-weight: 600;
}

.brand-mark {
    background: var(--erp-brand);
    color: var(--erp-brand-contrast);
    border-radius: 6px;
    padding: 0.15rem 0.45rem;
    font-size: 0.8rem;
    letter-spacing: 0.05em;
}

.main-nav {
    display: flex;
    gap: 1rem;
    flex: 1;
}

.main-nav a {
    color: var(--erp-muted);
    text-decoration: none;
}

.main-nav a:hover { color: var(--erp-text); }
/* Der Bereich, in dem der Benutzer gerade steht. Ohne diese Markierung ist
   an einer Detailseite nicht erkennbar, wozu die Leiste links gehoert. */
.main-nav a.main-nav-active { color: var(--erp-brand); font-weight: 600; }

/* Die Unterseiten des aktiven Bereichs stehen LINKS, nicht als dritte
   Kopfzeile: seit dem 08.09.2026 hat die Navigation drei Ebenen (Lager:
   Wareneingang > Freier Wareneingang), und die trug eine waagerechte Leiste
   nicht mehr -- die Verwaltung allein hat achtzehn Unterpunkte. Die Leiste
   steht auf JEDER Seite des Bereichs, auch auf Detail- und Formularseiten.
   `.hub-sidebar` bekommt die Breite, `.hub-content` den Rest. */
/* Eine Seite mit Leiste darf dem Inhalt nicht 210 px wegnehmen: der
   Seitencontainer ist auf 1100 px gedeckelt, und eine Auftragstabelle hatte
   danach spuerbar weniger Platz als vor dem Umbau. Der Deckel waechst
   deshalb genau um Leiste plus Abstand. `:has()` statt einer Klasse am
   `<main>`, weil dessen Klassenblock von einzelnen Seiten ueberschrieben wird
   (Etiketten-Designer) -- ein Browser ohne `:has()` bekommt schlicht den
   alten, engeren Deckel. */
.site-main:has(.hub-layout) { max-width: 1342px; }

.hub-layout {
    display: flex;
    gap: 2rem;
    align-items: flex-start;
}
.hub-sidebar {
    flex: 0 0 210px;
    position: sticky;
    top: 1rem;
}
.hub-content {
    flex: 1;
    min-width: 0;
}

.side-tabs {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}
.side-tab {
    padding: 0.55rem 0.8rem;
    text-decoration: none;
    color: var(--erp-muted);
    border-left: 2px solid transparent;
    border-radius: 0 6px 6px 0;
    font-size: 0.9rem;
}
.side-tab:hover { color: var(--erp-text); background: #fafcfb; }
.side-tab-active {
    color: var(--erp-brand);
    border-left-color: var(--erp-brand);
    background: #eef6f3;
    font-weight: 600;
}
/* Die dritte Ebene: eingerueckt und kleiner, damit auf einen Blick zu sehen
   ist, welcher Schritt zu welchem Bereich gehoert. Aufgeklappt ist immer nur
   die Gruppe, in der der Benutzer steht (siehe base.html). */
.side-tab-sub {
    padding-left: 1.7rem;
    font-size: 0.85rem;
}

/* Am Telefon (Kamera-Scan im Lager) faellt die Leiste auf eine waagerecht
   scrollende Zeile zurueck -- ein 210 px breites Seitenmenu naehme dort den
   halben Bildschirm.

   `align-items: flex-start` gilt auf der QUERachse: in einer Spalte ist das
   die waagerechte. Ohne das Zuruecksetzen zieht sich `.hub-content` auf seine
   Inhaltsbreite statt auf die des Elternteils und schiebt die Seite -- an der
   Produktion gemessen 923 px in einem 560 px breiten Elternteil, und zwar auf
   JEDER Seite mit Leiste. */
@media (max-width: 720px) {
    .hub-layout { flex-direction: column; gap: 0; align-items: stretch; }
    .hub-sidebar { position: static; flex: none; width: 100%; }
    .side-tabs {
        flex-direction: row;
        gap: 0.4rem;
        overflow-x: auto;
        border-bottom: 1px solid var(--erp-border);
        margin-bottom: 1.25rem;
    }
    .side-tab {
        border-left: none;
        border-bottom: 2px solid transparent;
        border-radius: 0;
        white-space: nowrap;
    }
    .side-tab-sub { padding-left: 0.8rem; }
    .side-tab-active {
        border-left-color: transparent;
        border-bottom-color: var(--erp-brand);
        background: transparent;
    }
}

.user-menu {
    display: flex;
    align-items: center;
    gap: 0.75rem;
}

.user-name { color: var(--erp-muted); }

/* Arbeitsplatzwaehler im Kopfbereich. Er steht neben dem Benutzermenue, weil
   beides dasselbe beantwortet: wer sitzt hier und woran. */
.workstation-picker {
    display: flex;
    align-items: center;
    gap: 0.35rem;
}

.workstation-picker select {
    padding: 0.3rem 0.5rem;
    border: 1px solid var(--erp-border);
    border-radius: 6px;
    background: transparent;
    color: var(--erp-text);
    font-size: 0.85rem;
    /* Ohne Deckel waechst der Waehler mit dem laengsten Arbeitsplatznamen und
       schiebt den Kopfbereich ueber seine ohnehin knappe Mindestbreite. */
    max-width: 11rem;
}

/* Beschriftung nur fuer Screenreader: der Waehler steht in einer Zeile, in
   der jedes sichtbare Wort Platz kostet -- aber ein Auswahlfeld ohne
   zugaenglichen Namen ist keine Loesung. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.btn {
    display: inline-block;
    padding: 0.4rem 0.9rem;
    border-radius: 6px;
    text-decoration: none;
    font-size: 0.9rem;
    border: 1px solid transparent;
}

.btn-ghost {
    color: var(--erp-text);
    border-color: var(--erp-border);
    background: transparent;
}

.btn-secondary {
    color: var(--erp-brand-contrast);
    background: var(--erp-brand);
}

.site-main {
    padding-top: 1.5rem;
    padding-bottom: 2.5rem;
    min-height: 60vh;
}

.site-footer {
    border-top: 1px solid var(--erp-border);
    padding: 1rem 0;
    color: var(--erp-muted);
}

.muted { color: var(--erp-muted); }

.flash-stack {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin-bottom: 1rem;
}

.flash {
    padding: 0.6rem 0.9rem;
    border-radius: 6px;
    border: 1px solid var(--erp-border);
}

.flash-error { border-color: var(--erp-danger); color: var(--erp-danger); }
.flash-info { border-color: var(--erp-info); color: var(--erp-info); }
.flash-success { border-color: var(--erp-ok); color: var(--erp-ok); }
/* Warnung: kein Fehler (der Vorgang ist durchgelaufen), aber etwas, das der
   Bearbeiter wissen muss -- etwa dass der Regelsteuersatz nur deshalb greift,
   weil die USt-IdNr. nicht bestaetigt ist. Ohne diese Regel erschien eine
   `flash(..., "warning")` in Standardfarbe und las sich wie eine Bestaetigung. */
.flash-warning { border-color: #8a5a00; color: #8a5a00; background: #fdf0d8; }

.empty-state {
    text-align: center;
    padding: 3rem 1rem;
}

.empty-icon { font-size: 2.5rem; margin-bottom: 0.5rem; }

.tile-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 1rem;
}

.tile {
    display: block;
    background: var(--erp-surface);
    border: 1px solid var(--erp-border);
    border-radius: 8px;
    padding: 1rem;
    text-decoration: none;
    color: var(--erp-text);
}

.tile:hover { border-color: var(--erp-brand); }

.tile-icon { font-size: 1.5rem; }
.tile-title { font-weight: 600; margin-top: 0.25rem; }
.tile-desc { color: var(--erp-muted); font-size: 0.85rem; margin-top: 0.15rem; }
.tile-count { display: block; font-size: 1.8rem; font-weight: 700; line-height: 1.1; }
/* Eine Kachel mit offener Arbeit hebt sich ab -- eine Null bleibt still.
   Ohne die Unterscheidung sieht ein leeres Lager aus wie ein volles. */
.tile-alert { border-color: var(--erp-brand); }
.tile-alert .tile-count { color: var(--erp-brand); }

/* Die Arbeitskette der Lager-Startseite: Wareneingang -> Einlagern -> Picken
   -> Packen -> Versand. Reines CSS, keine Diagrammbibliothek -- die CSP
   dieser Anwendung erlaubt kein Inline-Skript, und ein Balken je Station ist
   kein Anlass, eine zu laden. Sie umbricht statt zu scrollen: welche Station
   staut, ist die eine Frage dieser Seite, und die darf nicht rechts
   ausserhalb liegen. */
.flow-chain {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    margin-bottom: 1.5rem;
}
.flow-step {
    flex: 1 1 150px;
    background: var(--erp-surface);
    border: 1px solid var(--erp-border);
    border-radius: 8px;
    padding: 0.9rem;
    text-decoration: none;
    color: var(--erp-text);
}
.flow-step:hover { border-color: var(--erp-brand); }
.flow-step-busy { border-color: var(--erp-brand); }
.flow-step-title { display: block; font-weight: 600; font-size: 0.9rem; }
.flow-step-count { display: block; font-size: 1.9rem; font-weight: 700; line-height: 1.15; }
.flow-step-busy .flow-step-count { color: var(--erp-brand); }
.flow-step-label { display: block; color: var(--erp-muted); font-size: 0.8rem; }
.flow-step-done { display: block; color: var(--erp-muted); font-size: 0.75rem; margin-top: 0.3rem; }
/* Der Packassistent: vier Schritte auf EINER Seite (erfassen, verpacken und
   wiegen, Label, fertig). Dieselbe Formensprache wie die Arbeitskette oben --
   es ist dieselbe Kette, nur fuer einen einzelnen Auftrag. Der erledigte
   Schritt bleibt sichtbar: am Packtisch will man wissen, was man schon
   getan hat, nicht nur, was noch kommt. */
.pack-steps {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    margin-bottom: 1.5rem;
}
.pack-step {
    flex: 1 1 150px;
    background: var(--erp-surface);
    border: 1px solid var(--erp-border);
    border-radius: 8px;
    padding: 0.7rem 0.9rem;
}
.pack-step-no { display: block; font-size: 0.75rem; color: var(--erp-muted); }
.pack-step-title { display: block; font-weight: 600; font-size: 0.9rem; }
.pack-step-active { border-color: var(--erp-brand); border-width: 2px; }
.pack-step-active .pack-step-title { color: var(--erp-brand); }
.pack-step-done { border-color: var(--erp-ok); }
.pack-step-done .pack-step-no { color: var(--erp-ok); }
/* Ein spaeterer Schritt bleibt lesbar, aber gedaempft -- der Packer soll den
   ganzen Weg sehen und trotzdem erkennen, wo er steht. */
.card-pending { opacity: 0.6; }

.flow-bar {
    display: block;
    height: 5px;
    border-radius: 3px;
    background: var(--erp-border);
    margin-top: 0.55rem;
    overflow: hidden;
}
.flow-bar-fill { display: block; height: 100%; background: var(--erp-brand); }

/* ------------------------------------------------------------- Wareneingang */

.page-header { margin-bottom: 1.25rem; }
.page-header h1 { margin-bottom: 0.15rem; }

.card {
    background: var(--erp-surface);
    border: 1px solid var(--erp-border);
    border-radius: 8px;
    padding: 1.25rem;
    margin-bottom: 1.25rem;
}

/* Breite Tabellen scrollen INNERHALB ihrer Karte, statt die ganze Seite
   waagerecht zu schieben. Ohne den Wrapper laeuft eine Tabelle mit vielen
   Spalten (Bewegungshistorie: neun) am Arbeitsplatz-Monitor ueber den
   Seitenrand hinaus -- gemessen 172 px bei 1440 px Breite. */
.table-scroll { overflow-x: auto; }

.data-table { width: 100%; border-collapse: collapse; }
.data-table th, .data-table td {
    text-align: left;
    padding: 0.5rem 0.6rem;
    border-bottom: 1px solid var(--erp-border);
    font-size: 0.9rem;
}
.data-table th { color: var(--erp-muted); font-weight: 600; }
.data-table tr:hover td { background: #fafcfb; }
/* Markiert die Zeile, deren Detailbereich unten steht (Zwei-Bereichs-
   Ansichten des Bereichs "Intern"). Ohne die Markierung ist bei langen
   Listen nicht erkennbar, wozu der Detailbereich gehoert. */
.data-table tr.row-active td { background: #eef4f2; font-weight: 600; }

.btn-primary { color: var(--erp-brand-contrast); background: var(--erp-brand); border-color: var(--erp-brand); }
.btn-danger { color: var(--erp-danger); border-color: var(--erp-danger); background: transparent; }
.btn-success { color: #fff; background: var(--erp-ok); border-color: var(--erp-ok); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-sm { padding: 0.25rem 0.6rem; font-size: 0.8rem; }

.form-field { margin-bottom: 0.9rem; display: flex; flex-direction: column; gap: 0.3rem; }
.form-field label { font-weight: 600; font-size: 0.85rem; }
.form-field input[type="text"], .form-field input[type="search"], .form-field input[type="number"],
.form-field input[type="date"],
.form-field select, .form-field textarea {
    padding: 0.45rem 0.6rem;
    border: 1px solid var(--erp-border);
    border-radius: 6px;
    font: inherit;
}
/* Mehrzeilige Felder lassen sich in der Hoehe ziehen. Das Anmerkungsfeld der
   Belege (`fields.NOTES_KW`) ist dazu mindestens zehn Zeilen hoch und waechst
   mit dem Inhalt (`autogrow.js`) -- vorher waren es zwei Zeilen, und der
   eingesetzte Exporttext liess sich nur scrollend lesen. */
.form-field textarea { resize: vertical; }
.form-field textarea[data-autogrow] { width: 100%; min-height: 12em; box-sizing: border-box; }
.form-row { display: flex; gap: 1rem; flex-wrap: wrap; align-items: flex-end; }
.form-row .form-field { flex: 1; min-width: 160px; }
/* `flex-wrap` ist nicht kosmetisch: ohne es schiebt eine Aktionsleiste mit
   mehreren Knoepfen die ganze Seite waagerecht, statt umzubrechen -- an der
   Auftragsseite bei 400 px Breite 550 px Ueberlauf gemessen. Dieselbe Regel
   wie bei `.data-table` in `.table-scroll`: der Seitenkoerper scrollt nie
   waagerecht. */
.form-actions { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 0.75rem; }
.field-errors { color: var(--erp-danger); font-size: 0.8rem; margin: 0; padding-left: 1.1rem; }

/* Datumsfeld mit eigenem Kalenderknopf (static/js/date_field.js). Der
   Wrapper entsteht erst durch das Skript -- ohne JavaScript bleibt der
   eingebaute Knopf sichtbar und bedienbar. */
.date-field { position: relative; display: block; }
.date-field input[type="date"] { width: 100%; padding-right: 2.1rem; }
.date-field input[type="date"]::-webkit-calendar-picker-indicator { display: none; }
.date-field input[type="date"]::-webkit-inner-spin-button { display: none; }
.date-field-button {
    position: absolute;
    top: 50%;
    right: 0.35rem;
    transform: translateY(-50%);
    border: 0;
    background: none;
    cursor: pointer;
    font-size: 1rem;
    line-height: 1;
    padding: 0.15rem;
    border-radius: 4px;
}
.date-field-button:hover { background: var(--erp-surface-muted, #eef1f4); }
.date-field-open .date-field-button { background: var(--erp-surface-muted, #eef1f4); }

/* Belegkopf (Angebot/Auftrag): mehrere Karten untereinander statt einer
   langen Spalte. Die Breite bleibt die der bisherigen Einzelkarte -- ein
   Formular, das die ganze Bildschirmbreite nutzt, liest sich schlechter. */
.document-form { max-width: 720px; }
.address-block .form-field select { max-width: 100%; }

.status-badge {
    display: inline-block;
    padding: 0.15rem 0.55rem;
    border-radius: 999px;
    font-size: 0.75rem;
    font-weight: 600;
    border: 1px solid transparent;
}
.status-badge-ok { color: var(--erp-ok); background: #e4f2ee; border-color: var(--erp-ok); }
.status-badge-info { color: var(--erp-info); background: #e7f0fc; border-color: var(--erp-info); }
.status-badge-danger { color: var(--erp-danger); background: #fbe9e8; border-color: var(--erp-danger); }
.status-badge-warn { color: #8a5a00; background: #fdf0d8; border-color: #8a5a00; }
.status-badge-neutral { color: var(--erp-muted); background: #eef1f3; border-color: var(--erp-border); }

/* Statusschalter (Schiebeschalter nach iOS-Vorbild) -- ersetzt in den
   Stammdatenlisten das reine Aktiv/Inaktiv-Badge durch ein Bedienelement.
   Zwei Bauformen mit derselben Optik:

     Knopf    <button class="status-switch" aria-pressed="true|false">
              in einem Mini-POST-Formular; status_switch.js wertet ihn zu
              einem AJAX-Umschalter auf, ohne JavaScript bleibt es ein
              normaler POST (siehe partials/status_switch.html).
     Feld     <input type="checkbox" class="status-switch-check"> + Spur
              als Geschwister -- das `is_active`-Feld im Artikelformular.

   Alle Maße haengen an der `font-size` des Schalters, damit er sich in einem
   Zug skalieren laesst; gezeichnet wird rein mit CSS (Rundungen, Schatten),
   also auf jeder Aufloesung scharf. Die Beschriftung steht IM Schalter:
   beide Woerter sind im Markup, sichtbar ist immer nur das zum Zustand
   passende -- deshalb muss das JavaScript keinen Text umschreiben. */
.status-switch,
.status-switch-field { display: inline-flex; align-items: center; font-size: 0.75rem; }
.status-switch {
    appearance: none; -webkit-appearance: none;
    background: none; border: 0; padding: 0; margin: 0;
    font-family: inherit; cursor: pointer;
}
.status-switch-field { position: relative; cursor: pointer; }
.status-switch-check {
    position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer;
}

.status-switch-track {
    position: relative;
    display: block;
    width: 6.7em;
    height: 2.2em;
    border-radius: 999px;
    background: #d3d9de;
    border: 1px solid var(--erp-border);
    transition: background-color 180ms cubic-bezier(0.32, 0.72, 0, 1),
                border-color 180ms cubic-bezier(0.32, 0.72, 0, 1);
}
.status-switch-thumb {
    position: absolute;
    top: 0.15em; left: 0.15em;
    width: 1.8em; height: 1.8em;
    border-radius: 50%;
    background: #ffffff;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.28), 0 2px 6px rgba(0, 0, 0, 0.12);
    transition: transform 180ms cubic-bezier(0.32, 0.72, 0, 1);
}
.status-switch-text {
    position: absolute;
    top: 0; bottom: 0;
    display: flex; align-items: center;
    font-weight: 600;
    line-height: 1;
    white-space: nowrap;
    pointer-events: none;
}
/* Aus: Schieber links, das Wort steht rechts daneben. */
.status-switch-off { left: 2.3em; right: 0.7em; justify-content: flex-end; color: #3d4a54; }
/* An: Schieber rechts, das Wort steht links daneben. */
.status-switch-on { left: 0.8em; right: 2.3em; justify-content: flex-start; color: #ffffff; opacity: 0; }

.status-switch[aria-pressed="true"] .status-switch-track,
.status-switch-check:checked ~ .status-switch-track {
    background: var(--erp-ok);
    border-color: var(--erp-ok);
}
.status-switch[aria-pressed="true"] .status-switch-thumb,
.status-switch-check:checked ~ .status-switch-track .status-switch-thumb {
    transform: translateX(4.5em);
}
.status-switch[aria-pressed="true"] .status-switch-on,
.status-switch-check:checked ~ .status-switch-track .status-switch-on { opacity: 1; }
.status-switch[aria-pressed="true"] .status-switch-off,
.status-switch-check:checked ~ .status-switch-track .status-switch-off { opacity: 0; }

.status-switch:focus-visible .status-switch-track,
.status-switch-check:focus-visible ~ .status-switch-track {
    outline: 2px solid var(--erp-info);
    outline-offset: 2px;
}
/* Ohne Bearbeitungsrecht bleibt der Zustand ablesbar, aber unbedienbar. */
.status-switch[disabled] { cursor: default; opacity: 0.55; }
/* Waehrend der Server antwortet: der Schalter liegt schon um, ist aber
   erkennbar noch nicht bestaetigt. */
.status-switch.is-busy { opacity: 0.6; cursor: progress; }

@media (prefers-reduced-motion: reduce) {
    .status-switch-track, .status-switch-thumb { transition: none; }
}

.item-block { border: 1px solid var(--erp-border); border-radius: 8px; padding: 0.9rem; margin-bottom: 0.9rem; }
.item-block-header { display: flex; justify-content: space-between; align-items: baseline; gap: 0.75rem; flex-wrap: wrap; }
/* Set-Positionen: sichtbar, aber kein Arbeitsschritt -- der gestrichelte
   Rahmen unterscheidet sie von den Positionen, an denen etwas zu tun ist. */
.item-block-muted { border-style: dashed; background: var(--erp-surface-muted, transparent); }
.scan-progress { font-variant-numeric: tabular-nums; }
.scan-progress-complete { color: var(--erp-ok); font-weight: 600; }

.scan-panel { margin-top: 0.75rem; border-top: 1px dashed var(--erp-border); padding-top: 0.75rem; }
.scan-input-row { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center; }
.scan-input-row input { flex: 1; }
/* Die zu buchende Menge je Griff -- schmal und mit fester Breite, sonst nimmt
   sie dem Scanfeld die Zeile weg. */
.scan-quantity { display: flex; align-items: center; gap: 0.35rem; flex: 0 0 auto; }
.scan-quantity input { width: 4.5rem; flex: 0 0 auto; }
.scanner-viewport { max-width: 360px; margin-top: 0.5rem; }
.scan-feedback { margin-top: 0.4rem; font-size: 0.85rem; min-height: 1.2em; }
.scan-feedback.error { color: var(--erp-danger); }
.scan-feedback.ok { color: var(--erp-ok); }

.unit-list { list-style: none; padding: 0; margin: 0.5rem 0 0; display: flex; flex-wrap: wrap; gap: 0.4rem; }

/* Der Sendungsinhalt in der Sendungstabelle des Lieferscheins -- eine
   Aufzaehlung ohne Punkte, damit sie in der Tabellenzelle nicht wie eine
   zweite Liste neben den Positionen wirkt. */
.plain-list { list-style: none; padding: 0; margin: 0; }
.plain-list li + li { margin-top: 0.2rem; }
.unit-chip {
    display: inline-flex; align-items: center; gap: 0.35rem;
    border: 1px solid var(--erp-border); border-radius: 6px;
    padding: 0.2rem 0.5rem; font-size: 0.8rem; background: #fafcfb;
}
.unit-chip a { color: var(--erp-info); text-decoration: none; }
.unit-chip button { border: none; background: none; color: var(--erp-danger); cursor: pointer; padding: 0; font-size: 0.9rem; }

/* ------------------------------------------------------------------ Stammdaten */

.tabs {
    display: flex;
    gap: 0.4rem;
    border-bottom: 1px solid var(--erp-border);
    margin-bottom: 1.25rem;
    /* Die Leiste scrollt waagerecht, statt die Seite zu schieben -- dieselbe
       Behandlung, die auch die Unterleiste im Kopf bekommt. Ab fuenf
       Reitern (Lagereinstellungen, Intern) und sechs (E-Mail-Verwaltung)
       passt sie am Lager-Tablet sonst nicht mehr neben das Seitenmenu:
       gemessen 215 px Ueberlauf bei 820 px Breite. */
    overflow-x: auto;
}
.tab {
    padding: 0.5rem 0.9rem;
    text-decoration: none;
    color: var(--erp-muted);
    border-bottom: 2px solid transparent;
    font-size: 0.9rem;
    white-space: nowrap;
}
.tab:hover { color: var(--erp-text); }
.tab-active { color: var(--erp-brand); border-bottom-color: var(--erp-brand); font-weight: 600; }


/* Der Kopfbereich hat eine feste Mindestbreite: die Hauptnavigation schrumpft
   nicht unter ihre sechs Links (505 px), Marke und Benutzermenue nicht unter
   110 bzw. 274 px -- zusammen rund 957 px. Darunter schob er die ganze Seite
   waagerecht, am Handy um 567 px. Das trifft den Kamera-Scan im Lager, der
   ausdruecklich am Telefon laufen soll.

   Ab hier bricht der Kopf um: Marke und Benutzermenue teilen sich die erste
   Zeile, die Navigation bekommt die zweite und scrollt dort notfalls
   waagerecht (dieselbe Behandlung wie `.side-tabs` und `.tabs`).
   **Es wird nichts ausgeblendet** -- insbesondere nicht der Benutzername:
   im Lager teilen sich mehrere Leute ein Geraet, und wer gerade angemeldet
   ist, steht als `claimed_by` an Auftraegen und in den Protokollen. */
/* Die Kopfzeile passt in KEINER Breite mehr in eine Zeile: der Container ist
   auf 1100 px gedeckelt, und Marke + Benutzermenue + Arbeitsplatzwaehler
   brauchen davon bereits rund 1040 -- die Bereichsleiste ist seit
   2026-09-01 zehn Eintraege lang. Sie rueckt deshalb dauerhaft eine Zeile
   tiefer; dieselben Regeln wie in der 1000-px-Abfrage unten, nur ohne
   Breitenbedingung. Gesetzt wird die Klasse in `base.html` fuer jeden
   angemeldeten Benutzer. */
.site-header-stacked .container {
    flex-wrap: wrap;
    gap: 0.75rem 1rem;
}
.site-header-stacked .brand { order: 1; }
.site-header-stacked .user-menu {
    order: 2;
    margin-left: auto;
    flex-wrap: wrap;
    justify-content: flex-end;
}
.site-header-stacked .main-nav {
    order: 3;
    flex: 1 0 100%;
    overflow-x: auto;
}
.site-header-stacked .main-nav a { white-space: nowrap; }

@media (max-width: 1000px) {
    .site-header .container {
        flex-wrap: wrap;
        gap: 0.75rem 1rem;
    }
    .brand { order: 1; }
    .user-menu {
        order: 2;
        margin-left: auto;
        /* Notnagel fuer sehr schmale Geraete: lieber zwei Zeilen als ein
           abgeschnittener Abmelden-Knopf. */
        flex-wrap: wrap;
        justify-content: flex-end;
    }
    .main-nav {
        order: 3;
        flex: 1 0 100%;
        overflow-x: auto;
    }
    .main-nav a { white-space: nowrap; }
}

/* Am Telefon zaehlt jede Zeile: der Kamera-Scan braucht den Bildschirm.
   Zwei Kuerzungen, die nichts kosten -- der Markentext neben dem Logo ist
   Zierde (das "ERP"-Zeichen bleibt), und die beiden Kopfknoepfe brauchen
   nicht die volle Innenabstands-Groesse. Der Benutzername bleibt, siehe
   oben. Zusammen faellt der Kopf von drei Zeilen auf zwei. */
@media (max-width: 560px) {
    .brand-text { display: none; }
    .user-menu { gap: 0.4rem; }
    .user-menu .btn { padding: 0.3rem 0.6rem; font-size: 0.85rem; }
    /* Der Arbeitsplatz wird am Telefon seltener gewechselt als am Packtisch,
       der Name darf hier also schrumpfen. Ausgeblendet wird er NICHT: wer
       unterwegs etwas druckt, muss sehen, wo es herauskommt -- dieselbe
       Ueberlegung wie beim Benutzernamen. */
    .workstation-picker select { max-width: 7rem; font-size: 0.8rem; }
}


.search-bar { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
.search-bar input[type="text"] {
    flex: 1;
    padding: 0.45rem 0.6rem;
    border: 1px solid var(--erp-border);
    border-radius: 6px;
    font: inherit;
}

.detail-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 0.9rem; }
.detail-field label { display: block; font-size: 0.8rem; color: var(--erp-muted); font-weight: 600; }
.detail-field .value { font-size: 0.95rem; }

/* Die Artikel-Detailseite zeigt dasselbe Raster im Lese- und im
   Bearbeitungsmodus (masterdata/_product_grid.html). Damit beim Klick auf
   "Bearbeiten" nichts springt, traegt ein `.form-field` im `.detail-grid`
   dieselbe Beschriftungsanmutung wie ein `.detail-field` -- ohne das waere
   die Beschriftung im Bearbeitungsmodus groesser und dunkler
   (`.form-field label`: 0.85rem, Textfarbe). */
/* Bewusst KEIN `flex-direction: column`: das aufklappbare "i" (`.field-info`)
   steht im Markup direkt hinter der Beschriftung und wuerde als eigener
   Flex-Punkt in eine eigene Zeile rutschen -- die Zelle waere dann eine
   Zeile hoeher als ihre Nachbarn und das Raster geriete aus dem Tritt. */
.detail-grid .form-field { display: block; }
.detail-grid .form-field label {
    display: inline-block; margin-bottom: 0.15rem;
    font-size: 0.8rem; color: var(--erp-muted); font-weight: 600;
}
.detail-grid .form-field input[type="text"],
.detail-grid .form-field input[type="number"],
.detail-grid .form-field input[type="date"],
.detail-grid .form-field select,
.detail-grid .form-field textarea { display: block; width: 100%; max-width: 100%; }
/* Kontrollkaestchen behalten ihre natuerliche Breite und stehen neben der
   Beschriftung, nicht darunter. */
/* Kontrollkaestchen stehen UNTER ihrer Beschriftung, nicht daneben: die
   Beschriftungen sind hier unterschiedlich lang ("Aktiv" bis "Darf mit
   anderen Kleinteilen in ein Paket"), und daneben landete das Kaestchen mal
   in derselben und mal in der naechsten Zeile. */
.detail-grid .form-field input[type="checkbox"]:not(.status-switch-check) {
    display: block; width: auto; margin: 0.2rem 0 0;
}
/* Die drei Maß-Eingaben tragen ihre Beschriftung als Platzhalter. */
.detail-field-split input { width: 100%; min-width: 0; }
/* Dieselbe Ursache im Lesezustand: `.detail-field label` ist global ein
   Block. Eine Zelle mit "i" markiert sich, statt die globale Regel fuer
   jede Detailseite im ERP umzustellen. */
.detail-field-inline-label > label { display: inline-block; }
/* Eine Zelle, die mehrere Eingaben nebeneinander fasst (Maße L/B/H). */
.detail-field-split { display: flex; gap: 0.4rem; }
.detail-field-split > * { flex: 1; min-width: 0; }
/* Beschreibung und Notizen brauchen die ganze Breite des Rasters. */
.detail-field-wide { grid-column: 1 / -1; }

/* Die Artikelseite in Abschnittskarten (masterdata/_product_grid.html, seit
   07.10.2026): je Abschnitt eine Karte mit Symbol und Titel im Kopf, darunter
   das gewohnte `.detail-grid`. Ansehen und Bearbeiten teilen sich dieselben
   Karten, nur die Zellen schalten um. */
.product-section { padding: 0; }
.product-section-head {
    display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem;
    padding: 0.8rem 1.25rem; border-bottom: 1px solid var(--erp-border);
}
.product-section-head h2 { margin: 0; font-size: 1rem; }
.product-section-icon {
    width: 1.3rem; height: 1.3rem; flex: none; fill: none; stroke: var(--erp-brand);
    stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
}
.product-section-aside { margin-left: auto; }
/* Der Aktiv-Schalter im Kopf der ersten Karte: Beschriftung neben dem Schalter,
   nicht darueber -- der Kopf soll nicht hoeher werden als die uebrigen. */
.product-section-aside .form-field { display: flex; flex-direction: row; align-items: center; gap: 0.6rem; margin: 0; }
.product-section-aside .form-field label { margin: 0; font-size: 0.8rem; color: var(--erp-muted); font-weight: 600; }
.product-section > .detail-grid { padding: 1.1rem 1.25rem 1.25rem; }
/* Ja/Nein-Merkmale stehen in einer eigenen, durchgehenden Zeile. Im Bearbeiten
   steht das Kontrollkaestchen VOR seiner Beschriftung (`order`), nicht darunter. */
.detail-checks {
    grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 0.6rem 2rem;
    padding-top: 0.35rem; border-top: 1px dashed var(--erp-border);
}
.detail-checks .form-field { display: flex; flex-direction: row; align-items: center; gap: 0.45rem; margin: 0; }
.detail-checks .form-field label { margin: 0; font-size: 0.85rem; color: var(--erp-text); font-weight: 500; }
.product-section .detail-checks .form-field input[type="checkbox"]:not(.status-switch-check) {
    order: -1; margin: 0;
}
.detail-check { display: inline-flex; align-items: baseline; gap: 0.45rem; font-size: 0.85rem; }
.detail-check-label { color: var(--erp-muted); }
/* Zollknoepfe und ihre Ergebnisse spannen die ganze Zeile des Rasters. */
.product-tariff-actions { grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
.detail-grid > [data-tariff-lookup-results],
.detail-grid > [data-tariff-suggest-results] { grid-column: 1 / -1; }
.source-badge {
    display: inline-block; padding: 0.1rem 0.5rem; border-radius: 999px;
    font-size: 0.7rem; font-weight: 600; border: 1px solid var(--erp-border); color: var(--erp-muted);
}


/* Dashboard-Kennzahlen (Phase 20) */
.widget-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    margin: 1rem 0 1.5rem;
}
.widget {
    flex: 1 1 160px;
    min-width: 160px;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    padding: 0.75rem 0.9rem;
    border: 1px solid rgba(0, 0, 0, 0.12);
    border-radius: 8px;
    text-decoration: none;
    color: inherit;
    background: #fff;
}
.widget:hover { border-color: rgba(0, 0, 0, 0.3); }
.widget-value { font-size: 1.6rem; font-weight: 700; line-height: 1.1; }
.widget-label { font-size: 0.85rem; opacity: 0.75; }
.widget-hint { font-size: 0.78rem; opacity: 0.6; }

/* Startseite: Sendungen mit Zustellproblem oder Ruecksendung, oberhalb der
   Kennzahlen. */
.delivery-issue-panel { border-left: 4px solid var(--erp-danger); }
.delivery-issue-panel h2 { margin-top: 0; color: var(--erp-danger); font-size: 1.05rem; }
.delivery-issue-list { margin: 0; padding: 0; list-style: none; }
.delivery-issue-list li { padding: 0.45rem 0; border-top: 1px solid var(--erp-border); }
.delivery-issue-list li:first-child { border-top: 0; padding-top: 0; }
.delivery-issue-text { color: var(--erp-danger); font-size: 0.9rem; }
.delivery-issue-links { display: flex; flex-wrap: wrap; gap: 0.25rem 1.25rem; margin: 0.6rem 0 0; }

/* ------------------------------------------------------- PDF-Vorlagen-Editor */

/* Breiterer Seitenrahmen nur fuer die Editor-Seiten (main_class-Block in
   base.html) -- der globale .container bleibt bei 960px fuer den Rest der
   App, ein Code-Editor mit Live-Vorschau braucht aber sichtbar mehr Platz. */
.pdf-editor-main {
    max-width: 1800px;
    margin: 0 auto;
    padding: 0 1.25rem;
}

#custom_body_html, #custom_html {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.85rem;
    min-height: 65vh;
    resize: vertical;
}

.pdf-editor-split {
    display: flex;
    gap: 1rem;
    align-items: stretch;
    margin-bottom: 0.9rem;
}
.pdf-editor-split .form-field { flex: 1 1 50%; min-width: 0; margin-bottom: 0; }

.pdf-preview-pane {
    flex: 1 1 50%;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}
.pdf-preview-pane label { font-weight: 600; font-size: 0.85rem; }
.pdf-preview-frame {
    flex: 1;
    min-height: 65vh;
    border: 1px solid var(--erp-border);
    border-radius: 6px;
    background: #fff;
}
.pdf-preview-error {
    color: var(--erp-danger);
    background: #fbe9e8;
    border: 1px solid var(--erp-danger);
    border-radius: 6px;
    padding: 0.5rem 0.7rem;
    font-size: 0.85rem;
}

/* Textbausteine (Ueberschrift/Texte/Farbe) sind seltener als die
   HTML-Bearbeitung selbst -- eingeklappt lassen sie mehr Hoehe fuer den
   Live-Viewer. Bei einem Validierungsfehler in einem der Felder oeffnet das
   Template den Block per `open`-Attribut, sonst waeren Fehler unsichtbar. */
.pdf-editor-fields { margin-bottom: 0.9rem; border: 1px solid var(--erp-border); border-radius: 8px; }
.pdf-editor-fields summary {
    cursor: pointer;
    padding: 0.6rem 0.9rem;
    font-weight: 600;
    font-size: 0.9rem;
    color: var(--erp-muted);
}
.pdf-editor-fields[open] summary { border-bottom: 1px solid var(--erp-border); }
.pdf-editor-fields .pdf-editor-fields-body { padding: 0.9rem; }

/* ------------------------------------------- Einheitlicher Tabellenstandard
   Gehoert zu `tables.py`, `templates/partials/table.html` und
   `static/js/data_table.js`. Die Klassen tragen alle das Praefix `dt-`, damit
   sie sich nicht mit den gewachsenen Listenklassen einzelner Module beissen. */

.dt-bar {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: center;
    margin-bottom: 0.75rem;
}
/* Die Suche steht links und bleibt schmal -- sie soll die Zeile nicht
   fuellen, rechts daneben steht der Zeitraum. */
.dt-search { display: flex; gap: 0.4rem; flex: 0 1 22rem; min-width: 12rem; }
.dt-search input[type="search"] {
    flex: 1;
    min-width: 0;
    padding: 0.45rem 0.6rem;
    border: 1px solid var(--erp-border);
    border-radius: 6px;
    font: inherit;
}
.dt-bar-actions { display: flex; flex-wrap: wrap; gap: 0.4rem; }
/* Das Suchformular ist fuers Layout unsichtbar: seine Suche ordnet sich in
   die Zeile von `.dt-bar` ein wie die Knoepfe und der Zeitraum daneben. */
.dt-bar-form { display: contents; }

/* Zeitraumfilter, rechts ueber der Tabelle. Wie in der LRC-Warenwirtschaft:
   das Wertefeld (Jahr/Monat/Tag) steht als eigene Zeile ueber dem
   Auswahlfeld, deshalb eine Spalte. `margin-left: auto` zieht den Block an
   den rechten Rand -- er ist das letzte Kind der Leiste. */
.dt-dates {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    min-width: 11rem;
    margin-left: auto;
}
.dt-dates label {
    display: block;
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--erp-muted);
}
.dt-dates input, .dt-dates select {
    width: 100%;
    padding: 0.35rem 0.5rem;
    border: 1px solid var(--erp-border);
    border-radius: 6px;
    background: var(--erp-surface);
    font: inherit;
    font-size: 0.85rem;
}

/* Anzeige der aktiven Filter. Jede Marke ist ein Link, der genau diesen
   einen Filter wieder entfernt -- Filter loeschen, ohne die Spalte zu suchen. */
.dt-chips {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.35rem;
    margin-bottom: 0.75rem;
    font-size: 0.82rem;
}
.dt-chips-label { color: var(--erp-muted); font-weight: 600; }
.dt-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.15rem 0.55rem;
    border: 1px solid var(--erp-brand);
    border-radius: 999px;
    background: #eef4f2;
    color: var(--erp-brand);
    text-decoration: none;
    white-space: nowrap;
}
.dt-chip:hover { background: var(--erp-brand); color: var(--erp-brand-contrast); }
.dt-chip-clear { border-color: var(--erp-border); background: transparent; color: var(--erp-muted); }
.dt-chip-clear:hover { background: var(--erp-muted); color: #fff; }

/* Sortierbare Ueberschrift. Nicht sortierbare Spalten (Aktionen, Bilder,
   Auswahlkaestchen) bekommen bewusst weder Link noch Zeiger. */
.dt-table th.dt-th { vertical-align: bottom; white-space: nowrap; }
.dt-sort {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    color: inherit;
    text-decoration: none;
}
.dt-sort:hover { color: var(--erp-brand); }
.dt-sort-mark { font-size: 0.7em; opacity: 0.35; }
.dt-sorted .dt-sort-mark { opacity: 1; color: var(--erp-brand); }
.dt-sorted { color: var(--erp-text); }
.dt-align-right { text-align: right; }
.dt-align-right .dt-sort { flex-direction: row-reverse; }

/* Fusszeile mit Trefferzahl und Summen ueber alle Treffer. */
.dt-table tfoot .dt-total-row td {
    font-weight: 600;
    border-top: 2px solid var(--erp-border);
    border-bottom: 0;
    white-space: nowrap;
}

/* Filterzeile direkt unter den Ueberschriften. */
.dt-filter-row td {
    padding: 0.3rem 0.6rem 0.5rem;
    border-bottom: 2px solid var(--erp-border);
    vertical-align: top;
}
.dt-filter-cell { min-width: 0; }
.dt-range input {
    width: 100%;
    min-width: 5.5rem;
    padding: 0.25rem 0.4rem;
    border: 1px solid var(--erp-border);
    border-radius: 5px;
    font: inherit;
    font-size: 0.8rem;
    background: var(--erp-surface);
}
.dt-range { display: flex; gap: 0.25rem; }

.dt-filter { position: relative; }
.dt-filter-summary {
    display: inline-flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.3rem;
    width: 100%;
    min-width: 5.5rem;
    padding: 0.25rem 0.4rem;
    border: 1px solid var(--erp-border);
    border-radius: 5px;
    background: var(--erp-surface);
    font-size: 0.8rem;
    font-weight: 400;
    color: var(--erp-muted);
    cursor: pointer;
    list-style: none;
}
.dt-filter-summary::-webkit-details-marker { display: none; }
.dt-filter-summary:hover { border-color: var(--erp-brand); }
.dt-filter-active { border-color: var(--erp-brand); color: var(--erp-brand); font-weight: 600; }
.dt-caret { font-size: 0.7em; }
.dt-filter-panel {
    position: absolute;
    z-index: 30;
    top: calc(100% + 2px);
    left: 0;
    min-width: 15rem;
    max-width: 22rem;
    padding: 0.5rem;
    background: var(--erp-surface);
    border: 1px solid var(--erp-border);
    border-radius: 6px;
    box-shadow: 0 6px 18px rgba(31, 41, 51, 0.14);
}
.dt-align-right .dt-filter-panel,
/* Die letzte Spalte (meist „Status") klappt nach links auf: nach rechts ragte
   das Menue aus `.table-scroll` und wurde samt „Übernehmen" abgeschnitten. */
.dt-filter-cell:last-child .dt-filter-panel { left: auto; right: 0; }
.dt-value-search {
    width: 100%;
    padding: 0.3rem 0.4rem;
    margin-bottom: 0.4rem;
    border: 1px solid var(--erp-border);
    border-radius: 5px;
    font: inherit;
    font-size: 0.8rem;
}
/* Gedeckelt, damit eine Liste mit zweihundert Werten das Aufklappmenue nicht
   ueber den Bildschirmrand hinaus wachsen laesst. */
.dt-options { max-height: 15rem; overflow-y: auto; }
.dt-option {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.15rem 0.1rem;
    font-size: 0.82rem;
    font-weight: 400;
    cursor: pointer;
}
.dt-option:hover { background: #f2f6f5; }
.dt-option input { margin: 0; }
.dt-no-options { margin: 0.2rem 0; font-size: 0.8rem; color: var(--erp-muted); }
.dt-filter-actions { margin-top: 0.45rem; text-align: right; }

/* Blaetterleiste. */
.dt-pagination {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.75rem;
    margin-top: 0.9rem;
    padding-top: 0.7rem;
    border-top: 1px solid var(--erp-border);
    font-size: 0.82rem;
    color: var(--erp-muted);
}
.dt-count-total { opacity: 0.75; }
.dt-pages, .dt-page-size { display: flex; align-items: center; gap: 0.2rem; margin-left: auto; }
.dt-page-size { margin-left: 0; }
.dt-page {
    display: inline-block;
    min-width: 1.85rem;
    padding: 0.2rem 0.45rem;
    border: 1px solid var(--erp-border);
    border-radius: 5px;
    color: var(--erp-text);
    text-align: center;
    text-decoration: none;
}
.dt-page:hover { border-color: var(--erp-brand); color: var(--erp-brand); }
.dt-page-current { background: var(--erp-brand); border-color: var(--erp-brand); color: var(--erp-brand-contrast); }
.dt-page-current:hover { color: var(--erp-brand-contrast); }
.dt-page-disabled { opacity: 0.4; pointer-events: none; }
.dt-page-gap { border: 0; min-width: 1rem; }

.empty-hint { color: var(--erp-muted); font-size: 0.85rem; }

/* Schmale Bildschirme: aus jeder Zeile wird eine Karte mit Beschriftungen.
   Die Beschriftungen setzt `data_table.js` aus den Spaltentiteln -- kein
   Template muss sie ein zweites Mal fuehren. Die Filterzeile verschwindet
   dabei; gefiltert wird ueber die Suche und die Marken, die bleiben. */
@media (max-width: 720px) {
    .dt-table, .dt-table thead, .dt-table tbody, .dt-table tfoot, .dt-table tr, .dt-table td { display: block; width: 100%; }
    .dt-table tfoot .dt-total-row td { border-top: 0; }
    .dt-table thead tr:first-child { display: none; }
    /* Die Filterzeile bleibt erreichbar -- sie wandert nur in einen
       gestapelten Block ueber die Liste. Sie wegzublenden hiesse, dass am
       Lager-Tablet und am Telefon gar nicht gefiltert werden kann; genau dort
       stehen die Leute, die eine Sendung suchen. */
    .dt-table thead .dt-filter-row {
        border: 1px solid var(--erp-border);
        border-radius: 8px;
        margin-bottom: 0.9rem;
        padding: 0.35rem 0.5rem;
        background: #fbfcfc;
    }
    .dt-filter-row td {
        display: flex;
        align-items: center;
        gap: 0.75rem;
        padding: 0.3rem 0;
        border-bottom: 0;
    }
    .dt-filter-row td::before {
        content: attr(data-label);
        color: var(--erp-muted);
        font-weight: 600;
        font-size: 0.78rem;
        flex: 0 0 34%;
    }
    /* Die Bedienelemente teilen sich den Rest. Ohne `min-width: 0` behalten
       zwei nebeneinanderliegende Felder (ab/bis) ihre Wunschbreite, und das
       zweite steht ausserhalb des sichtbaren Bereichs -- am Telefon waere die
       obere Grenze eines Betrags schlicht nicht auffindbar. */
    .dt-filter-row td > * { flex: 1 1 auto; min-width: 0; }
    .dt-filter-row .dt-range input { flex: 1 1 0; min-width: 0; }
    /* Spalten ohne Filter (Aktionen, reine Anzeige) tragen eine leere Zelle --
       sie als beschriftete Leerzeile mitzustapeln waere nur Hoehe ohne Inhalt.
       Die Zelle traegt dafuer eine eigene Klasse: `td:not(:has(*))` traf zwar
       zu (`element.matches()` bestaetigt es), wurde von Chromium beim Setzen
       des Stils aber ignoriert -- solange die Spalten noch Text- und
       Datumsfilter trugen, fiel das niemandem auf, weil es keine leeren
       Zellen gab. */
    .dt-filter-row td.dt-filter-empty { display: none; }
    .dt-filter-row .dt-filter-panel { position: static; max-width: none; box-shadow: none; }
    .dt-table tr {
        border: 1px solid var(--erp-border);
        border-radius: 8px;
        margin-bottom: 0.6rem;
        padding: 0.35rem 0.5rem;
    }
    .dt-table td {
        display: flex;
        justify-content: space-between;
        gap: 1rem;
        border-bottom: 0;
        padding: 0.28rem 0;
        text-align: left;
    }
    .dt-table td::before {
        content: attr(data-label);
        color: var(--erp-muted);
        font-weight: 600;
        font-size: 0.78rem;
        flex: 0 0 42%;
    }
    .dt-table td:empty { display: none; }
    .dt-table tr:hover td { background: transparent; }
    .dt-pages, .dt-page-size { margin-left: 0; }
    /* Suche und Zeitraum stehen untereinander statt nebeneinander -- bei
       11 rem Mindestbreite je Block passte sonst keiner von beiden. */
    .dt-search { flex: 1 1 100%; }
    .dt-dates { width: 100%; margin-left: 0; }
}

/* Eine Zeile mit Befund (z. B. eine E-Rechnung, die nicht ausgewertet werden
   konnte) hebt sich ab, ohne die Zeilenfarbe der Tabelle zu ueberschreiben. */
.data-table tr.dt-row-warn td { background: #fffaf0; }
.data-table tr.dt-row-note td { border-top: 0; padding-top: 0; }

/* ------------------------------------------------ Livesuche (Zuordnungsfelder)
   Markup: lookup/fields.py::LookupWidget, Verhalten: static/js/lookup.js.
   Der Chip ist die Antwort auf "was ist hier eigentlich zugeordnet?" -- er
   traegt Bezeichnung UND Identifikatoren, weil eine Kundennummer ohne Namen
   nichts sagt und ein Firmenname ohne Nummer nicht eindeutig ist. */
.lookup { position: relative; display: block; }

.lookup input[type="search"].lookup-input {
    width: 100%;
    padding: 0.45rem 0.6rem;
    border: 1px solid var(--erp-border);
    border-radius: 6px;
    font: inherit;
}
.lookup input[type="search"].lookup-input:focus {
    outline: 2px solid var(--erp-brand);
    outline-offset: -1px;
}

.lookup-selected:empty { display: none; }

.lookup-chip {
    display: inline-flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.45rem;
    max-width: 100%;
    padding: 0.35rem 0.55rem;
    border: 1px solid var(--erp-border);
    border-left: 3px solid var(--erp-brand);
    border-radius: 6px;
    background: var(--erp-surface);
}
.lookup-chip-thumb {
    width: 1.6rem;
    height: 1.6rem;
    object-fit: contain;
    background: #fff;
    border: 1px solid var(--erp-border);
    border-radius: 3px;
    flex: 0 0 auto;
}
.lookup-chip-label { font-weight: 600; }
.lookup-chip-meta { color: var(--erp-muted); font-size: 0.82rem; }
.lookup-chip-flag {
    font-size: 0.75rem;
    color: var(--erp-danger);
    border: 1px solid var(--erp-danger);
    border-radius: 4px;
    padding: 0 0.3rem;
}
.lookup-chip-inactive { border-left-color: var(--erp-danger); }

.lookup-chip-change, .lookup-chip-clear {
    margin-left: auto;
    background: none;
    border: 1px solid transparent;
    border-radius: 4px;
    color: var(--erp-muted);
    font: inherit;
    font-size: 0.8rem;
    cursor: pointer;
    padding: 0 0.35rem;
}
.lookup-chip-clear { margin-left: 0; font-size: 1rem; line-height: 1; }
.lookup-chip-change:hover, .lookup-chip-clear:hover {
    color: var(--erp-text);
    border-color: var(--erp-border);
}

.lookup-results {
    position: absolute;
    z-index: 30;
    left: 0;
    right: 0;
    margin: 0.2rem 0 0;
    padding: 0;
    list-style: none;
    max-height: 19rem;
    overflow-y: auto;
    background: var(--erp-surface);
    border: 1px solid var(--erp-border);
    border-radius: 6px;
    box-shadow: 0 6px 18px rgba(31, 41, 51, 0.12);
}

.lookup-result {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    padding: 0.4rem 0.6rem;
    cursor: pointer;
    border-bottom: 1px solid var(--erp-border);
}
/* Mit Bild wird aus der Zeile ein Zweispalter: Miniatur links, die Texte
   rechts daneben untereinander. Die Bildspalte bekommt JEDE Zeile der Liste,
   sobald eine ein Bild hat (`lookup.js`) -- sonst begaenne jede zweite
   Bezeichnung an einer anderen Stelle. */
.lookup-result-with-image { flex-direction: row; align-items: center; gap: 0.55rem; }
.lookup-result-body { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
.lookup-result-thumb {
    flex: 0 0 auto;
    width: 2.75rem;
    height: 2.75rem;
    /* `contain`, nicht `cover`: ein Ramenkocher, dem der Rand abgeschnitten
       wird, ist als Wiedererkennung weniger wert als einer mit Luft. */
    object-fit: contain;
    background: #fff;
    border: 1px solid var(--erp-border);
    border-radius: 4px;
}
/* Der leere Platzhalter traegt kein Bild, aber denselben Raum. */
.lookup-result-thumb.is-empty { background: #f4f6f5; }
.lookup-result:last-child { border-bottom: none; }
.lookup-result.is-active { background: #eef4f2; }
.lookup-result-label { font-weight: 600; }
.lookup-result-meta { color: var(--erp-muted); font-size: 0.8rem; }
.lookup-result-reason { color: var(--erp-brand); font-size: 0.78rem; }
.lookup-result-inactive .lookup-result-label { color: var(--erp-muted); }

.lookup-status {
    margin: 0.25rem 0 0;
    font-size: 0.8rem;
    color: var(--erp-muted);
}
.lookup-status-error { color: var(--erp-danger); }
.lookup-status-empty { color: var(--erp-text); }

/* ------------------------------------- Positionstabelle der Belegseite
   Die Artikelspalte traegt Livesuche UND Beschreibung, deshalb bekommt sie
   den Platz, den bis zum 16.09.2026 eine eigene Spalte „Beschreibung" quer
   durch die Tabelle belegte.

   Der Chip darin bleibt EINZEILIG: `margin-left:auto` am „ändern" schiebt
   den Knopf sonst an den rechten Zellenrand und zieht den Chip ueber die
   ganze Breite auseinander -- die Zuordnung sah dadurch doppelt so gross
   aus, wie sie ist.

   Die Tabelle rechnet mit `table-layout: fixed`: alle Spalten ausser der
   Artikelspalte haben eine feste Breite, die Artikelspalte bekommt den Rest.
   Dadurch passt die Tabelle IMMER in ihre Karte, statt ueber die Summe fester
   Eingabefeldbreiten (5rem/7rem/5rem) plus 22rem Mindestbreite hinauszuwachsen
   und eine waagerechte Scrollleiste zu erzwingen. `.table-scroll` bleibt
   trotzdem drumherum -- am schmalen Lagergeraet greift die Mindestbreite
   unten wieder. */
#items-table { table-layout: fixed; }
#items-table th, #items-table td { padding: 0.4rem 0.35rem; }
#items-table .item-article { width: auto; }
#items-table .col-move { width: 2.6rem; }
#items-table .col-qty { width: 5rem; }
#items-table .col-unit { width: 7rem; }
#items-table .item-name { width: 100%; margin-top: .25rem; }
#items-table .col-price { width: 6.2rem; }
#items-table .col-discount { width: 4.6rem; }
#items-table .col-vat { width: 5.6rem; }
#items-table .col-net { width: 6rem; }
/* 7rem, nicht 6: im vorgemerkten Zustand heisst der Knopf „Zurückholen" und
   ist damit breiter als „Löschen" -- bei 6rem trat die Tabelle um 1px ueber
   ihre Karte hinaus, also genau wieder mit Scrollleiste. */
#items-table .col-del { width: 7rem; text-align: center; }
#items-table .col-del .btn { white-space: nowrap; }
#items-table .col-move .btn { display: block; width: 100%; padding: 0 0.2rem; }
/* Die Eingabefelder fuellen ihre Spalte, statt eine eigene Breite mitzubringen
   -- eine feste rem-Breite im Feld haette die Spaltenbreite wieder ueberholt. */
#items-table input[type="number"],
#items-table input[type="text"],
#items-table input[type="search"] {
    width: 100%;
    max-width: 100%;
    box-sizing: border-box;
}
/* Unterhalb von 1250px bleibt fuer die Artikelspalte nicht mehr genug Rest --
   die feste Aufteilung staucht sie dann zusammen, statt zu scrollen (der Chip
   fiel dabei buchstabenweise auseinander). Dort geht die Tabelle zurueck auf
   die natuerliche Aufteilung mit Mindestbreite, und `.table-scroll` uebernimmt
   wieder. */
@media (max-width: 1250px) {
    #items-table { table-layout: auto; }
    #items-table .item-article { min-width: 20rem; }
}
/* Oberhalb davon passt die Tabelle in ihre Karte -- dann darf der
   Scrollbereich aufhoeren, einer zu sein. Er kappt sonst die aufgeklappte
   Artikelsuche der letzten Zeile: ein Scrollbereich schneidet auch nach UNTEN
   ab (`overflow-x: auto` macht die Y-Achse ebenfalls zu `auto`), und von acht
   Treffern blieb der erste sichtbar. Unterhalb von 1250px bleibt es beim
   Scrollbereich -- dort wird er gebraucht. */
@media (min-width: 1251px) {
    .items-table-scroll { overflow: visible; }
}
/* Eine zum Loeschen vorgemerkte Zeile bleibt STEHEN, bis gespeichert wird --
   entfernt wird sie serverseitig ueber `delete` (`apply_document_items()`).
   Sie muss deshalb als vorgemerkt erkennbar sein und zurueckholbar bleiben;
   die Felder bleiben bedienbar und werden nicht `disabled`, sonst faenden
   `item_id` und `delete` gar nicht mehr den Weg in den POST. */
#items-table tr.is-deleted { opacity: 0.6; }
#items-table tr.is-deleted .lookup-chip-label,
#items-table tr.is-deleted [data-role="line-net"] { text-decoration: line-through; }

#items-table .lookup-chip {
    flex-wrap: nowrap;
    align-items: center;
    gap: 0.3rem;
    padding: 0.2rem 0.45rem;
}
#items-table .lookup-chip-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
#items-table .lookup-chip-meta { white-space: nowrap; }
#items-table .lookup-chip-change { margin-left: 0.35rem; white-space: nowrap; }
.item-description {
    width: 100%;
    margin-top: 0.3rem;
    font-size: 0.85rem;
}

/* Bestandteil eines Sets -- die Zeile rueckt ein und bekommt einen Strich
   an der linken Kante, im Lese- wie im Bearbeitungszustand der
   Positionstabelle. Kein eigener Tabellenblock: es ist eine gewoehnliche
   Warenposition, nur ihr Preis steckt in der Zeile darueber. Die
   Einrueckung sitzt am Zellinhalt, nicht an der Zelle -- feste
   Spaltenbreiten bleiben damit unangetastet. */
tr.item-child > td:first-child { box-shadow: inset 3px 0 0 0 var(--border, #ddd); }
tr.item-child > td.item-article { padding-left: 1.25rem; }
tr.item-child > td:first-child:not(.col-move) { padding-left: 1.25rem; }

/* Artikelbild auf der Artikelseite -- Vorschau links, Upload daneben.
   `contain` und ein heller Grund: ein freigestelltes PNG soll nicht auf der
   Kartenfarbe schwimmen, und ein Hochformat nicht beschnitten werden. */
/* Miniatur in der Artikelliste. Feste Kantenlaenge, damit die Zeilenhoehe
   der Liste nicht von den Seitenverhaeltnissen der Bilder abhaengt; die
   leere Huelle haelt die Spalte auch ohne Bild auf Breite. */
.product-thumb-cell { width: 3.5rem; }
.product-thumb {
    display: block;
    width: 2.5rem;
    height: 2.5rem;
    object-fit: contain;
    background: #fff;
    border: 1px solid var(--erp-border);
    border-radius: 4px;
}
.product-thumb.is-empty { background: #f4f6f5; }

.product-image-row { display: flex; flex-wrap: wrap; gap: 1rem; align-items: flex-start; }
.product-image-preview {
    width: 13.75rem;
    height: 13.75rem;
    object-fit: contain;
    background: #fff;
    border: 1px solid var(--erp-border);
    border-radius: 6px;
}
.product-image-preview.is-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    background: #f4f6f5;
    color: var(--erp-muted);
    font-size: 0.85rem;
}
.product-image-actions { display: flex; flex-direction: column; gap: 0.75rem; }
.product-image-actions input[type="file"] { display: block; margin-bottom: 0.5rem; font-size: 0.85rem; }

/* Historie einer Belegseite -- zugeklappt, siehe partials/history.html.
   Die <h2> steht IM <summary>, damit die Karte im zugeklappten Zustand
   genau eine Zeile hoch ist und die Ueberschrift bleibt, was sie war. */
.history-panel > summary { cursor: pointer; }
.history-panel > summary > h2 { display: inline; margin: 0; font-size: 1.05rem; }
.history-panel > summary:hover > h2 { color: var(--erp-brand); }
.history-panel > h3:first-of-type { margin-top: 1rem; }

/* Zuordnung von Hand im Zahlungsabgleich -- zugeklappt, damit sie den Blick
   nicht von den Vorschlägen zieht, aber immer da. */
.manual-assign { margin: 0.5rem 0; }
.manual-assign > summary { cursor: pointer; font-size: 0.85rem; color: var(--erp-muted); }
.manual-assign > summary:hover { color: var(--erp-text); }
.manual-assign .form-row { margin-top: 0.5rem; align-items: flex-end; }

/* Betrag einer Tabellenzeile direkt bearbeiten (Versandposition am Beleg).
   Nur dort sinnvoll, wo genau EIN Wert geaendert wird -- fuer mehr Felder
   ist das eigene Formular unter der Tabelle der richtige Ort. */
.inline-form { display: flex; gap: 0.35rem; align-items: center; margin: 0; }
.inline-form input[type="number"] { padding: 0.2rem 0.4rem; font-size: 0.85rem; }

/* Erklaerung hinter einem hochgestellten "i" (partials/field.html::info).
   Zugeklappt ist es ein kleines rundes i neben der Ueberschrift, aufgeklappt
   steht der Text darunter -- ohne JavaScript, die CSP laesst keins zu. */
.field-info { display: inline-block; vertical-align: super; margin-left: 0.3rem; }
.field-info > summary {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.05rem;
    height: 1.05rem;
    border: 1px solid var(--erp-border);
    border-radius: 50%;
    background: var(--erp-surface);
    font-size: 0.7rem;
    font-style: italic;
    font-weight: 600;
    line-height: 1;
    color: var(--erp-muted);
    cursor: help;
    list-style: none;
}
.field-info > summary::-webkit-details-marker { display: none; }
.field-info > summary:hover { border-color: var(--erp-brand); color: var(--erp-brand); }
.field-info[open] > summary { border-color: var(--erp-brand); color: var(--erp-brand); }
.field-info > p {
    margin: 0.4rem 0 0;
    font-size: 0.85rem;
    font-style: normal;
    font-weight: 400;
    line-height: 1.45;
    color: var(--erp-muted);
    text-align: left;
    vertical-align: baseline;
}
.field-info[open] { display: block; vertical-align: baseline; margin-left: 0; }

/* Adressblock im Belegkopf UND die Lieferanschrift im Kundenformular: die
   Felder stehen zugeklappt hinter einer Zusammenfassung. Dasselbe Mittel wie
   `.manual-assign` im Zahlungsabgleich -- natives <details>, kein
   Accordion-Framework. Die Klasse kommt ohne `data-address-details` aus:
   `sales_address.js` ist nur im Belegkopf noetig, wo eine Adressbuchauswahl
   darueber steht.

   `.pack-dimensions` am Packtisch teilt sich das Aussehen: dort stehen die
   Abmessungen zugeklappt, weil sie nur Speditionsware betreffen -- bei DHL
   und DPD zaehlt allein das Gewicht. Ein zweites Aussehen fuer denselben
   Handgriff waere eines zu viel. */
.address-details,
.pack-dimensions { margin-top: 0.5rem; }
.address-details > summary,
.pack-dimensions > summary {
    cursor: pointer;
    padding: 0.35rem 0;
    font-size: 0.9rem;
    color: var(--erp-muted);
}
.address-details > summary:hover,
.pack-dimensions > summary:hover { color: var(--erp-text); }
.address-details[open] > summary,
.pack-dimensions[open] > summary { color: var(--erp-text); font-weight: 600; }

/* Abschnittsweises Bearbeiten der Belegseite (sales/_sections.html).

   Titel links, Knopfgruppe rechts -- und beide bleiben beim Umschalten an
   derselben Stelle. Das Springen des Layouts beim Klick auf „Bearbeiten“ war
   der Anlass des Umbaus; ein Abschnitt darf seine Breite und seine Kopfzeile
   nicht aendern, nur seinen Inhalt. */
.doc-section-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
    margin-bottom: 0.75rem;
}
.doc-section-head > h2 { margin: 0; }
.doc-section-actions { display: flex; align-items: center; gap: 0.5rem; }
/* Die Knopfgruppe rutscht am Telefon unter den Titel, statt ihn zu quetschen. */
@media (max-width: 560px) {
    .doc-section-head { flex-direction: column; align-items: stretch; }
    .doc-section-actions { justify-content: flex-start; }
}

/* „Abbrechen" verwirft -- deshalb rot, aber ohne Flaeche: es ist der
   Nebenweg neben „Speichern", nicht die gefaehrlichere Hauptsache. */
.btn-cancel {
    color: var(--erp-danger, #b3261e);
    border-color: transparent;
    background: transparent;
}
.btn-cancel:hover { border-color: var(--erp-danger, #b3261e); }

/* Waehrend ein Abschnitt laedt oder speichert. Er bleibt sichtbar und an
   seinem Platz -- ein verschwindender Abschnitt liesse die Seite springen,
   also genau das, was hier abgestellt wurde. */
.doc-section.is-busy { opacity: 0.6; pointer-events: none; }

/* Die Rueckfrage, was mit einer geaenderten Anschrift geschehen soll.
   Ohne JavaScript dauerhaft sichtbar (Vorbelegung "nur fuer diesen Beleg"). */
.address-change {
    margin-top: 0.75rem;
    padding: 0.6rem 0.75rem;
    border: 1px solid var(--erp-border);
    border-left: 3px solid var(--erp-brand);
    border-radius: 6px;
    background: var(--erp-surface);
}
.address-change-question { margin: 0 0 0.4rem; font-size: 0.9rem; font-weight: 600; }
.address-change ul { list-style: none; margin: 0; padding: 0; }
.address-change li { margin: 0.15rem 0; }
.address-change label { font-weight: 400; }

/* „Neuen Kunden anlegen …" im Leer-Zustand der Livesuche. Steht im
   Statusabsatz, nicht in der Ergebnisliste -- die indiziert ihre Zeilen fuer
   die Pfeiltasten (static/js/lookup.js::createButton). */
.lookup-create {
    display: block;
    margin-top: 0.35rem;
    padding: 0.25rem 0.5rem;
    border: 1px solid var(--erp-brand);
    border-radius: 5px;
    background: var(--erp-surface);
    font-size: 0.85rem;
    color: var(--erp-brand);
    cursor: pointer;
}
.lookup-create:hover { background: var(--erp-brand); color: #fff; }

/* ── Popup (<dialog>) ────────────────────────────────────────────────────
   Optik der Karte, damit ein Dialog nicht wie ein Fremdkoerper wirkt.
   Er wird OFFEN ausgeliefert und erst von static/js/dialog.js geschlossen
   (Begruendung in templates/partials/dialog.html) -- deshalb muss der
   geoeffnete Zustand ohne JavaScript wie ein normaler Seitenblock
   aussehen, nicht wie ein schwebendes Fenster. Das leistet die
   Unterscheidung zwischen `[open]` und `:modal`: nur der per `showModal()`
   geoeffnete Dialog schwebt. */
.erp-dialog {
    width: min(46rem, calc(100% - 2rem));
    padding: 0;
    margin-bottom: 1.25rem;
    background: var(--erp-surface);
    color: var(--erp-text);
    border: 1px solid var(--erp-border);
    border-radius: 8px;
}
.erp-dialog:modal {
    margin: auto;
    box-shadow: 0 12px 32px rgba(31, 41, 51, 0.24);
}
.erp-dialog::backdrop { background: rgba(31, 41, 51, 0.45); }
.erp-dialog-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 1rem 1.25rem;
    border-bottom: 1px solid var(--erp-border);
}
.erp-dialog-head h2 { margin: 0; font-size: 1.05rem; }
.erp-dialog-body { padding: 1.25rem; }
/* Die Karten im Dialogkoerper tragen ihren Rahmen schon aussen. */
.erp-dialog-body > .card:last-child { margin-bottom: 0; }
/* Eine `inline-form` ist auf eine Tabellenzelle zugeschnitten und laeuft im
   schmaleren Dialog waagerecht heraus -- samt Absendeknopf, der dann nicht
   mehr zu sehen ist (an der Zahlungsmaske gemessen). Im Dialog bricht sie
   deshalb um; ausserhalb bleibt sie einzeilig. */
.erp-dialog-body .inline-form { flex-wrap: wrap; align-items: flex-end; gap: 0.6rem; }
.erp-dialog-body .inline-form input[type="text"],
.erp-dialog-body .inline-form input[type="number"] { max-width: 100%; }

@media (max-width: 640px) {
    .erp-dialog { width: calc(100% - 1rem); }
    .erp-dialog-body { padding: 0.9rem; }
}

/* Trockenlauf-Vorschau der Kundenzusammenfuehrung. Vorformatiert, weil der
   Report seine Zeilen selbst einrueckt (`customer_merge.MergeReport.lines()`)
   -- derselbe Text, den der CLI-Lauf ausgibt. */
.merge-preview {
    background: var(--erp-surface-muted, #f6f7f9);
    border: 1px solid var(--erp-border, #dcdfe4);
    border-radius: 6px;
    padding: 0.75rem 1rem;
    font-size: 0.85rem;
    white-space: pre-wrap;
    overflow-x: auto;
}

/* Ausgabemenue am Beleg (PDF / Mail / Drucken) -- natives <details>, dasselbe
   Muster wie `.dt-filter` und `.address-details`. Der Panel-Inhalt ist eine
   Spalte aus Links und Ein-Knopf-Formularen; `display: contents` waere hier
   falsch, weil die Formulare sonst ihre Breite verlieren. */
.output-menu { position: relative; display: inline-block; }
.output-menu-summary { list-style: none; cursor: pointer; }
.output-menu-summary::-webkit-details-marker { display: none; }
.output-menu-panel {
    position: absolute;
    z-index: 30;
    top: calc(100% + 2px);
    left: 0;
    min-width: 14rem;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.25rem;
    padding: 0.4rem;
    background: var(--erp-surface);
    border: 1px solid var(--erp-border);
    border-radius: 6px;
    box-shadow: 0 6px 18px rgba(31, 41, 51, 0.14);
}
.output-menu-panel .btn,
.output-menu-panel form { width: 100%; }
.output-menu-panel form .btn { width: 100%; }


/* ── Statistik: Diagramme und Kennzahlen ─────────────────────────────── */
.chart { margin: 0.5rem 0 0; }
.chart-svg { display: block; width: 100%; height: auto; max-height: 260px; }
.chart-grid { stroke: var(--chart-grid); stroke-width: 1; }
.chart-axis { stroke: var(--erp-border); stroke-width: 1; }
.chart-tick { fill: var(--erp-muted); font-size: 11px; font-variant-numeric: tabular-nums; }
.chart-fill-1 { fill: var(--chart-series-1); background: var(--chart-series-1); }
.chart-fill-2 { fill: var(--chart-series-2); background: var(--chart-series-2); }
.chart-stroke-1 { stroke: var(--chart-series-1); }
.chart-stroke-2 { stroke: var(--chart-series-2); }
.chart-bar:hover { opacity: 0.75; }
.chart-line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.chart-point { stroke: var(--erp-surface); stroke-width: 2; }
.chart-point:hover { r: 6; }
.chart-legend { display: flex; gap: 1rem; list-style: none; margin: 0 0 0.25rem; padding: 0;
                font-size: 0.85rem; color: var(--erp-muted); flex-wrap: wrap; }
.chart-legend li { display: flex; align-items: center; gap: 0.35rem; }
.chart-swatch { display: inline-block; width: 10px; height: 10px; border-radius: 2px; }
.chart-sparkline { vertical-align: middle; }

.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
            gap: 0.75rem; margin-bottom: 1rem; }
.kpi { background: var(--erp-surface); border: 1px solid var(--erp-border); border-radius: 8px;
       padding: 0.75rem 1rem; }
.kpi-label { color: var(--erp-muted); font-size: 0.85rem; }
.kpi-value { display: block; font-size: 1.5rem; font-weight: 600; line-height: 1.2;
             margin-top: 0.15rem; }
.kpi-delta { font-size: 0.85rem; color: var(--erp-muted); }
a.kpi { color: inherit; text-decoration: none; }
a.kpi:hover { border-color: var(--erp-brand); }

.stat-switch { display: flex; gap: 0.4rem; flex-wrap: wrap; margin: 0 0 0.75rem; }
.cohort-cell { text-align: right; font-variant-numeric: tabular-nums; }
.stat-columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
                gap: 0.75rem 1.5rem; }
.stat-columns h3 { margin: 0 0 0.35rem; font-size: 1rem; }

/* Sendungsseite: Bezug, Zustellproblem und Sendungsverlauf (24.09.2026).
   Der Verlauf ist ein natives <details> wie die Historie darunter -- dieselbe
   Zusammenfassungszeile, damit beide Aufklapper gleich aussehen. */
.shipment-refs { display: flex; flex-wrap: wrap; gap: 0.25rem 1.25rem; margin: 0.35rem 0 0;
                 color: var(--erp-muted); }
.shipment-refs a { font-weight: 600; }
.tracking-problem { max-width: 720px; }
.tracking-problem-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 1rem;
                            margin-top: 0.6rem; }
.tracking-problem-actions .tracking-number { display: inline-flex; align-items: center; gap: 0.4rem; }
.tracking-history > summary,
.tracking-history-parcel > summary { cursor: pointer; }
.tracking-history > summary > h2 { display: inline; margin: 0; font-size: 1.05rem; }
.tracking-history > summary:hover > h2 { color: var(--erp-brand); }
.tracking-history-parcel { margin-top: 0.75rem; }
/* Zahlungsabgleich (/bank/): drei aufklappbare Bereiche, dieselbe
   Zusammenfassungszeile wie Historie und Sendungsverlauf. Die Buchungskarten
   darin ruecken enger zusammen -- sie stehen in einer Karte, nicht frei. */
.bank-section > summary { cursor: pointer; }
.bank-section > summary > h2 { display: inline; margin: 0; font-size: 1.05rem; }
.bank-section > summary:hover > h2 { color: var(--erp-brand); }
.bank-section[open] > summary { margin-bottom: 0.75rem; }
.bank-section .card { padding: 1rem; margin-bottom: 0.75rem; }
.tracking-history-parcel > summary { font-weight: 600; }
.tracking-events { margin-top: 0.6rem; }
.tracking-events td.nowrap, td.nowrap { white-space: nowrap; }
.tracking-events tr.is-problem td { color: var(--erp-danger); font-weight: 600; }
.tracking-problem-icon { margin-right: 0.25rem; }
/* Sendungsliste: der Carrier-Text zum Status erscheint beim Ueberfahren.
   `position: fixed` ohne Koordinaten bleibt an seiner statischen Stelle, wird
   aber nicht von `.table-scroll` abgeschnitten (die Statusspalte steht ganz
   rechts, ein `absolute`-Feld lief dort ueber den Rand). Die Spalten-Flexbox
   richtet diese statische Stelle rechtsbuendig aus, damit das Feld nach
   links aufklappt -- senkrecht liegt sie aber auf dem Badge (ein
   `absolute`/`fixed`-Kind zaehlt als einziges Flex-Element), deshalb traegt
   `margin-top` die Badgehoehe (ca. 1.3rem) plus Abstand. */
.status-hint { display: inline-flex; flex-direction: column; align-items: flex-end;
               cursor: help; outline: none; }
.status-hint:focus-visible .status-badge { outline: 2px solid var(--erp-brand); outline-offset: 2px; }
.status-hint-panel {
    display: none;
    position: fixed;
    z-index: 50;
    max-width: 22rem;
    margin-top: 1.7rem;
    padding: 0.5rem 0.65rem;
    background: var(--erp-surface);
    border: 1px solid var(--erp-border);
    border-radius: 6px;
    box-shadow: 0 6px 18px rgba(31, 41, 51, 0.14);
    font-size: 0.85rem;
    line-height: 1.4;
    white-space: normal;
    text-align: left;
    color: var(--erp-text);
}
.status-hint-panel.is-problem { color: var(--erp-danger); }
.status-hint-panel small { display: block; margin-top: 0.25rem; }
.status-hint:hover .status-hint-panel,
.status-hint:focus-within .status-hint-panel { display: block; }

/* Zweiter Faktor (auth/templates/auth/*): Trennlinie "oder" zwischen
   Passwort und Passkey, QR-Code, Notfallcodes. */
.mfa-divider { display: flex; align-items: center; gap: 0.75rem; margin: 1rem 0; color: var(--erp-muted); }
.mfa-divider::before, .mfa-divider::after { content: ""; flex: 1; border-top: 1px solid var(--erp-border); }
.mfa-error { color: var(--erp-danger); margin: 0.5rem 0 0; }
.mfa-banner { margin-bottom: 1rem; }
.mfa-qr svg { display: block; width: 200px; height: 200px; margin: 0.5rem 0; }
.mfa-secret { font-size: 1rem; letter-spacing: 0.08em; word-break: break-all; }
.mfa-codes { list-style: none; padding: 0; display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.4rem 1.5rem; }
.mfa-codes code { font-size: 1.05rem; letter-spacing: 0.05em; }

/* Startseite: Kennzahlen-Karten nebeneinander, auf schmalen Bildschirmen untereinander. */
.dashboard-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(420px, 1fr)); gap: 1rem; }
.dashboard-grid .card { margin: 0; }
@media (max-width: 600px) { .dashboard-grid { grid-template-columns: 1fr; } }
.row-total td { font-weight: 600; border-top: 2px solid var(--erp-border, #ccc); }
.doc-chain { display: flex; flex-wrap: wrap; gap: .25rem .75rem; list-style: none; padding: 0; margin: 0; }
.doc-chain li + li::before { content: "→"; margin-right: .75rem; color: var(--erp-muted, #777); }
