/*
 * Günes Döner corporate identity overlay.
 *
 * Loaded after core.css and deliberately limited to overriding Bootstrap's
 * CSS custom properties. The 800 KB vendor file is never edited, so the
 * template can still be updated in place.
 *
 *   #E42D24  signal red   -> primary
 *   #252525  near black   -> dark surfaces, sidebar, headings
 */

:root,
[data-bs-theme='light'] {
  --gd-red: #e42d24;
  --gd-red-rgb: 228, 45, 36;
  --gd-red-dark: #b8231c;
  --gd-red-light: #f26058;
  --gd-ink: #252525;
  --gd-ink-rgb: 37, 37, 37;

  --bs-primary: var(--gd-red);
  --bs-primary-rgb: var(--gd-red-rgb);
  --bs-primary-contrast: #ffffff;
  --bs-primary-bg-subtle: #fdeceb;
  --bs-primary-border-subtle: #f7c3c0;
  --bs-primary-text-emphasis: #8c1a15;

  --bs-dark: var(--gd-ink);
  --bs-dark-rgb: var(--gd-ink-rgb);

  --bs-link-color: var(--gd-red);
  --bs-link-color-rgb: var(--gd-red-rgb);
  --bs-link-hover-color: var(--gd-red-dark);
}

[data-bs-theme='dark'] {
  --bs-primary: #f2564e;
  --bs-primary-rgb: 242, 86, 78;
  --bs-primary-bg-subtle: #4a1b19;
  --bs-primary-border-subtle: #7a2b26;
  --bs-primary-text-emphasis: #f8a5a1;

  --bs-link-color: #f2564e;
  --bs-link-color-rgb: 242, 86, 78;
  --bs-link-hover-color: #f8a5a1;
}

/* Public Sans is not bundled; keep the intended look with system faces
   instead of pulling a webfont from an external host. */
:root {
  --bs-font-sans-serif: 'Public Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
    'Helvetica Neue', Arial, sans-serif;
}

/* --- Semi dark sidebar --------------------------------------------------- */

/*
 * The menu is scoped to its own set of Vuexy's custom properties instead of
 * overriding individual selectors, so template updates keep working.
 * --bs-base-color feeds the color-mix() that produces hover and sub-item
 * backgrounds; flipping it to white makes those mixes lighten rather than
 * darken on the dark surface.
 */
#layout-menu.menu-vertical {
  --bs-menu-bg: var(--gd-ink);
  --bs-menu-bg-rgb: var(--gd-ink-rgb);
  --bs-menu-color: rgba(255, 255, 255, 0.75);
  --bs-menu-color-rgb: 255, 255, 255;
  --bs-menu-header-color: rgba(255, 255, 255, 0.45);
  --bs-menu-divider-color: rgba(255, 255, 255, 0.08);
  --bs-menu-box-shadow: none;

  /*
   * Hover and sub-item backgrounds are set as literal colours, not derived.
   * Vuexy ships them as color-mix() with the light theme's values already
   * baked in (color-mix(in sRGB, #2f2b3d 6%, #fff)), so overriding the
   * ingredients of that mix has no effect and the pill would stay white.
   *
   * Beim Überfahren wird die Zeile heller, nicht hell: ein weißes Feld auf
   * der dunklen Leiste zwang das Wort in ein Grau, und sobald eine Gruppe
   * aufgeklappt war, blieb es beim hellen Feld mit hellem Wort — unlesbar.
   * Ein leichter weißer Schleier mit weißer Schrift funktioniert in jedem
   * Zustand, und die rote Füllung bleibt dem aktiven Punkt vorbehalten.
   */
  --bs-menu-hover-bg: rgba(255, 255, 255, 0.1);
  --bs-menu-hover-color: #ffffff;
  --bs-menu-sub-active-bg: rgba(255, 255, 255, 0.08);
  --bs-menu-sub-active-color: #ffffff;

  background-color: var(--gd-ink);
  border-right: 1px solid rgba(255, 255, 255, 0.06);
}

/* Das Symbol hat eine eigene Farbregel und braucht denselben Wechsel wie das
   Wort. Ohne :not(.open) — eine aufgeklappte Gruppe wird genauso überfahren
   wie ein einzelner Punkt. */
#layout-menu .menu-item:not(.active) > .menu-link:hover,
#layout-menu .menu-item:not(.active) > .menu-link:hover .menu-icon {
  color: #ffffff;
}

/* The brand block sits on the same dark surface as the menu. */
#layout-menu .app-brand {
  background-color: var(--gd-ink);
  padding-top: 1.25rem;
  padding-bottom: 1.25rem;
}

#layout-menu .menu-header-text,
#layout-menu .menu-icon {
  color: rgba(255, 255, 255, 0.55);
}

#layout-menu .menu-item.active > .menu-link .menu-icon {
  color: #ffffff;
}

/* The fade at the top of the scroll area must match the dark menu. */
#layout-menu .menu-inner-shadow {
  background: linear-gradient(var(--gd-ink) 41%, rgba(37, 37, 37, 0.11) 95%, rgba(37, 37, 37, 0));
}

/* --- Brand accents ------------------------------------------------------ */

.app-brand-logo img {
  height: 96px;
  width: auto;
  max-width: 100%;
}

/* In the collapsed rail only the mark fits, so scale it down there. */
.layout-menu-collapsed:not(.layout-menu-hover) .app-brand-logo img {
  height: 40px;
}

