/* ============================================================
   GRALAX UI — Verbindliches zentrales Stylesheet
   ============================================================
   Regeln:
   1. Dieses Stylesheet ist die EINZIGE Quelle für Styles.
      Keine <style>-Blöcke in Views, keine Seiten-CSS-Dateien.
   2. Neue Seiten verwenden ausschließlich gx-Klassen.
   3. Farben, Abstände, Radien NIE hart codieren — immer Tokens
      (var(--…)) verwenden.
   4. Bestandsseiten: gralax-legacy.css NACH dieser Datei laden,
      bis die Views auf gx-Klassen migriert sind.

   Ladereihenfolge:
     <link rel="stylesheet" href="/gralax-ui.css">
     <link rel="stylesheet" href="/gralax-legacy.css">  (übergangsweise)

   Namensschema:  gx-<komponente>            Block
                  gx-<komponente>__<teil>    Element
                  gx-<komponente>--<variante> Modifier
   ============================================================ */

/* ------------------------------------------------------------
   1. DESIGN TOKENS
   ------------------------------------------------------------
   Zusätzlich zu :root auch auf .gx-page deklariert: solange
   styles.css NACH dieser Datei lädt und in seinem :root dieselben
   Token-Namen mit Altwerten belegt, würden gx-Komponenten sonst
   mit den Altwerten rendern. Eine Deklaration direkt auf .gx-page
   schlägt den vererbten :root-Wert — gx-Seiten bekommen so die
   Gralax-Tokens, Bestandsseiten bleiben unberührt. Entfällt mit
   #139 Issue 6 (styles.css löschen).
   ------------------------------------------------------------ */
:root,
.gx-page {
  /* --- Markenfarben (fix, nicht ändern) --- */
  --gralax-gold: #cbb16e;
  --gralax-black: #0a0a0a;
  --gralax-white: #f2f5fc;
  --gralax-blue: #3260ab;
  --gralax-darkgray: #3b3c3c;
  --gralax-gray: #7a7b7b;
  --gralax-lightgray: #d4d5d6;

  /* --- RGB-Helfer für rgba()-Nutzung (alle definiert!) --- */
  --rgb-gold: 203, 177, 110;
  --rgb-blue: 50, 96, 171;
  --rgb-black: 10, 10, 10;
  --rgb-gray: 122, 123, 123;
  --rgb-error: 229, 62, 62;
  --rgb-success: 56, 161, 105;

  /* --- Semantik: Text --- */
  --text-primary: var(--gralax-black);
  --text-secondary: var(--gralax-darkgray);
  --text-muted: var(--gralax-gray);
  --text-light: var(--gralax-white);
  --text-link: var(--gralax-blue);

  /* --- Semantik: Flächen --- */
  --bg-page: var(--gralax-white);
  --bg-panel: #ffffff;
  --bg-sunken: #e9edf5;            /* eingelassene Bereiche, Subrows, Tab-Leisten */
  --bg-table: #ffffff;
  --bg-table-alt: #f7f9fc;         /* Zebra */
  --bg-table-hover: rgba(var(--rgb-gold), 0.14);
  --bg-field: #ffffff;
  --bg-field-focus: #f8faff;
  --bg-editable: #fefcf5;          /* editierbare Zellen (Gold-Tint) */
  --bg-editable-hover: #fdf9ed;

  /* --- Semantik: Ränder --- */
  --border-light: var(--gralax-lightgray);
  --border-medium: var(--gralax-gray);
  --border-field: var(--gralax-lightgray);
  --border-focus: var(--gralax-gold);

  /* --- Status --- */
  --color-error: #e53e3e;
  --color-success: #38a169;
  --color-warning: #d69e2e;
  --color-info: var(--gralax-blue);
  --bg-error-light: #fceaea;
  --bg-success-light: #e6f4ec;
  --bg-warning-light: #fbf3dd;
  --bg-info-light: #e8eef8;
  /* dark text on the light status backgrounds (alert, warning toast) */
  --text-error: #822727;
  --text-success: #22543d;
  --text-warning: #744210;
  --text-info: #2a4365;

  /* --- Typografie --- */
  --font-ui: Arial, Helvetica, sans-serif;
  --font-mono: 'Courier New', Courier, monospace;
  --fs-xs: 0.75rem;
  --fs-sm: 0.85rem;
  --fs-base: 0.9rem;   /* Tabellen & Formulare */
  --fs-md: 1rem;
  --fs-lg: 1.15rem;
  --fs-xl: 1.4rem;
  --fs-price: 2.2rem;  /* große Preisanzeige */

  /* --- Abstände --- */
  --sp-0: 2px;   /* feinste Stufe: dichte Listen (.gx-item--compact) */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-6: 32px;

  /* --- Radien (nur zwei Stufen, verbindlich) --- */
  --rad-sm: 4px;   /* Inputs, Buttons, Badges */
  --rad-md: 6px;   /* Panels, Tabellen-Container, Alerts */

  /* --- Schatten (sparsam einsetzen) --- */
  --shadow-sm: 0 1px 3px rgba(var(--rgb-black), 0.08);
  --shadow-md: 0 4px 12px rgba(var(--rgb-black), 0.12);

  /* --- Bewegung (nur Farbe/Rand, keine Transform-Spielereien) --- */
  --ease: 0.15s ease;

  /* --- Layout --- */
  --sidebar-w: 350px;
  --page-max: 1600px;
}

/* ------------------------------------------------------------
   2. BASIS / RESET
   ------------------------------------------------------------ */
body {
  font-family: var(--font-ui);
  font-size: 16px;
  margin: 0;
  background: var(--bg-page);
  color: var(--text-primary);
}

h1 { font-size: 1.5rem; margin: var(--sp-4) 0; color: var(--text-primary); }
h2 { font-size: 1.2rem; margin: var(--sp-4) 0 var(--sp-3); color: var(--text-primary); }
h3 { font-size: 1.05rem; margin: var(--sp-3) 0 var(--sp-2); color: var(--text-secondary); }

a { color: var(--text-link); }
a:hover { color: var(--gralax-gold); }

/* Das HTML-Attribut hidden muss gegen jede gx-Komponente gewinnen, die
   selbst ein display setzt (.gx-savestate: inline-flex, .gx-split__main:
   flex, …) — sonst bleibt ein per hidden ausgeblendetes Element sichtbar.
   Gleiche Spezifitaet entscheidet sonst nach Reihenfolge, und die
   Komponenten stehen weiter unten. Deshalb !important (wie normalize.css /
   HTML5 Boilerplate). Views blenden damit ohne Inline-Style aus (#139). */
[hidden] { display: none !important; }

/* ------------------------------------------------------------
   3. LAYOUT
   ------------------------------------------------------------ */
/* Seiteninhalt-Wrapper */
.gx-page {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: var(--sp-4);
}

/* Zweispalter: Hauptbereich + rechte Sidebar (Live-Preview etc.) */
.gx-split {
  display: grid;
  grid-template-columns: 1fr var(--sidebar-w);
  gap: var(--sp-4);
  align-items: start;
}
.gx-split__side { position: sticky; top: var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-4); }
.gx-split__main { display: flex; flex-direction: column; gap: var(--sp-4); min-width: 0; }

/* Aktionszeile über/unter Tabellen (Buttons, Zähler) */
.gx-toolbar {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin: var(--sp-3) 0;
  flex-wrap: wrap;
}
.gx-toolbar__spacer { flex: 1; }

/* ------------------------------------------------------------
   4. NAVIGATION (Header)
   ------------------------------------------------------------ */
header {
  background: var(--gralax-blue);
  border-bottom: 3px solid var(--gralax-gold);
  position: sticky;
  top: 0;
  z-index: 1000;
}

nav ul {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-1);
  margin: 0;
  padding: var(--sp-2) var(--sp-4);
}
nav ul li { position: relative; }
nav ul li > a {
  display: block;
  padding: var(--sp-2) var(--sp-3);
  color: var(--text-light);
  text-decoration: none;
  font-weight: 500;
  font-size: var(--fs-base);
  border-radius: var(--rad-sm);
  white-space: nowrap;
  transition: background var(--ease), color var(--ease);
}
nav ul li > a:hover {
  background: var(--gralax-gold);
  color: var(--gralax-black);
}
nav ul li.active > a {
  background: var(--gralax-white);
  color: var(--gralax-blue);
}

/* Dropdown */
nav ul li ul {
  display: none;
  position: absolute;
  left: 0;
  top: 100%;
  min-width: 240px;
  background: var(--bg-panel);
  border: 1px solid var(--border-light);
  border-radius: var(--rad-md);
  box-shadow: var(--shadow-md);
  padding: var(--sp-1);
  flex-direction: column;
  gap: 0;
  z-index: 1001;
}
nav ul li:hover > ul { display: flex; }
/* button: the logout POST form (#471) looks like the other entries */
nav ul li ul li a,
nav ul li ul li button {
  display: block;
  width: 100%;
  padding: var(--sp-2) var(--sp-3);
  color: var(--text-primary);
  background: none;
  border: 0;
  border-radius: var(--rad-sm);
  font: inherit;
  font-weight: 400;
  text-align: left;
  cursor: pointer;
}
nav ul li ul li a:hover,
nav ul li ul li button:hover {
  background: var(--gralax-gold);
  color: var(--gralax-black);
}

/* ------------------------------------------------------------
   5. PANELS
   ------------------------------------------------------------ */
.gx-panel {
  background: var(--bg-panel);
  border: 1px solid var(--border-light);
  border-radius: var(--rad-md);
  padding: var(--sp-4);
}
.gx-panel__title {
  margin: 0 0 var(--sp-3);
  padding-bottom: var(--sp-2);
  font-size: var(--fs-lg);
  font-weight: 600;
  color: var(--text-primary);
  border-bottom: 2px solid var(--gralax-gold);
}
/* Eingelassene Variante (z.B. Kategorien-Container) */
.gx-panel--sunken {
  background: var(--bg-sunken);
  border: none;
}
/* Ergebnis-/Preis-Panel: gleiche Fläche, goldene Oberkante */
.gx-panel--result { border-top: 3px solid var(--gralax-gold); }

/* Große Preisanzeige im Ergebnis-Panel */
.gx-price {
  text-align: center;
  padding: var(--sp-4) 0;
}
.gx-price__value {
  font-size: var(--fs-price);
  font-weight: bold;
  color: var(--gralax-blue);
  line-height: 1.1;
}
.gx-price__label {
  font-size: var(--fs-base);
  color: var(--text-muted);
  margin-top: var(--sp-1);
}

/* ------------------------------------------------------------
   6. TABELLEN
   ------------------------------------------------------------
   Grundgerüst:
     <table class="gx-table"> … </table>
   Modifier:
     .gx-table--compact   dichter (Massendaten)
     .gx-table--edit      Inputs in Zellen
     .gx-table--plain     ohne Zebra
     .gx-table--sticky    Kopfzeile bleibt beim Scrollen sichtbar
   ------------------------------------------------------------ */
.gx-table {
  border-collapse: collapse;
  width: 100%;
  margin: var(--sp-3) 0;
  background: var(--bg-table);
  border: 1px solid var(--border-light);
  border-radius: var(--rad-md);
  font-size: var(--fs-base);
  table-layout: fixed;
}