.app-brand-text {
  color: var(--gd-ink);
  font-weight: 700;
  letter-spacing: -0.02em;
}

[data-bs-theme='dark'] .app-brand-text {
  color: #fff;
}

/* --- Calculation specific helpers --------------------------------------- */

/* A price that deviates from the global standard. The marker has to survive
   at a glance in a long table, so it gets a left border rather than a badge. */
.price-override {
  border-left: 3px solid var(--bs-warning);
  padding-left: 0.5rem;
}

/* --- Der Strich am vereinbarten Preis --------------------------------------
   Er sagt in einem Blick, wie der Preis zum Sollpreis steht: grün erreicht,
   orange bis zehn Prozent darunter, rot mehr als das. Vorher war er immer
   orange und bedeutete nur "von Hand gesetzt" — eine Information, die in
   dieser Tabelle jede Zeile hat und deshalb keine ist. */

.price-verdict {
  border-left: 3px solid transparent;
  padding-left: 0.5rem;
}

.price-verdict-good {
  border-left-color: var(--bs-success);
}

.price-verdict-close {
  border-left-color: var(--bs-warning);
}

.price-verdict-bad {
  border-left-color: var(--bs-danger);
}

/* Ohne Sollpreis gibt es nichts zu beurteilen. Der Strich bleibt als
   grauer Platzhalter stehen, damit die Zahlen in einer Flucht bleiben. */
.price-verdict-none {
  border-left-color: var(--bs-border-color);
}

.price-global {
  color: var(--bs-secondary-color);
  text-decoration: line-through;
  font-size: 0.8125rem;
}

.margin-positive {
  color: var(--bs-success);
  font-weight: 600;
}

.margin-thin {
  color: var(--bs-warning);
  font-weight: 600;
}

.margin-negative {
  color: var(--bs-danger);
  font-weight: 600;
}

/*
 * Action icons in table rows. The template ships them at roughly 26 px,
 * which is below a comfortable touch target — these are the buttons people
 * hit dozens of times a day, so they get a proper hit area and a larger glyph.
 */