.gx-table th,
.gx-table td {
  border: 1px solid var(--border-light);
  padding: 6px 10px;
  text-align: left;
  vertical-align: top;
  overflow-wrap: break-word;
}

.gx-table th {
  background: var(--bg-table-alt);
  font-weight: 600;
  color: var(--text-secondary);
  border-bottom: 2px solid var(--gralax-gold);
  height: auto;               /* KEINE fixen Header-Höhen mehr */
}

/* Zebra (Standard an) + Hover. Kind-Kombinatoren, damit eine Tabelle in
   einer .gx-subrow nicht die Zebra-/Hover-Faerbung ihrer Elternzeile auf
   alle ihre Zellen vererbt bekommt (#412). */
.gx-table > tbody > tr:nth-child(even) > td { background: var(--bg-table-alt); }
.gx-table--plain > tbody > tr:nth-child(even) > td { background: var(--bg-table); }
.gx-table > tbody > tr:hover > td { background: var(--bg-table-hover); }

/* Kompakt-Variante für Massendaten */
.gx-table--compact { font-size: var(--fs-sm); }
.gx-table--compact th,
.gx-table--compact td { padding: 3px 8px; }

/* Sticky-Kopfzeile */
.gx-table--sticky th { position: sticky; top: 0; z-index: 2; }

/* Sortierbare Spaltenköpfe */
.gx-th--sort { cursor: pointer; user-select: none; }
.gx-th--sort:hover { background: var(--bg-table-hover); }
.gx-th--sort a { color: inherit; text-decoration: none; display: block; }
.gx-th--sort .gx-sort-arrow { margin-left: var(--sp-1); color: var(--gralax-gold); }

/* Rotierte Spaltenköpfe — Opt-in für Matrix-Tabellen */
.gx-table--rotated th { height: 100px; padding: 0; vertical-align: bottom; }
.gx-table--rotated th > div {
  transform: rotate(-30deg);
  transform-origin: bottom left;
  white-space: nowrap;
  padding: 0 0 var(--sp-2) var(--sp-3);
}

/* Spaltenbreiten (ersetzt .breit/.normal/.schmal/.mini) */
.gx-w-xl { width: 16em; }
.gx-w-lg { width: 12em; }
.gx-w-md { width: 10em; }
.gx-w-sm { width: 7em; }
.gx-w-xs { width: 2.5em; }

/* Zell-Ausrichtung */
.gx-num { text-align: right; font-variant-numeric: tabular-nums; }
.gx-center { text-align: center; }
/* In Tabellenzellen brauchen die Utilities einen Selektor mit Element:
   ".gx-table th, .gx-table td { text-align: left }" hat (0,1,1) und schlaegt
   sonst .gx-num (0,1,0) — Zahlen stuenden trotz Klasse links. */
.gx-table th.gx-num,    .gx-table td.gx-num    { text-align: right; }
.gx-table th.gx-center, .gx-table td.gx-center { text-align: center; }

/* Zeilenkopf: <th scope="row"> als erste Zelle einer Datenzeile. Die
   goldene Unterkante gehoert der Kopfzeile — in jeder Zeile wuerde sie
   zum Zaun. Ausrichtung bleibt links wie bei jeder th. */
.gx-table tbody th[scope='row'] {
  border-bottom: 1px solid var(--border-light);
  background: var(--bg-table-alt);
}

/* Editierbare Zellen (Klick-zu-editieren) */
.gx-cell--edit {
  background: var(--bg-editable) !important;
  cursor: pointer;
}
.gx-cell--edit:hover { background: var(--bg-editable-hover) !important; }

/* Inputs in Tabellenzellen */
.gx-table--edit input,
.gx-table--edit select,
.gx-table--edit textarea {
  width: 100%;
  box-sizing: border-box;
  padding: 3px 6px;
  font-size: inherit;
  font-family: inherit;
  border: 1px solid var(--border-field);
  border-radius: var(--rad-sm);
  background: var(--bg-field);
}
.gx-table--edit input:focus,
.gx-table--edit select:focus,
.gx-table--edit textarea:focus {
  outline: none;
  border-color: var(--border-focus);
  background: var(--bg-field-focus);
}

/* Checkboxen behalten ihre intrinsische Groesse - width:100% aus der
   Input-Regel oben wuerde sie in Flex-Zeilen (.gx-check in Zellen)
   strecken und den Label-Text an den Zellenrand druecken. */
.gx-table--edit input[type="checkbox"] { width: auto; }

/* Mehrere Felder in EINER Zelle (z.B. Maschinen-Parameter,
   Materialübersicht: Kategorie+Unterkategorie, Name+Stärke, X+Y …)
   Ohne Label: Inputs einfach direkt stapeln.
   Mit Label:  .gx-cell-row (Label links) oder
               .gx-cell-field (Mini-Label über dem Input, wie 'Einrichtung (€):') */
.gx-cell-stack { display: flex; flex-direction: column; gap: var(--sp-1); }
.gx-cell-row { display: flex; align-items: center; gap: var(--sp-1); }
.gx-cell-row > label {
  flex: 0 0 auto;
  font-size: var(--fs-xs);
  color: var(--text-muted);
  min-width: 5em;
}
.gx-cell-field > label {
  display: block;
  font-size: var(--fs-xs);
  color: var(--text-muted);
  margin-bottom: 1px;
  white-space: nowrap;
}

/* Aufklapp-Zeile: verknüpfter Detailbereich unter einer Datenzeile
   <tr class="gx-subrow"><td colspan="N"> … </td></tr>              */
.gx-subrow > td {
  background: var(--bg-sunken) !important;
  padding: var(--sp-4);
  border-top: 2px solid var(--gralax-gold);
}

/* Zeilen-Status */
.gx-row--invalid td { background: var(--bg-error-light) !important; }
.gx-row--highlight td { background: var(--bg-warning-light) !important; font-weight: 600; }
.gx-row--best td { background: var(--bg-success-light) !important; font-weight: 600; }
/* Geänderte, ungespeicherte Zeile (Sammel-Speichern-Muster) */
.gx-row--dirty td { background: var(--bg-warning-light) !important; }
.gx-row--dirty td:first-child { border-left: 3px solid var(--color-warning); }

/* Schlüssel-Wert-Tabelle (Ergebnisse, Zusammenfassungen)
   <table class="gx-kv"><tr><th>Label</th><td>Wert</td></tr>…</table> */
.gx-kv {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-base);
}
.gx-kv th,
.gx-kv td {
  padding: var(--sp-2) var(--sp-3);
  border-bottom: 1px solid var(--border-light);
  text-align: left;
}
.gx-kv th { width: 40%; color: var(--text-secondary); font-weight: 500; }
.gx-kv td { text-align: right; font-weight: 600; }
.gx-kv tr:last-child th,
.gx-kv tr:last-child td { border-bottom: none; }
.gx-kv .gx-kv__total th,
.gx-kv .gx-kv__total td {
  border-top: 2px solid var(--gralax-gold);
  background: var(--bg-info-light);
  font-weight: bold;
  font-size: var(--fs-md);
}

/* ------------------------------------------------------------
   7. FORMULARE
   ------------------------------------------------------------ */
/* Feld = Label + Input + optionaler Hinweis */
.gx-field { min-width: 0; }
/* :not(.gx-check), weil .gx-field > label (0,1,1) sonst .gx-check (0,1,0)
   schlaegt: eine Checkbox-Zeile direkt im Feld wuerde als Feld-Label
   gerendert, das display:flex ginge verloren und mit ihm der gap. */
.gx-field > label:not(.gx-check) {
  display: block;
  margin-bottom: var(--sp-1);
  font-weight: 600;
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}
.gx-field > small {
  display: block;
  margin-top: var(--sp-1);
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

.gx-field input,
.gx-field select,
.gx-field textarea {
  width: 100%;
  box-sizing: border-box;
  padding: 5px 8px;
  font-size: var(--fs-base);
  font-family: inherit;
  color: var(--text-primary);
  background: var(--bg-field);
  border: 1px solid var(--border-field);
  border-radius: var(--rad-sm);
  transition: border-color var(--ease), background var(--ease);
}
.gx-field input:hover,
.gx-field select:hover,
.gx-field textarea:hover { border-color: var(--border-medium); }
.gx-field input:focus,
.gx-field select:focus,
.gx-field textarea:focus {
  outline: none;
  border-color: var(--border-focus);
  background: var(--bg-field-focus);
  box-shadow: 0 0 0 2px rgba(var(--rgb-gold), 0.2);
}
.gx-field input:disabled,
.gx-field select:disabled {
  background: var(--bg-sunken);
  color: var(--text-muted);
  cursor: not-allowed;
}
.gx-field input[type='checkbox'],
.gx-field input[type='radio'] { width: auto; }

/* Formular-Raster */
.gx-grid { display: grid; gap: var(--sp-3); }
.gx-grid > * { min-width: 0; }
.gx-grid.cols-1 { grid-template-columns: 1fr; }
.gx-grid.cols-2 { grid-template-columns: repeat(2, 1fr); }
.gx-grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
.gx-grid.cols-4 { grid-template-columns: repeat(4, 1fr); }
.gx-grid.cols-5 { grid-template-columns: repeat(5, 1fr); }
.gx-grid.cols-6 { grid-template-columns: repeat(6, 1fr); }
/* Nur das ENDE besetzen: die Kurzform "grid-column: span N" setzt den
   START auf "span N" und macht .gx-row-start (13b) wirkungslos. Ohne
   .gx-row-start verhalten sich beide Schreibweisen identisch. */
.gx-span-2 { grid-column-end: span 2; }
.gx-span-3 { grid-column-end: span 3; }
.gx-span-4 { grid-column-end: span 4; }
.gx-full { grid-column: 1 / -1; }

/* Checkbox-/Radio-Zeile */
.gx-check {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-base);
  cursor: pointer;
}
.gx-check input { margin: 0; }

/* Filterleiste über Tabellen */
.gx-filter {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  align-items: flex-end;
  padding: var(--sp-3);
  background: var(--bg-sunken);
  border-radius: var(--rad-md);
  margin-bottom: var(--sp-3);
}
.gx-filter .gx-field { min-width: 180px; flex: 0 1 auto; }

/* ------------------------------------------------------------
   8. BUTTONS
   ------------------------------------------------------------
   <button class="gx-btn">Standard</button>
   Varianten: --primary  --danger  --ghost  --sm
   ------------------------------------------------------------ */
.gx-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 6px 14px;
  font-size: var(--fs-base);
  font-family: inherit;
  font-weight: 500;
  color: var(--text-primary);
  background: var(--bg-panel);
  border: 1px solid var(--border-medium);
  border-radius: var(--rad-sm);
  cursor: pointer;
  transition: background var(--ease), border-color var(--ease), color var(--ease);
}
.gx-btn:hover { border-color: var(--gralax-gold); background: var(--bg-editable); }