.table .btn-icon {
  width: 2.5rem;
  height: 2.5rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.table .btn-icon i {
  font-size: 1.35rem;
}

.table .btn-icon:hover {
  background-color: var(--bs-secondary-bg);
  color: var(--bs-primary);
}

/* The action column must stay on one line — the larger buttons otherwise
   wrap under each other and double the row height. */
.table td.text-end,
.table th.text-end {
  white-space: nowrap;
  width: 1%;
}

.table td:last-child .btn-icon + .btn-icon {
  margin-left: 0.125rem;
}

/* Sort indicators for the in-place tables, which do not use DataTables. */
.table th.sorting-asc::after {
  content: ' ↑';
  opacity: 0.5;
}

.table th.sorting-desc::after {
  content: ' ↓';
  opacity: 0.5;
}

/* Numbers in tables line up only with tabular figures. */
.num {
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
}

.table thead th.num,
.table tfoot td.num {
  text-align: right;
}

/* The result panel of a calculation. */
.calc-summary {
  background: linear-gradient(135deg, var(--gd-ink) 0%, #3a3a3a 100%);
  color: #fff;
}

.calc-summary .calc-label {
  color: rgba(255, 255, 255, 0.6);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.calc-summary .calc-value {
  font-size: 1.5rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.calc-summary .calc-value-accent {
  color: var(--gd-red-light);
}

/* Recipe editor rows stay compact so a 20 line recipe fits on one screen. */
.recipe-row td {
  vertical-align: middle;
  padding-top: 0.35rem;
  padding-bottom: 0.35rem;
}

.recipe-row input[type='text'],
.recipe-row input[type='number'] {
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.ingredient-customer-only {
  border-left: 3px solid var(--bs-info);
}

/* --- Auth screen -------------------------------------------------------- */

.auth-wrapper {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, var(--gd-ink) 0%, #1a1a1a 60%, #2f1210 100%);
  padding: 1.5rem;
}

.auth-card {
  width: 100%;
  max-width: 27rem;
}

.auth-logo {
  width: min(300px, 70vw);
  height: auto;
}

.auth-version {
  margin-top: 0.75rem;
  color: rgba(255, 255, 255, 0.45);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* --- Drag and drop in the price index ------------------------------------ */

/* Der Griff zum Ziehen.
   Ziehen und Ablegen im Browser kennt keine Finger — auf einem Telefon oder
   Tablet passiert beim Wischen nichts. Deshalb hängt der Griff nicht an der
   Fensterbreite, sondern an der Frage, ob überhaupt eine Maus da ist. Vorher
   war er unter 992 Pixeln ausgeblendet, und auf einem halbbreiten Fenster am
   Schreibtisch fand ihn niemand. */
.drag-cell,
.drag-head,
.drag-hint {
  display: none;
}

@media (hover: hover) and (pointer: fine) {
  .drag-cell,
  .drag-head {
    display: table-cell;
  }

  .drag-hint {
    display: inline;
  }
}

.drag-cell {
  cursor: grab;
  text-align: center;
  user-select: none;
}

.drag-cell:active {
  cursor: grabbing;
}

tr.dragging {
  opacity: 0.45;
  background-color: var(--bs-primary-bg-subtle);
}

/* An empty group still needs a visible drop area. */
tbody[data-drop-group]:empty::after {
  content: '';
  display: block;
  height: 3rem;
}

/* --- Hiding figures while sitting with a customer ------------------------
   Every euro amount comes out of the money() helper wrapped in .money, and
   the price inputs are marked by privacy.js. Both are masked rather than
   removed, so the layout does not jump when the switch is flipped and the
   kilogram columns stay exactly where they were. */

/* Weichgezeichnet statt ausgegraut: der Betrag bleibt als Form erkennbar,
   die Zeile behält ihre Höhe, und es sieht nicht nach Platzhalter aus. Der
   Radius ist in em angegeben, damit er mit der Schriftgröße mitwächst — sonst
   wären die großen Kacheln noch lesbar und die kleinen Tabellen unnötig
   verschmiert. */

html.privacy-on .money {
  filter: blur(0.3em);
  user-select: none;
  /* Blur allein lässt Klick und Markierung zu; das wäre eine Lücke, wenn
     jemand den Text markiert und so wieder lesbar macht. */
  pointer-events: none;
}

/* Amounts typed into a field: dots, and still editable. */
html.privacy-on input.money-input {
  -webkit-text-security: disc;
  text-security: disc;
}

html.privacy-on #privacy-icon {
  color: var(--bs-primary);
}

/* --- The role matrix ------------------------------------------------------
   The permission column needs room to read, but not so much that the
   checkboxes are pushed off a phone screen entirely. */

.role-matrix th:first-child,
.role-matrix td:first-child {
  min-width: 12rem;
}

@media (min-width: 768px) {
  .role-matrix th:first-child,
  .role-matrix td:first-child {
    min-width: 20rem;
  }
}

/* --- Die Kalkulation als Rechnung -----------------------------------------
   Drei Spalten: Operator, Bezeichnung, Betrag. Der Operator steht in einer
   eigenen schmalen Spalte, damit alle Beträge auf derselben Kante enden und
   das Auge die Summen findet, ohne zu suchen. */

.calc-line {
  display: grid;
  grid-template-columns: 1.25rem 1fr auto;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0.3rem 0;
}

.calc-op {
  color: rgba(255, 255, 255, 0.35);
  font-weight: 600;
  text-align: center;
}

.calc-line-label {
  color: rgba(255, 255, 255, 0.7);
  font-size: 0.85rem;
}

.calc-line-value {
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  white-space: nowrap;
}

/* Eine Summe steht unter einem Strich und wiegt schwerer. */
.calc-line-sum {
  border-top: 1px solid rgba(255, 255, 255, 0.18);
  margin-top: 0.15rem;
  padding-top: 0.5rem;
}

.calc-line-sum .calc-line-label {
  color: rgba(255, 255, 255, 0.9);
  font-weight: 600;
}

.calc-line-sum .calc-line-value {
  font-size: 1.15rem;
  font-weight: 700;
}

/* Der tatsächliche Verkaufspreis ist kein Rechenschritt, sondern der Wert,
   gegen den gerechnet wird — deshalb abgesetzt statt eingereiht. */
.calc-line-result {
  display: grid;
  grid-template-columns: 1.25rem 1fr auto;
  align-items: baseline;
  gap: 0.5rem;
  margin-top: 1.1rem;
  padding: 0.5rem 0 0.3rem;
}

.calc-line-result .calc-line-label {
  color: rgba(255, 255, 255, 0.9);
  font-size: 0.85rem;
}

.calc-line-result .calc-line-value {
  font-size: 1.35rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.calc-line-muted .calc-line-value {
  color: rgba(255, 255, 255, 0.55);
  font-weight: 400;
}

/* Die Rechnung braucht keine volle Kartenbreite: sonst driften Bezeichnung
   und Betrag so weit auseinander, dass die Zeile schwer zu lesen ist. */
.calc-chain {
  max-width: 32rem;
}

/* --- Fußzeile der DataTables ----------------------------------------------
   Anzahl und Blätterknöpfe saßen ohne Abstand auf der Kartenkante. Die
   letzte Zeile bekommt Luft nach oben und unten; die Kopfzeile mit Länge
   und Suchfeld gleich mit, damit die Karte oben und unten gleich atmet. */

.dt-container > .row:first-child {
  padding: 1rem 0.75rem 0.25rem;
  margin-top: 0;
}

.dt-container > .row:last-child {
  padding: 0.75rem 0.75rem 1rem;
  margin-top: 0;
}

/* Die Zeilenzahl steht auf gleicher Grundlinie wie die Knöpfe daneben. */
.dt-container .dt-info {
  padding-bottom: 0;
}

@media (max-width: 767.98px) {
  /* Untereinander gestapelt braucht es zwischen Anzahl und Knöpfen Abstand. */
  .dt-container > .row:last-child .dt-paging {
    margin-top: 0.75rem;
  }
}

/* --- Das Einkaufsraster ----------------------------------------------------
   Eine Nachbildung des Papierblattes: oben die Lieferanten, links die Ware,
   in der Zelle Menge und Kilopreis. Dreizehn Lieferanten passen auf kein
   Telefon, deshalb scrollt die Tabelle waagerecht — die Warenspalte bleibt
   dabei stehen, sonst weiß niemand mehr, welche Zeile er gerade liest. */

.purchase-grid {
  font-size: 0.8125rem;
}

.purchase-grid th,
.purchase-grid td {
  vertical-align: top;
}

.purchase-head-supplier {
  min-width: 8.5rem;
  font-size: 0.75rem;
  line-height: 1.2;
}

/* Die Zeilenselektoren stehen mit, damit die Regel Bootstraps
   `.table > :not(caption) > * > *` schlägt — sonst bleibt die Zelle
   durchsichtig, und beim Scrollen liest man zwei Spalten übereinander. */
.purchase-grid > thead > tr > .purchase-head-total,
.purchase-grid > tbody > tr > .purchase-cell-total,
.purchase-grid > tfoot > tr > .purchase-cell-total {
  min-width: 8.5rem;
  background-color: var(--bs-tertiary-bg);
}

/* Die Warenspalte bleibt beim Scrollen stehen. position: sticky wirkt nur,
   wenn die Zelle einen eigenen Hintergrund hat — sonst scrollt der Inhalt
   sichtbar darunter durch. */
.purchase-grid > thead > tr > .purchase-head-article,
.purchase-grid > tbody > tr > .purchase-cell-article,
.purchase-grid > tfoot > tr > .purchase-cell-article {
  position: sticky;
  left: 0;
  z-index: 2;
  min-width: 11rem;
  background-color: var(--bs-body-bg);
}

.purchase-grid > thead > tr > .purchase-head-article {
  z-index: 3;
}

/* Die Warenzeile ist ein th, damit sie zur Zeile gehört — aber sie ist
   Inhalt, keine Überschrift. Ohne das hier setzt die Tabellenvorlage sie
   in Versalien, und aus "Kalbslappen rosé" wird "KALBSLAPPEN ROSÉ". */
.purchase-grid > tbody > tr > .purchase-cell-article {
  text-transform: none;
  letter-spacing: normal;
  font-weight: 500;
}

/* Auf dem Telefon nimmt eine 11rem breite Warenspalte die halbe Anzeige
   ein; dann bleibt für die Lieferanten nichts übrig. */
@media (max-width: 767.98px) {
  .purchase-grid > thead > tr > .purchase-head-article,
  .purchase-grid > tbody > tr > .purchase-cell-article,
  .purchase-grid > tfoot > tr > .purchase-cell-article {
    min-width: 7.5rem;
  }

  .purchase-head-supplier {
    min-width: 7rem;
  }
}

.purchase-cell {
  min-height: 3.25rem;
  position: relative;
}

.purchase-cell-editable {
  cursor: pointer;
}

/* Das Pluszeichen erscheint erst beim Überfahren: 91 Zellen mit einem
   dauerhaften Plus wären ein Nadelkissen. */
.purchase-add {
  display: none;
  color: var(--bs-secondary-color);
}

.purchase-cell-editable:hover {
  background-color: var(--bs-tertiary-bg);
}

.purchase-cell-editable:hover .purchase-add {
  display: inline;
}

/* Ein Eintrag ist entweder ein notierter Preis oder eine Bestellung. Der
   Preis steht in beiden Fällen in derselben Zeile, damit die Spalte beim
   Vergleichen ruhig bleibt; die Bestellung setzt Liefertag und Menge davor. */
.purchase-entry {
  padding: 0.125rem 0;
}

.purchase-entry + .purchase-entry {
  border-top: 1px dashed var(--bs-border-color);
  margin-top: 0.25rem;
  padding-top: 0.25rem;
}

.purchase-entry-line {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.25rem 0.375rem;
}

/* Die Bestellung ist die Zeile, auf die es ankommt. Sie bekommt eine neutrale
   Tönung und eine rote Kante — flächiges Rot läse sich als Warnung, und im
   ganzen Programm ist Rot die Farbe der Probleme. */
.purchase-entry-ordered {
  margin: -0.125rem -0.25rem;
  padding: 0.25rem 0.25rem 0.25rem 0.4rem;
  border-left: 3px solid var(--bs-primary);
  border-radius: 0 var(--bs-border-radius-sm) var(--bs-border-radius-sm) 0;
  background-color: var(--bs-tertiary-bg);
}

.purchase-entry-ordered + .purchase-entry,
.purchase-entry + .purchase-entry-ordered {
  border-top: 0;
  margin-top: 0.25rem;
}

/* Grün und nicht rot: das ist eine gute Nachricht, und Rot gehört im ganzen
   Programm den Warnungen. */
.purchase-best {
  display: inline-block;
  margin-bottom: 0.125rem;
  padding: 0.1rem 0.4rem;
  background-color: var(--bs-success-bg-subtle);
  color: var(--bs-success-text-emphasis);
  border: 1px solid var(--bs-success-border-subtle);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.01em;
}

/* Das Datum der Abfrage ist Beiwerk und darf den Preis nicht überstimmen. */
.purchase-quoted-on {
  font-size: 0.6875rem;
  color: var(--bs-secondary-color);
}

/* Der Liefertag stand auf dem Blatt handschriftlich über der Spalte. Hier
   gehört er an den Eintrag: derselbe Lieferant kann in einer Woche zweimal
   liefern, und dann ist es je Zeile ein anderer Tag. */
.purchase-day {
  flex: 0 0 auto;
  min-width: 1.75rem;
  padding: 0 0.25rem;
  border-radius: var(--bs-border-radius-sm);
  background-color: var(--bs-primary-bg-subtle);
  color: var(--bs-primary-text-emphasis);
  font-size: 0.6875rem;
  font-weight: 600;
  text-align: center;
}

.purchase-qty {
  font-weight: 600;
}

/* An einer Anfrage ist die Menge nur die Grundlage der Frage, an einer
   Bestellung ist sie die Zusage. Deshalb dort kräftig, hier zurückhaltend. */
.purchase-qty-quoted {
  font-weight: 400;
  color: var(--bs-secondary-color);
}

.purchase-note-icon {
  color: var(--bs-secondary-color);
}

/* --- Die Übersicht zum Drucken ---------------------------------------------
   Gedruckt wird nicht das Raster: das ist zum Erfassen gebaut und besteht auf
   Papier fast nur aus leeren Zellen. Stattdessen die Übersicht nach Ware, die
   am Bildschirm verborgen in der Seite steht. */

.purchase-print-only {
  display: none;
}

.purchase-overview-head {
  margin-bottom: 1rem;
}

.purchase-overview-category {
  margin: 1.25rem 0 0.35rem;
  padding-bottom: 0.2rem;
  border-bottom: 1px solid #000;
  font-size: 0.95rem;
}

.purchase-overview-table {
  break-inside: auto;
}

.purchase-overview-table tr {
  break-inside: avoid;
}

/* Ohne Warennamen wirkt die Folgezeile wie ein neuer Posten. Ein feiner Strich
   nur oberhalb der ersten Zeile einer Ware hält sie zusammen. */
.purchase-overview-article:not(:empty) {
  font-weight: 600;
}

@media print {
  @page {
    size: portrait;
    margin: 1.4cm;
  }

  /* Alles außer der Übersicht bleibt draußen — Menü, Kopfzeile, das Raster,
     die Dialoge und die Karten drumherum. */
  .layout-menu,
  .layout-navbar,
  .content-footer,
  .breadcrumb,
  .card,
  .modal,
  .modal-backdrop,
  .alert {
    display: none !important;
  }

  .layout-page,
  .content-wrapper,
  .container-xxl {
    margin: 0 !important;
    padding: 0 !important;
  }

  .purchase-print-only {
    display: block;
  }

  .purchase-overview-table {
    width: 100%;
    font-size: 0.75rem;
  }

  /* Auf Papier bringt eine getönte Kopfzeile nichts als Tonerverbrauch. */
  .purchase-overview-table th {
    border-bottom: 1px solid #000;
    background: none !important;
  }

  .purchase-overview-table td,
  .purchase-overview-table th {
    padding: 0.2rem 0.35rem;
  }
}

/* --- Vollbild für das Einkaufsraster ---------------------------------------
   Dreizehn Lieferantenspalten passen in keine Karte im Seitenlayout. Im
   Vollbild legt sich die Karte über das Fenster, und beim Abtelefonieren
   sieht man die ganze Zeile auf einmal. */

.card.purchase-fullscreen {
  position: fixed;
  inset: 0;
  z-index: 1045; /* unter dem Bootstrap-Dialog (1055), über allem anderen */
  display: flex;
  flex-direction: column;
  margin: 0;
  border-radius: 0;
}

.card.purchase-fullscreen > .table-responsive {
  flex: 1 1 auto;
  overflow: auto;
}

/* Im Vollbild wird senkrecht gescrollt, also müssen auch die Lieferanten
   stehen bleiben — sonst weiß man in der siebten Zeile nicht mehr, welche
   Spalte man liest. */
.card.purchase-fullscreen .purchase-grid > thead > tr > th {
  position: sticky;
  top: 0;
  z-index: 2;
  background-color: var(--bs-body-bg);
}

.card.purchase-fullscreen .purchase-grid > thead > tr > .purchase-head-article {
  z-index: 4;
}

.card.purchase-fullscreen .purchase-grid > thead > tr > .purchase-head-total {
  background-color: var(--bs-tertiary-bg);
}

/* Der Hintergrund der Seite darf nicht mitscrollen, während oben das Vollbild
   liegt. */
html.purchase-fullscreen-open,
html.purchase-fullscreen-open body {
  overflow: hidden;
}

/* Menüleiste und Kopfzeile liegen in der Vorlage über allem anderen. Sie
   auszublenden ist ehrlicher, als sie mit einem noch höheren z-index zu
   überbieten — dann käme der nächste Dialog nicht mehr darüber. Zurück geht
   es über den Knopf in der Kartenkopfzeile oder mit Escape. */
html.purchase-fullscreen-open .layout-menu,
html.purchase-fullscreen-open .layout-navbar,
html.purchase-fullscreen-open .content-footer {
  display: none !important;
}

/* Eine Bestellung, die in einer anderen Woche aufgegeben wurde und in dieser
   geliefert wird. Sie gehört nicht in dieses Blatt, sondern kommt nur darin
   an — deshalb blasser und ohne die rote Kante der eigenen Bestellungen. */
.purchase-entry-foreign {
  opacity: 0.75;
  border-left-color: var(--bs-border-color);
}

.purchase-foreign-week {
  flex: 0 0 auto;
  padding: 0.1rem 0.4rem;
  background-color: var(--bs-tertiary-bg);
  color: var(--bs-secondary-color);
  border: 1px solid var(--bs-border-color);
  font-size: 0.625rem;
  font-weight: 600;
  text-decoration: none;
}

.purchase-foreign-week:hover {
  color: var(--bs-primary);
  border-color: var(--bs-primary);
}

/* Der Preisverlauf zeigt Beträge. Beim Ausblenden muss er mit verschwinden —
   ein Diagramm ist kein Text, die .money-Regel greift dort nicht. */
html.privacy-on .purchase-chart {
  filter: blur(0.4em);
  user-select: none;
  pointer-events: none;
}

/* --- „In Lieferung" --------------------------------------------------------
   Gelesen wird das auf dem Telefon, mit einer Hand, im Stehen. Deshalb Karten
   statt Tabelle: fünf Spalten auf 375 Pixeln werden entweder zur Tapetenrolle
   oder zum Rätsel. */

.delivery-day-head {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin-bottom: 0.75rem;
  padding-bottom: 0.35rem;
  border-bottom: 2px solid var(--bs-border-color);
}

.delivery-day-name {
  font-size: 1.0625rem;
  font-weight: 700;
}

.delivery-day-date {
  color: var(--bs-secondary-color);
  font-size: 0.875rem;
}

/* Was heute kommt, ist die Zeile, wegen der man die Seite aufmacht. */
.delivery-day-today .delivery-day-head {
  border-bottom-color: var(--bs-primary);
}

.delivery-day-today .delivery-day-name {
  color: var(--bs-primary);
}

/* Und was schon da ist, tritt zurück: in der laufenden Woche steht der Montag
   sonst genauso laut auf der Seite wie der Tag, der noch kommt. */
.delivery-day-past .delivery-day-name,
.delivery-day-past .delivery-day-date {
  color: var(--bs-secondary-color);
  font-weight: 500;
}

.delivery-day-past .delivery-day-head {
  border-bottom-width: 1px;
}

.delivery-card {
  position: relative;
  transition: border-color 0.15s ease-in-out;
}

.delivery-card:hover {
  border-color: var(--bs-primary);
}

.delivery-article {
  font-weight: 600;
  line-height: 1.25;
}

.delivery-price {
  font-weight: 700;
  white-space: nowrap;
}

.delivery-supplier {
  font-weight: 500;
}

/* Auf dem Telefon zählt jeder Millimeter Höhe: die Karten rücken zusammen
   und der Tagestitel bleibt beim Scrollen oben stehen, damit man nie raten
   muss, zu welchem Tag der Block gehört, den man gerade liest. */
@media (max-width: 767.98px) {
  .delivery-card .card-body {
    padding: 0.75rem;
  }

  .delivery-day-head {
    position: sticky;
    top: 0;
    z-index: 3;
    margin-bottom: 0.5rem;
    padding-top: 0.5rem;
    background-color: var(--bs-body-bg);
  }
}

/* Öffentliche Seiten (SEPA-Mandat): heller Grund, damit ein Formular mit
   Unterschrift wie ein Blatt Papier wirkt und nicht wie ein Anmeldefenster. */
body.public-page {
    background: var(--bs-body-bg);
    min-height: 100vh;
}

/* Das Logo trägt weiße Schrift — auf hellem Grund braucht es die dunkle
   Platte der CI unter sich. */
.public-brand {
    display: inline-block;
    background: #252525;
    border-radius: .75rem;
    padding: .85rem 1.5rem;
}

.public-brand img {
    height: 2.75rem;
    display: block;
}

/* Das Zeichenfeld für die Unterschrift. Gestrichelt, damit klar ist, dass es
   nichts anzeigt, sondern etwas erwartet. */
.signature-pad {
    border: 2px dashed var(--bs-border-color);
    border-radius: .5rem;
    background: #fff;
    touch-action: none;
    width: 100%;
    height: 11rem;
    cursor: crosshair;
    display: block;
}

.signature-pad.is-signed {
    border-style: solid;
    border-color: var(--bs-primary);
}

/* „In Lieferung": die Karte ist der Knopf. Ein <button> ohne Aussehen, damit
   der ganze Block anklickbar ist — an der Rampe wird mit dem Daumen getroffen,
   nicht mit dem Mauszeiger. */
.btn-reset {
    padding: 0;
    border: 0;
    background: none;
    color: inherit;
    font: inherit;
    cursor: pointer;
}

/* Eine bestätigte Lieferung tritt zurück, ohne zu verschwinden: die Kante
   links sagt „erledigt", der Rest bleibt lesbar. */
.delivery-card-received {
    border-left: 3px solid var(--bs-success);
}

.delivery-received {
    font-size: .8125rem;
    color: var(--bs-success);
}

/* Vorschau und abgelegte Fotos, gleiche Kachelgröße. */
.delivery-photo-grid {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
}

.delivery-photo {
    display: block;
    width: 4.5rem;
    height: 4.5rem;
    border-radius: .375rem;
    overflow: hidden;
    border: 1px solid var(--bs-border-color);
    background: var(--bs-tertiary-bg);
}

.delivery-photo img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* Das Kreuz zum Entfernen liegt auf der Kachel. Eine Reihe Bilder mit einer
   Reihe Knöpfe darunter kostet auf dem Telefon die halbe Dialoghöhe. */
.delivery-photo-tile {
    position: relative;
    display: inline-block;
}

.delivery-photo-remove {
    position: absolute;
    top: -.35rem;
    right: -.35rem;
    width: 1.4rem;
    height: 1.4rem;
    padding: 0;
    line-height: 1;
    border: 0;
    border-radius: 50%;
    background: var(--bs-danger);
    color: #fff;
    font-size: .75rem;
    cursor: pointer;
}

/* Abgelegte Bilder tragen ein dunkles Kreuz: dort löscht es endgültig, in der
   Reihe darüber nimmt es nur aus der Auswahl. Gleiche Geste, andere Folge —
   also auch anderes Aussehen. */
.delivery-photo-grid-stored .delivery-photo-remove {
    background: var(--bs-secondary);
}

/* Der Helfer avatar() liefert ein <span>, damit er auch in einem <a> oder in
   einer Tabellenzelle stehen darf. Vuexy setzt die Maße über block-size, und
   das greift auf einem inline-Element nicht — ohne diese Zeile wächst das Bild
   auf seine volle Größe und sprengt die Seite. */
span.avatar {
    display: inline-block;
    vertical-align: middle;
}

/* Ein Porträt ist selten quadratisch; beschnitten wird mittig statt gestaucht. */
.avatar img {
    object-fit: cover;
}

/* Notizbuch: die Kante links sagt den Zustand, ohne dass man das Etikett
   lesen muss. Erledigtes tritt zurück, verschwindet aber nicht — eine Notiz
   von gestern erklärt oft, warum etwas heute so ist. */
.note-card {
    border-left: 3px solid var(--bs-border-color);
}

.note-open {
    border-left-color: var(--bs-warning);
}

.note-waiting {
    border-left-color: var(--bs-info);
}

.note-done {
    border-left-color: var(--bs-success);
}

.note-done .note-body {
    color: var(--bs-secondary-color);
}

.note-body {
    white-space: pre-wrap;
    word-break: break-word;
}

/* Winziges Gesicht im Zuweisungs-Etikett. */
.avatar-xs,
.avatar-xs .avatar-initial {
    width: 1.15rem;
    height: 1.15rem;
    font-size: .625rem;
}

/* Notizbuch-Auszug im Dashboard: feste Höhe, scrollt in sich. Ohne das schiebt
   sich bei zwanzig Notizen alles Übrige aus dem Bild. */
.dashboard-notes {
    max-height: 24rem;
    overflow-y: auto;
}

.dashboard-note {
    display: flex;
    gap: .75rem;
    padding: .75rem 1.25rem;
    border-top: 1px solid var(--bs-border-color);
    border-left: 3px solid transparent;
    color: inherit;
    text-decoration: none;
}

.dashboard-notes .dashboard-note:first-child {
    border-top: 0;
}

.dashboard-note:hover {
    background: var(--bs-tertiary-bg);
    color: inherit;
}

.dashboard-note.note-open {
    border-left-color: var(--bs-warning);
}

.dashboard-note.note-waiting {
    border-left-color: var(--bs-info);
}

/* Zwei Zeilen genügen für den Überblick; der Rest steht im Notizbuch. */
.dashboard-note-body {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    margin: .15rem 0;
}

/* Kommentare hängen an ihrer Notiz und rücken dafür ein. */
.note-comments {
    margin-top: .75rem;
    padding-left: 3rem;
    border-top: 1px solid var(--bs-border-color);
    padding-top: .75rem;
}

.note-comment {
    display: flex;
    gap: .5rem;
    padding: .35rem 0;
}

/* Die Symbole in der Kopfzeile: Auge, Notizbuch, Glocke, Sprache.
   Sie hingen auf der Standardgröße des Zeichensatzes — rund 16 Pixel und damit
   auf einem Telefon kaum zu treffen. Die Klassen .ti-md und .ti-lg, die das
   Vuexy-Markup mitbringt, sind in diesem Bündel nirgends definiert und tun
   nichts; die Größe muss also hierher.

   Nicht nur größer, auch anfassbar: 2,5rem Fläche je Knopf sind die übliche
   Untergrenze für einen Daumen. */
.layout-navbar .navbar-nav .nav-link > .ti {
    font-size: 1.5rem;
    line-height: 1;
}

.layout-navbar .navbar-nav > .nav-item > .nav-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 2.5rem;
    min-height: 2.5rem;
    padding: .25rem;
}

/* Das eigene Gesicht darf etwas größer sein als die Symbole daneben — es ist
   der Punkt, an dem man das Menü sucht. */
.layout-navbar .dropdown-user .avatar {
    --bs-avatar-size: 2.75rem;
}

/* Die Zahl an der Glocke wächst mit, sonst klebt sie verloren am Rand. */
.layout-navbar .badge-notifications {
    font-size: .6875rem;
    padding: .2rem .35rem;
}

/* Das Sprachkürzel neben dem Symbol war winzig. */
.layout-navbar .navbar-nav .nav-link > .small {
    font-size: .8125rem;
}

/* Die Größenklassen, die das Vuexy-Markup überall mitbringt — .ti-sm, .ti-md,
   .ti-lg —, sind in diesem CSS-Bündel nirgends definiert. Jedes Symbol hing
   deshalb auf der Standardgröße des Zeichensatzes, auch dort, wo das Markup
   ausdrücklich ein großes verlangt: die leeren Zustände etwa, deren Symbol
   halb so groß war wie gedacht. */
.ti-xs {
    font-size: .875rem;
}

.ti-sm {
    font-size: 1.125rem;
}

.ti-md {
    font-size: 1.375rem;
}

.ti-lg {
    font-size: 2.25rem;
}

/* Der Textblock schrumpft, statt neben dem Bild umzubrechen.
   `flex-grow-1` allein genügt dafür nicht: die Grundbreite bleibt der
   Inhalt, und ein langer Satz schob sich unter das Bild statt daneben. */
.note-text {
    flex: 1 1 0;
    min-width: 0;
}

/* Notizkarte auf dem Telefon: Status und Zuweisung stehen am großen Bildschirm
   rechts neben dem Text. Auf 375 Pixeln ist daneben kein Platz — dort rutschten
   die Auswahlfelder aus der Karte heraus. Sie rücken deshalb unter den Text und
   nehmen die volle Breite. */
@media (max-width: 767.98px) {
    .note-card .note-controls {
        width: 100%;
        align-items: stretch !important;
    }

    .note-card .note-controls > form {
        flex-wrap: wrap;
    }

    .note-card .note-controls select {
        width: 100% !important;
    }

    .note-card .note-controls > form > div {
        flex: 1 1 8rem;
    }

    /* Der eingerückte Kommentarbereich kostet auf dem Telefon zu viel Breite. */
    .note-comments {
        padding-left: 0;
        margin-top: .5rem;
        padding-top: .5rem;
    }

    /* Die Filterleiste bleibt eine Zeile und schiebt sich seitwärts, statt
       auf zwei Zeilen zu brechen. Vier Knöpfe untereinander sind kein
       Filter mehr, sondern ein Menü. */
    .note-filter {
        display: flex;
        flex-wrap: nowrap;
        overflow-x: auto;
        scrollbar-width: none;
    }

    .note-filter::-webkit-scrollbar {
        display: none;
    }

    /* Vier Zustände in eine Zeile: schmaler gesetzt passen sie ohne Schieben.
       Die Zahl daneben bleibt, sie ist der eigentliche Grund hinzusehen. */
    .note-filter > .btn {
        flex: 0 0 auto;
        padding: .4rem .55rem;
        font-size: .8125rem;
    }

    .note-filter > .btn .badge {
        padding: .2rem .3rem;
        font-size: .6875rem;
    }

    /* Schalter, Archiv und „Neue Notiz" bilden eine Zeile statt drei. */
    .note-toolbar {
        gap: .5rem !important;
        width: 100%;
    }

    .note-toolbar .form-check {
        margin-inline-end: auto;
    }

    .note-card .card-body {
        padding: .875rem;
    }

    .note-head {
        gap: .625rem !important;
    }

    /* Der Kopf einer Notiz — Name, Zeitpunkt, Zustand, Empfänger — darf
       umbrechen, aber eng. */
    .note-head .badge {
        font-size: .6875rem;
    }

    .note-controls {
        margin-top: .25rem;
    }

    /* Die Beschriftungen über den beiden Feldern kosten je eine Zeile.
       Klein genug, dass sie erklären, ohne zu tragen. */
    .note-controls .form-label {
        font-size: .6875rem;
        margin-bottom: .125rem !important;
    }

    /* Kommentarzeile: das Feld über die volle Breite, darunter Kamera und
       Knopf nebeneinander. Nebeneinander bliebe für den Text ein Schlitz. */
    .note-comments form > .form-control {
        flex: 1 1 100%;
    }

    /* Die Bilder rücken nicht ein — auf 375 Pixeln ist die Einrückung
       ein Viertel der Breite. */
    .note-card .delivery-photo-grid.ms-5 {
        margin-left: 0 !important;
        padding-left: 0 !important;
    }
}

/* Eine Notiz, die während des Lesens hinzukommt, blendet sich ein und leuchtet
   kurz auf. Lautlos erscheinen hieße übersehen werden. */
@keyframes note-arrive {
    0% {
        opacity: 0;
        transform: translateY(-.5rem);
        box-shadow: 0 0 0 .35rem rgba(228, 45, 36, .25);
    }

    60% {
        opacity: 1;
        transform: none;
        box-shadow: 0 0 0 .35rem rgba(228, 45, 36, .25);
    }

    100% {
        opacity: 1;
        transform: none;
        box-shadow: 0 0 0 0 rgba(228, 45, 36, 0);
    }
}

.note-arrived {
    animation: note-arrive 1.6s ease-out;
}

/* Wer Bewegung im Betriebssystem abgestellt hat, bekommt sie auch hier nicht. */
@media (prefers-reduced-motion: reduce) {
    .note-arrived {
        animation: none;
        outline: 2px solid var(--bs-primary);
    }
}

/* Das große Bild im Dialog. */
.avatar-full {
    width: 100%;
    max-width: 18rem;
    aspect-ratio: 1;
    object-fit: cover;
    border-radius: .75rem;
}

/* Ein Bild, das sich anklicken lässt, soll das auch zeigen. */
.avatar-zoom {
    cursor: zoom-in;
}

/* Das Herz darf beim Setzen kurz hüpfen. */
.avatar-like.btn-danger i {
    animation: heart-beat .4s ease-out;
}

@keyframes heart-beat {
    0% { transform: scale(1); }
    45% { transform: scale(1.35); }
    100% { transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
    .avatar-like.btn-danger i {
        animation: none;
    }
}