.gx-btn--primary {
  background: var(--gralax-blue);
  border-color: var(--gralax-blue);
  color: var(--text-light);
}
.gx-btn--primary:hover {
  background: var(--gralax-gold);
  border-color: var(--gralax-gold);
  color: var(--gralax-black);
}

.gx-btn--danger {
  background: var(--color-error);
  border-color: var(--color-error);
  color: #fff;
}
.gx-btn--danger:hover { background: #c53030; border-color: #c53030; color: #fff; }

.gx-btn--ghost {
  background: transparent;
  border-color: transparent;
  color: var(--text-link);
}
.gx-btn--ghost:hover { background: var(--bg-info-light); border-color: transparent; }

.gx-btn--sm { padding: 3px 8px; font-size: var(--fs-sm); }

.gx-btn:disabled,
.gx-btn--primary:disabled {
  background: var(--gralax-lightgray);
  border-color: var(--gralax-lightgray);
  color: var(--text-muted);
  cursor: not-allowed;
}

/* ------------------------------------------------------------
   9. TABS
   ------------------------------------------------------------ */
.gx-tabs {
  border: 1px solid var(--border-light);
  border-radius: var(--rad-md);
  overflow: hidden;
  margin-top: var(--sp-4);
  background: var(--bg-panel);
}
.gx-tabs__bar {
  display: flex;
  background: var(--bg-sunken);
  border-bottom: 1px solid var(--border-light);
  overflow-x: auto;
  white-space: nowrap;
}
.gx-tab {
  padding: var(--sp-2) var(--sp-4);
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  font-family: inherit;
  font-size: var(--fs-base);
  font-weight: 600;
  color: var(--text-muted);
  cursor: pointer;
  transition: color var(--ease), border-color var(--ease);
}
.gx-tab:hover { color: var(--text-primary); }
.gx-tab.active {
  color: var(--gralax-blue);
  border-bottom-color: var(--gralax-gold);
  background: var(--bg-panel);
}
.gx-tabs__pane { display: none; padding: var(--sp-4); }
.gx-tabs__pane.active { display: block; }

/* ------------------------------------------------------------
   10. AUFKLAPP-SEKTIONEN (CSS-only, Checkbox-Pattern)
   ------------------------------------------------------------
   <div class="gx-collapse">
     <input type="checkbox" id="x" class="gx-collapse__toggle">
     <label for="x" class="gx-collapse__header">Titel</label>
     <div class="gx-collapse__body"> … </div>
   </div>
   ------------------------------------------------------------ */
.gx-collapse { margin: var(--sp-2) 0; position: relative; }
/* Visuell versteckt, aber fokussierbar — display:none würde die Checkbox
   aus Tab-Reihenfolge und Accessibility-Tree entfernen, Tastaturnutzer
   könnten die Sektion nie öffnen. Fokus wird am Header sichtbar gemacht. */
.gx-collapse__toggle {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}
.gx-collapse__toggle:focus-visible ~ .gx-collapse__header {
  outline: 2px solid var(--border-focus);
  outline-offset: 1px;
}
.gx-collapse__header {
  display: block;
  padding: var(--sp-2) var(--sp-3);
  background: var(--bg-sunken);
  border: 1px solid var(--border-light);
  border-radius: var(--rad-md);
  font-weight: 600;
  font-size: var(--fs-base);
  color: var(--text-secondary);
  cursor: pointer;
  position: relative;
  transition: background var(--ease);
}
.gx-collapse__header::after {
  content: '▾';
  position: absolute;
  right: var(--sp-3);
  transition: transform var(--ease);
}
.gx-collapse__header:hover { background: var(--bg-table-hover); }
.gx-collapse__body {
  display: none;
  padding: var(--sp-4);
  border: 1px solid var(--border-light);
  border-top: none;
  border-radius: 0 0 var(--rad-md) var(--rad-md);
  background: var(--bg-panel);
}
.gx-collapse__toggle:checked ~ .gx-collapse__body { display: block; }
.gx-collapse__toggle:checked ~ .gx-collapse__header {
  border-radius: var(--rad-md) var(--rad-md) 0 0;
}
.gx-collapse__toggle:checked ~ .gx-collapse__header::after { transform: rotate(180deg); }

/* ------------------------------------------------------------
   11. MELDUNGEN: ALERTS & TOASTS
   ------------------------------------------------------------ */
.gx-alert {
  padding: var(--sp-3);
  border-radius: var(--rad-md);
  border: 1px solid var(--border-light);
  font-size: var(--fs-base);
  margin: var(--sp-3) 0;
}
.gx-alert--success { background: var(--bg-success-light); border-color: var(--color-success); color: var(--text-success); }
.gx-alert--error   { background: var(--bg-error-light);   border-color: var(--color-error);   color: var(--text-error); }
.gx-alert--warning { background: var(--bg-warning-light); border-color: var(--color-warning); color: var(--text-warning); }
.gx-alert--info    { background: var(--bg-info-light);    border-color: var(--color-info);    color: var(--text-info); }

.gx-toast {
  position: fixed;
  bottom: 20px;
  right: 20px;
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--rad-md);
  color: #fff;
  font-size: var(--fs-base);
  box-shadow: var(--shadow-md);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s;
  z-index: 2000;
}
.gx-toast.show { opacity: 1; pointer-events: auto; }
.gx-toast--success { background: var(--color-success); }
.gx-toast--error   { background: var(--color-error); }
.gx-toast--info    { background: var(--color-info); }
/* Dark text like .gx-alert--warning: white on --color-warning is too low in contrast */
.gx-toast--warning { background: var(--bg-warning-light); color: var(--text-warning); border: 1px solid var(--color-warning); }

/* ------------------------------------------------------------
   11b. SPEICHERN-MUSTER
   ------------------------------------------------------------
   Verbindlich: pro Seite genau EIN Muster.
   A) Sammel-Speichern (Editier-Tabellen, mehrere Felder/Datensatz):
      geänderte Zeilen -> .gx-row--dirty, Leiste .gx-savebar wird
      sichtbar (.show), Button speichert ALLE Änderungen.
      Zeilen-Buttons „Speichern“ pro Zeile entfallen.
   B) Auto-Save (einzelne Checkboxen/Dropdowns, Einstellungen):
      speichert sofort, Status über .gx-savestate.
   ------------------------------------------------------------ */
.gx-savebar {
  position: sticky;
  bottom: 0;
  z-index: 100;
  display: none;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-4);
  background: var(--bg-panel);
  border: 1px solid var(--border-light);
  border-top: 3px solid var(--color-warning);
  border-radius: var(--rad-md) var(--rad-md) 0 0;
  box-shadow: var(--shadow-md);
}
.gx-savebar.show { display: flex; }
.gx-savebar__count {
  font-weight: 600;
  font-size: var(--fs-base);
  color: var(--text-secondary);
}
.gx-savebar__count::before {
  content: '';
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--color-warning);
  margin-right: var(--sp-2);
}
.gx-savebar__spacer { flex: 1; }

/* Auto-Save-Statusanzeige (neben Titel oder Feld) */
.gx-savestate {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
.gx-savestate::before {
  content: '';
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--gralax-lightgray);
}
.gx-savestate--saving { color: var(--text-muted); }
.gx-savestate--saving::before { background: var(--color-warning); }
.gx-savestate--saved { color: var(--color-success); }
.gx-savestate--saved::before { background: var(--color-success); }
.gx-savestate--error { color: var(--color-error); font-weight: 600; }
.gx-savestate--error::before { background: var(--color-error); }

/* ------------------------------------------------------------
   12. BADGES & STATUS
   ------------------------------------------------------------ */
.gx-badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: var(--rad-sm);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  font-weight: bold;
  line-height: 1.5;
}
.gx-badge--gold    { background: var(--gralax-gold); color: var(--gralax-black); }   /* Angebotsnr. */
.gx-badge--muted   { background: var(--gralax-lightgray); color: var(--gralax-darkgray); } /* IDs */
.gx-badge--success { background: var(--bg-success-light); color: var(--color-success); }
.gx-badge--error   { background: var(--bg-error-light); color: var(--color-error); }
.gx-badge--info    { background: var(--bg-info-light); color: var(--gralax-blue); }

/* Status-Punkt (ersetzt Emoji-Indikatoren 🔴🟡⚪) */
.gx-dot {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  margin-right: var(--sp-1);
  vertical-align: baseline;
  border: 1px solid transparent;
}
.gx-dot--mandatory   { background: var(--color-error); }
.gx-dot--recommended { background: var(--color-warning); }
.gx-dot--optional    { background: var(--bg-panel); border-color: var(--border-medium); }

/* Listen-Item mit Status-Randfarbe (Handling, Addons) */
.gx-item {
  display: flex;
  /* wrap + flex-basis am __desc: die Beschreibung bekommt eine eigene Zeile,
     alles andere (Dots, Checkboxen, Buttons) bleibt daneben. */
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  background: var(--bg-panel);
  border: 1px solid var(--border-light);
  border-left: 3px solid var(--border-light);
  border-radius: var(--rad-sm);
  font-size: var(--fs-base);
}
.gx-item--mandatory   { border-left-color: var(--color-error); }
.gx-item--recommended { border-left-color: var(--color-warning); }
.gx-item--optional    { border-left-color: var(--border-medium); }
.gx-item--selected    { border-color: var(--gralax-gold); background: var(--bg-editable); }
.gx-item__desc {
  /* Als Flex-Kind von .gx-item erzwingt flex-basis die eigene Zeile; das
     display deckt die Faelle ab, in denen das Element ausserhalb steht.
     Ohne beides bleibt ein <span> neben dem Namen und margin-top verpufft. */
  flex-basis: 100%;
  display: block;
  font-size: var(--fs-xs);
  color: var(--text-muted);
  margin-top: 2px;
}

/* Karten-Raster für Item-Sammlungen (Addons, Materialien, Kategorien) */
.gx-item-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: var(--sp-2);
}

/* ------------------------------------------------------------
   13. UTILITIES
   ------------------------------------------------------------ */
.gx-muted  { color: var(--text-muted); }
.gx-mono   { font-family: var(--font-mono); }
.gx-right  { text-align: right; }
.gx-nowrap { white-space: nowrap; }
.gx-currency { font-weight: 600; color: var(--gralax-blue); font-variant-numeric: tabular-nums; }
.gx-placeholder {
  color: var(--text-muted);
  font-style: italic;
  text-align: center;
  padding: var(--sp-3);
}
.gx-disabled { opacity: 0.5; pointer-events: none; }

/* ------------------------------------------------------------
   13b. ERGÄNZUNGEN (generisch, token-basiert)
   ------------------------------------------------------------
   Regel aus #139: fehlt ein generischer Baustein, wird er HIER
   ergänzt — nie in einer View. Erstnutzer in Klammern.
   ------------------------------------------------------------ */
/* Split mit Sidebar LINKS (Master-Detail: Liste links, Editor
   rechts). DOM-Reihenfolge: __side vor __main. (?view=presets) */
.gx-split--side-left { grid-template-columns: var(--sidebar-w) 1fr; }

/* Scrollende Checkbox-Liste für lange Auswahlmengen — Inhalt sind
   .gx-check-Zeilen. (?view=presets: Whitelists) */
.gx-checklist {
  max-height: 16rem;
  overflow-y: auto;
  border: 1px solid var(--border-light);
  border-radius: var(--rad-sm);
  background: var(--bg-panel);
  padding: var(--sp-1) var(--sp-2);
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}

/* .gx-item als klickbares Element (button/a) in Auswahllisten —
   volle Breite, linksbündig, Auswahlzustand via .gx-item--selected.
   (?view=presets: Preset-Liste) */
.gx-item--action {
  width: 100%;
  text-align: left;
  font: inherit;
  /* Landet auf einem <button>: ohne eigenes color greift buttontext aus dem
     UA-Stylesheet, der Wert waere nicht token-basiert (Regel 2). */
  color: inherit;
  cursor: pointer;
  transition: border-color var(--ease), background var(--ease);
}
.gx-item--action:hover { border-color: var(--gralax-gold); }

/* Vertikaler Stapel gleichrangiger Bloecke (mehrere .gx-panel
   untereinander) mit einheitlichem Abstand — .gx-page selbst hat
   bewusst kein gap. (?view=change_password) */
.gx-stack { display: flex; flex-direction: column; gap: var(--sp-4); }

/* Lange, umbruchlose Zeichenketten (API-Tokens, Hashes) im Fluss
   halten, statt den Container zu sprengen. (?view=change_password) */
.gx-break { overflow-wrap: anywhere; }

/* Markup-/Code-Beispiel im Fluss. Eigener Baustein statt <pre> nackt,
   damit Doku-Seiten nicht anfangen, sich Stile selbst zu bauen.
   (?view=styleguide) */
.gx-code {
  margin: var(--sp-2) 0;
  padding: var(--sp-3);
  background: var(--bg-sunken);
  border: 1px solid var(--border-light);
  border-radius: var(--rad-sm);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--text-secondary);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  tab-size: 2;
}

/* Debug-Ausgabe (nur bei DEBUG=1). Ersetzt die Klasse .debug-output, fuer die
   es nie eine CSS-Regel gab. gralax-ui.css laedt in jedem Asset-Set, die
   Ausgabe ist damit auf 'legacy'- und 'gx'-Seiten gleich gestylt.
   (jede Seite, wenn DEBUG=1) */
.gx-debug {
  margin: var(--sp-4) 0;
  padding: var(--sp-3);
  background: var(--bg-sunken);
  border: 1px solid var(--border-light);
  border-left: 3px solid var(--gralax-gold);
  border-radius: var(--rad-md);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--text-secondary);
  overflow-x: auto;
}
.gx-debug pre { margin: 0; white-space: pre-wrap; }
.gx-debug h3 { margin: var(--sp-2) 0 var(--sp-1); font-size: var(--fs-sm); }

/* Sidebar, deren Inhalt normal mitscrollt. .gx-split__side ist per
   Default sticky; in einer Spalte, in der Felder bedient werden, geht
   das nicht auf: ein klebender Block behaelt seinen Platz im Fluss und
   wandert beim Scrollen ueber seine Geschwister hinweg — alles unter
   ihm waere verdeckt. (?view=kalkulation_extern) */
.gx-split__side--flow { position: static; }

/* Dichte Seite: Werkzeugseiten mit sehr vielen Feldern. Statt jede
   Komponente einzeln enger zu stellen, wird die Abstands-Skala
   heruntergesetzt — alle gx-Komponenten rechnen mit diesen Tokens.
   (?view=kalkulation_extern) */
.gx-page--dense {
  --sp-3: 8px;
  /* --sp-0/1/2 bleiben, sonst verlieren die dichten Listen ihre Lesbarkeit */
  --sp-4: 10px;
  --sp-5: 14px;
}
.gx-page--dense h2 { margin: var(--sp-3) 0 var(--sp-2); }
.gx-page--dense h3 { margin: var(--sp-2) 0 var(--sp-1); }

/* Senkrechte Liste gleichrangiger .gx-item — Pendant zu .gx-item-grid.
   (?view=kalkulation_extern: Arbeitsschritte) */
.gx-item-list { display: flex; flex-direction: column; gap: var(--sp-1); }

/* Dichte Variante von .gx-item fuer lange Auswahllisten. Das Mengenfeld
   bestimmt die Zeilenhoehe, deshalb muss es mit verkleinert werden —
   sonst bringt das kleinere Polster nichts.
   (?view=kalkulation_extern: Addons, Arbeitsschritte) */
.gx-item--compact { padding: var(--sp-0) var(--sp-2); gap: var(--sp-1); font-size: var(--fs-sm); }
.gx-item--compact .gx-item__desc { margin-top: 0; }
.gx-item--compact .gx-field input { padding: var(--sp-0) var(--sp-1); font-size: var(--fs-sm); }

/* Erzwingt eine eigene Zeile innerhalb einer .gx-item (Preis,
   Zusatzangabe). .gx-item ist ein flex-wrap-Container; ohne flex-basis
   haengen die Angaben unterschiedlich weit hinter dem Namen.
   (?view=kalkulation_extern: Addon-Preis) */
.gx-item__line { flex-basis: 100%; }

/* Eingabefeld mit fester Breite statt 100 % — Mengen in Item-Zeilen.
   Ohne .gx-field bliebe das <input> ungestylt (UA-Stylesheet): gx stylt
   Inputs nur unter .gx-field und .gx-table--edit.
   (?view=kalkulation_extern: Menge je Addon/Arbeitsschritt) */
.gx-field--narrow input { width: 4em; }

/* Pflichtfeld-Markierung. (?view=kalkulation_extern: Adressfelder, die
   je nach Dokumenttyp Pflicht sind — export_mark_offer, #123) */
.gx-field--required > label::after {
  content: ' *';
  color: var(--color-error);
  font-weight: bold;
}

/* Beginnt eine neue Rasterzeile, auch wenn rechts noch Platz waere —
   haelt zusammengehoerige Feldgruppen zusammen (Anrede/Titel gegen
   Vorname/Nachname). Setzt nur den Start, die Breite kommt weiter von
   .gx-span-*. (?view=kalkulation_extern: Adressfelder) */
.gx-row-start { grid-column-start: 1; }

/* Breite Datentabelle im eigenen Scrollport — viele Spalten (Roh-
   ausgabe einer DB-View) scrollen horizontal statt sich in die Seite
   zu quetschen. Der Wrapper braucht eine Hoehe, sonst wird er zum
   Scrollport ohne Bewegung und .gx-table--sticky im Inneren klebt an
   nichts; die Tabelle braucht table-layout: auto, sonst kann sie den
   Wrapper nie ueberragen. (?view=sql_views) */
.gx-table-scroll { max-height: 70vh; overflow: auto; }
.gx-table-scroll .gx-table {
  table-layout: auto;
  width: max-content;
  min-width: 100%;
  margin: 0;
}

/* Aufklappbare Datenzeile: ein Klick auf die Zeile oeffnet oder schliesst
   eine .gx-subrow direkt darunter (Angebot -> Positionen). Der Zeiger
   sagt, dass die Zeile reagiert; Schaltflaechen in der Zeile bleiben
   davon unberuehrt. (?view=angebots_verwaltung) */
.gx-row--expand { cursor: pointer; }
.gx-row--expand.open > td { background: var(--bg-table-hover); }

/* Tabelle innerhalb einer Subrow (Positionen unter einem Angebot): der
   Aussenabstand der .gx-table addiert sich sonst zum Polster der Subrow.
   (?view=angebots_verwaltung) */
.gx-subrow .gx-table { margin: 0; }

/* Aktionsmenue: eine Schaltflaeche (⋮), die eine Liste von Aktionen
   aufklappt — fuer Zeilen mit mehr Aktionen, als in eine Zelle passen.
   Aufgeklappt wird per JS ueber das hidden-Attribut der Liste:
     <span class="gx-menu">
       <button class="gx-btn gx-btn--sm gx-menu__toggle">⋮</button>
       <div class="gx-menu__list" hidden> <button class="gx-btn gx-btn--sm">…</button> … </div>
     </span>
   Gleiches Muster wie das Navigations-Dropdown (Abschnitt 4), nur
   klick- statt hover-gesteuert. (?view=angebots_verwaltung) */
.gx-menu { position: relative; display: inline-block; }
.gx-menu__list {
  position: absolute;
  right: 0;
  top: 100%;
  min-width: 200px;
  padding: var(--sp-1);
  background: var(--bg-panel);
  border: 1px solid var(--border-light);
  border-radius: var(--rad-md);
  box-shadow: var(--shadow-md);
  z-index: 100;
  text-align: left;
}
.gx-menu__list > .gx-btn {
  display: flex;
  width: 100%;
  justify-content: flex-start;
  margin: 0;
  background: transparent;
  border-color: transparent;
  white-space: nowrap;
}
.gx-menu__list > .gx-btn:hover { background: var(--gralax-gold); color: var(--gralax-black); }
.gx-menu__list > .gx-btn--danger { color: var(--color-error); }
.gx-menu__list > .gx-btn:disabled { background: transparent; border-color: transparent; }
/* Bestaetigungsdialog auf dem nativen <dialog>-Element — Ersatz fuer
   confirm(). Geoeffnet per dialog.showModal(); ein <form method="dialog">
   darin schliesst ueber die Buttons und liefert deren value als
   dialog.returnValue ("confirm"/"cancel"). Das Element steht INNERHALB
   von .gx-page, damit es die Tokens erbt (Top-Layer erbt per DOM).
   (?view=categories) */
.gx-dialog {
  width: min(28rem, calc(100vw - 2 * var(--sp-5)));
  padding: 0;
  border: 1px solid var(--border-light);
  border-radius: var(--rad-md);
  background: var(--bg-panel);
  color: var(--text-primary);
  box-shadow: var(--shadow-md);
}
.gx-dialog::backdrop { background: rgba(var(--rgb-black), 0.4); }
.gx-dialog__body { display: flex; flex-direction: column; gap: var(--sp-3); padding: var(--sp-5); }
.gx-dialog__title { margin: 0; font-size: var(--fs-lg); }
.gx-dialog__text { margin: 0; color: var(--text-secondary); }
.gx-dialog__actions { display: flex; justify-content: flex-end; gap: var(--sp-2); margin-top: var(--sp-2); }

/* Eingebettete eigenständige Seite (iframe), z. B. ein 3D-Viewer:
   volle Breite, Höhe am Fenster ausgerichtet. (?view=packrechner) */
.gx-embed {
  display: block;
  width: 100%;
  height: 60vh;
  min-height: 20rem;
  border: 1px solid var(--border-light);
  border-radius: var(--rad-md);
  background: var(--bg-panel);
}

/* ------------------------------------------------------------
   14. RESPONSIVE (Desktop-Tool; nur grobe Sicherung < 1024px)
   ------------------------------------------------------------ */
@media (max-width: 1023px) {
  .gx-split { grid-template-columns: 1fr; }
  .gx-split--side-left { grid-template-columns: 1fr; }
  .gx-split__side { position: static; }
  .gx-grid.cols-3, .gx-grid.cols-4, .gx-grid.cols-5, .gx-grid.cols-6 { grid-template-columns: repeat(2, 1fr); }
}
