:root {
  --bg: #f6f6f7;
  --bg-elevated: #ffffff;
  --fg: #1c1d20;
  --fg-muted: #5e6470;
  --border: #d8dadd;
  --accent: #1f6feb;
  --accent-fg: #ffffff;
  --danger: #c0392b;
  --warning: #d28e00;
  --ok: #2c8a3e;
  --violet: #7c3aed;

  /* Rechtematrix: Vorgabe blau, Schreiben grün, nur lesen gelb, unsichtbar rot.
     Eigene Werte statt der semantischen Statusfarben: Die sind für Meldungen
     gedacht und wirken auf 20 Pixel zu blass. */
  --acl-def: #808080;
  --acl-rw: #16a34a;
  --acl-ro: #eab308;
  --acl-hidden: #dc2626;
  --shadow: 0 1px 3px rgba(0, 0, 0, .07);
  --shadow-tile: 0 2px 10px rgba(0, 0, 0, .14);
  --radius: 6px;
  --statusbar-h: 48px;
  --menu-w: 220px;
  --menu-rail-w: 56px;   /* collapsed icon rail */
  --menu-col: var(--menu-w);   /* effective menu column width; → rail via [data-menu="rail"] */
  --actions-w: 280px;
  /* type scale */
  --fs-11: 11px; --fs-12: 12px; --fs-13: 13px; --fs-14: 14px; --fs-15: 15px; --fs-16: 16px;
  /* spacing scale */
  --space-2: 2px; --space-4: 4px; --space-6: 6px; --space-8: 8px;
  --space-10: 10px; --space-12: 12px; --space-16: 16px; --space-24: 24px;
}

:root[data-theme="dark"] {
  --bg: #15171b;
  --bg-elevated: #1e2026;
  --fg: #ecedef;
  --fg-muted: #aab0bb;
  --border: #2c2f36;
  --accent: #5b8def;
  --accent-fg: #0f1115;
  --violet: #a78bfa;

  --acl-def: #808080;
  --acl-rw: #22c55e;
  --acl-ro: #facc15;
  --acl-hidden: #f87171;
  --shadow: 0 1px 3px rgba(0, 0, 0, .4);
  --shadow-tile: 0 3px 12px rgba(0, 0, 0, .6);
}

/* Same values as [data-theme="dark"] above — plain CSS cannot share custom
   properties across a media-query boundary, so the palette is duplicated on
   purpose. Keep both blocks in sync. */
@media (prefers-color-scheme: dark) {
  :root[data-theme="system"] {
    --bg: #15171b;
    --bg-elevated: #1e2026;
    --fg: #ecedef;
    --fg-muted: #aab0bb;
    --border: #2c2f36;
    --accent: #5b8def;
    --accent-fg: #0f1115;
    --violet: #a78bfa;

    --acl-def: #808080;
    --acl-rw: #22c55e;
    --acl-ro: #facc15;
    --acl-hidden: #f87171;
    --shadow: 0 1px 3px rgba(0, 0, 0, .4);
    --shadow-tile: 0 3px 12px rgba(0, 0, 0, .6);
  }
}

* { box-sizing: border-box; }

/* The HTML `hidden` attribute must win over component display rules
   (e.g. .btn sets display:inline-flex, which otherwise overrides it). */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  font-size: 14px;
  color: var(--fg);
  background: var(--bg);
}

#app { min-height: 100%; display: flex; flex-direction: column; }

.statusbar {
  position: sticky;
  top: 0;
  z-index: 50;
  height: var(--statusbar-h);
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 0 16px;
  background: var(--bg-elevated);
  border-bottom: 1px solid var(--border);
  box-shadow: var(--shadow);
}
.statusbar__brand { font-weight: 600; flex: none; }
.statusbar__menu-toggle { flex: none; padding: 4px 8px; font-size: 16px; line-height: 1; }
.statusbar__live {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px;
  font-variant-numeric: tabular-nums; overflow: hidden;
}
.statusbar__clock { font-variant-numeric: tabular-nums; color: var(--fg-muted); font-size: 13px; }
/* CSMS backend badges: right-aligned group, just left of the user name. */
.statusbar__backends { display: flex; align-items: center; gap: 6px; font-variant-numeric: tabular-nums; }

/* Flat status pills (no borders, shadows or gradients). */
.spill {
  display: inline-block; padding: 2px 9px; border-radius: 10px;
  font-size: 11px; font-weight: 600; line-height: 1.6; white-space: nowrap;
}
.spill--ok      { background: var(--ok);      color: #fff; }
.spill--warn    { background: var(--warning); color: #fff; }
.spill--danger  { background: var(--danger);  color: #fff; }
.spill--info    { background: var(--accent);  color: var(--accent-fg); }
.spill--neutral { background: color-mix(in srgb, var(--fg-muted) 20%, transparent); color: var(--fg-muted); }

/* Product-license banner (between status bar and layout). Warn = eval mode
   (invalid/missing/expired), info = a valid license about to expire. */
.license-banner { padding: 8px 16px; font-size: 13px; line-height: 1.35; text-align: center; }
.license-banner[hidden] { display: none; }
.license-banner--warn { background: var(--warning); color: #fff; }
.license-banner--info { background: var(--accent); color: var(--accent-fg); }

/* Systeminfo → Lizenz: upload/remove controls */
.license-manage { margin-top: 18px; border-top: 1px solid var(--border); padding-top: 12px; }
.license-manage__row { display: flex; flex-wrap: wrap; gap: 8px 12px; align-items: center; margin: 8px 0; }
.license-manage__msg { margin-top: 8px; }
/* Emphasised badge for an abnormal global state (e.g. load management switched off):
   heavier weight + a ring so it stands out among the neutral status pills. */
.spill--strong {
  font-weight: 800;
  letter-spacing: .02em;
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--danger) 35%, transparent);
}

/* LM activity heartbeat: a dot that pulses once on every load-allocator recompute
   (the "tick" live event). Frequent pulses = actively recomputing; a steady dim
   dot = idle/stalled. Replaces the old countdown ring, which could never deplete
   while recomputes ran faster than tick_max_interval_s. */
.lm-pulse {
  display: inline-block; width: 9px; height: 9px; border-radius: 50%;
  background: var(--border); vertical-align: middle;
}
@keyframes lm-pulse {
  0%   { background: var(--accent); transform: scale(1.65); }
  60%  { background: var(--accent); }
  100% { background: var(--border); transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) { .lm-pulse { animation: none !important; } }
.statusbar__right { display: flex; gap: 8px; align-items: center; }
/* Der eigene Name ist ein Knopf (führt zum eigenen Passwort), soll aber wie
   Text aussehen — ein weiterer Knopf neben Glocke und Theme-Umschalter machte
   die Leiste unruhig. Erst beim Überfahren gibt er sich zu erkennen. */
.statusbar__user {
  color: var(--fg-muted);
  background: none; border: 0; padding: 0; font: inherit;
  cursor: pointer;
}
.statusbar__user:hover,
.statusbar__user:focus-visible { color: var(--fg); text-decoration: underline; }

.layout {
  flex: 1;
  display: grid;
  grid-template-columns: var(--menu-col) 1fr var(--actions-col, var(--actions-w));
  min-height: 0;
}

/* The right actions sidebar (Abbrechen/Neu/Speichern) is only shown when a view
   opts in via data-visible="true". While it is hidden, collapse its grid column
   to 0 so the content — and the diagram canvas — use the full width instead of
   reserving 280px of empty space on the right. Uses a variable (not a direct
   grid-template-columns override) so the login single-column rule still wins. */
.layout:has(> .actions[data-visible="false"]) { --actions-col: 0px; }

/* Icon-rail menu (driven by the data-menu attribute on <html>; set from the
   user's toggle preference or, when unset, the viewport width — see main.js). */
:root[data-menu="rail"] { --menu-col: var(--menu-rail-w); }
:root[data-menu="rail"] .menu a { justify-content: center; gap: 0; padding: 8px 0; }
:root[data-menu="rail"] .menu a > span { display: none; }   /* labels → title tooltip */
:root[data-menu="rail"] .menu h3 { display: none; }
:root[data-menu="rail"] .menu-link--indent { padding-left: 0 !important; }

.menu {
  border-right: 1px solid var(--border);
  background: var(--bg-elevated);
  /* Scroll the left menu independently of the page content: pin it below the
     sticky status bar and give it its own viewport-height scroll area. */
  position: sticky;
  top: var(--statusbar-h);
  align-self: start;
  height: calc(100vh - var(--statusbar-h));
  /* Drei Teile: oben schließt die Kopfleiste an, unten steht der Fuß fest, und
     nur die Menüpunkte dazwischen bewegen sich. */
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
/* Der bewegliche Teil. Der eingerückte Schatten an beiden Kanten zeigt, dass
   er zwischen zwei festen Rändern läuft — ohne ihn sieht ein abgeschnittener
   Menüpunkt nach einem Anzeigefehler aus. */
.menu__scroll {
  flex: 1 1 auto; overflow-y: auto; padding: 12px 0;
  box-shadow: inset 0 7px 6px -7px rgb(0 0 0 / .18),
              inset 0 -7px 6px -7px rgb(0 0 0 / .18);
}
.menu__foot {
  flex: 0 0 auto; display: flex; align-items: center; gap: 6px;
  padding: 8px 10px; border-top: 1px solid var(--border);
  background: var(--bg-elevated);
}
.menu__version { font-size: var(--fs-11); color: var(--fg-muted);
                 font-variant-numeric: tabular-nums; }
/* Der Name schiebt die beiden Symbole nach rechts und wird selbst gekürzt,
   wenn der Platz nicht reicht — abgeschnitten ist besser als umgebrochen. */
.menu__foot-btn[data-bind="menu-user"] {
  flex: 1 1 auto; min-width: 0; text-align: left;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.menu__foot-btn {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  gap: 4px; padding: 4px 6px; border: 0; border-radius: var(--radius);
  background: none; color: var(--fg-muted); cursor: pointer;
  font: inherit; font-size: var(--fs-12);
}
.menu__foot-btn:hover { background: color-mix(in srgb, var(--fg) 8%, transparent);
                        color: var(--fg); }
.menu__foot-btn .menu-icon { width: 15px; height: 15px; flex: 0 0 15px; }
/* Schmales Fenster: Die Version rutscht in eine eigene Zeile, damit Name und
   Symbole nebeneinander bleiben. */
@media (max-width: 1100px) {
  .menu__foot { flex-wrap: wrap; }
  .menu__version { flex: 1 0 100%; }
}
/* Im Symbolstreifen bleibt nur, was ohne Text verständlich ist. */
:root[data-menu="rail"] .menu__foot { flex-direction: column; gap: 2px; padding: 6px 0; }
:root[data-menu="rail"] .menu__version,
:root[data-menu="rail"] .menu__foot-btn[data-bind="menu-user"] { display: none; }
.menu ul { list-style: none; margin: 0; padding: 0; }
.menu li { padding: 0; }
.menu a {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 16px;
  color: var(--fg);
  text-decoration: none;
  border-left: 3px solid transparent;
}
.menu-icon {
  width: 16px; height: 16px; flex: 0 0 16px;
  color: var(--fg-muted);
}
.menu a.is-active .menu-icon { color: var(--accent); }
.menu a:hover .menu-icon { color: var(--fg); }
.menu a.is-active {
  border-left-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}
.menu a:hover { background: color-mix(in srgb, var(--fg) 4%, transparent); }
.menu h3 { font-size: 11px; text-transform: uppercase; color: var(--fg-muted); margin: 16px 16px 4px; }

/* Ein-/ausklappbare Menüabschnitte. Der Titel ist ein echter Button (Tastatur
   und Screenreader bekommen aria-expanded), sieht aber weiter wie die frühere
   Überschrift aus. */
.menu-section { list-style: none; }
.menu-section__btn {
  display: flex; align-items: center; justify-content: space-between; gap: 6px;
  width: calc(100% - 24px); margin: 16px 12px 4px; padding: 2px 4px;
  background: none; border: 0; cursor: pointer;
  font: inherit; font-size: 11px; text-transform: uppercase; letter-spacing: .02em;
  color: var(--fg-muted); text-align: left; border-radius: var(--radius);
}
.menu-section__btn:hover { background: color-mix(in srgb, var(--fg) 6%, transparent); }
.menu-section__chev { width: 14px; height: 14px; flex: none; transition: transform .15s ease; }
/* Eingeklappt zeigt der Pfeil nach rechts. */
.menu-section__btn[aria-expanded="false"] .menu-section__chev { transform: rotate(-90deg); }
/* Der Abschnitt mit der aktuellen Seite bleibt erkennbar, auch eingeklappt. */
.menu-section.has-active .menu-section__btn { color: var(--fg); font-weight: 600; }
/* Im Icon-Schmalmenü gibt es keine Abschnittstitel — dort auch keine Schalter. */
:root[data-menu="rail"] .menu-section { display: none; }

.view {
  padding: 16px;
  overflow-y: auto;
  min-width: 0;   /* let the 1fr track shrink so wide tables scroll, not blow out the grid */
}
.view__loading { color: var(--fg-muted); }
/* Wide tables scroll horizontally within their own area instead of forcing the
   whole layout wider (esp. on narrow viewports). */
.table-scroll { overflow-x: auto; }

.actions {
  position: sticky;
  top: var(--statusbar-h);
  align-self: start;
  border-left: 1px solid var(--border);
  background: var(--bg-elevated);
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  height: calc(100vh - var(--statusbar-h));
}
.actions[data-visible="false"] { visibility: hidden; }

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 6px 12px;
  background: var(--bg-elevated);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  font-size: 13px;
  transition: background-color .12s ease, border-color .12s ease,
              color .12s ease, filter .12s ease;
}
.btn:hover { background: color-mix(in srgb, var(--fg) 6%, transparent); }
.btn:active { transform: translateY(1px); }   /* press feedback */
.btn--primary { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }
/* Re-assert the accent background on hover: the generic .btn:hover (equal
   specificity, earlier in source) would otherwise wash it to the fg-mix. */
.btn--primary:hover { background: var(--accent); filter: brightness(.93); }
.btn--ghost { background: transparent; border-color: transparent; }
.btn--danger { background: var(--danger); color: white; border-color: var(--danger); }
.btn--danger:hover { background: var(--danger); filter: brightness(.93); }
/* Gesperrte Knöpfe sahen bisher aus wie bedienbare — ein roter „Löschen"-Knopf,
   der auf Klick nichts tut, liest sich als Fehler. Die Regel steht NACH den
   Varianten und überschreibt deren Hover (gleiche Spezifität, spätere Quelle). */
.btn:disabled, .btn[disabled] { opacity: .45; cursor: not-allowed; }
.btn:disabled:hover, .btn[disabled]:hover { filter: none; transform: none; }
.btn:disabled:active, .btn[disabled]:active { transform: none; }

.toggle-group {
  display: inline-flex;
  /* Inside a column-flex .field, align-items:stretch would blow the group up to
     full width — keep it only as wide as its buttons. */
  align-self: flex-start;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
.toggle-group .btn {
  border: 0;
  border-radius: 0;
  border-right: 1px solid var(--border);
}
.toggle-group .btn:last-child { border-right: 0; }
.toggle-group .btn[aria-pressed="true"] {
  background: var(--accent);
  color: var(--accent-fg);
}

.field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 12px;
  max-width: 360px;
}
/* Eine ganze Adresse ist kein Formularfeld: Sie wird gelesen und kopiert, nicht
   ausgefüllt. Die 360 px der Formularspalte schneiden sie mittendrin ab. */
.field--wide { max-width: none; }
.field label { color: var(--fg-muted); font-size: 12px; }
.field-hint {
  display: inline-flex;
  vertical-align: middle;
  margin-left: 5px;
  color: var(--fg-muted);
  cursor: help;
  line-height: 0;
}
.field-hint:hover, .field-hint:focus { color: var(--accent); outline: none; }
.field input, .field select, .input {
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 6px 8px;
  font-size: 14px;
}
.field input:focus, .field select:focus, .input:focus { outline: 2px solid var(--accent); outline-offset: -1px; }

/* Disabled / read-only fields must be clearly distinguishable from editable
   ones (e.g. OCPP identity / version on real stations): muted, dashed, with a
   not-allowed cursor. */
.field input:disabled, .field input[readonly],
.field select:disabled, .field select[readonly],
.input:disabled, .input[readonly],
textarea:disabled, textarea[readonly] {
  background: color-mix(in srgb, var(--fg) 7%, var(--bg));
  color: var(--fg-muted);
  border-style: dashed;
  cursor: not-allowed;
}
.toggle-group button:disabled { opacity: 0.5; cursor: not-allowed; }

/* Button styled as an inline link (e.g. clickable station id / message id in a
   log detail). */
.linklike {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  color: var(--accent);
  cursor: pointer;
  font: inherit;
  text-align: left;
}
.linklike:hover { text-decoration: underline; }

/* Diagnostics page: grouped command columns + connection header + result log. */
.diag-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.diag-log { list-style: none; margin: 0; padding: 0; max-height: 280px; overflow: auto; }
.diag-log__item { padding: 5px 0; border-bottom: 1px solid var(--border); font-size: 13px; }
.diag-log__item:last-child { border-bottom: 0; }
.diag-log__time { color: var(--fg-muted); margin-right: 6px; font-variant-numeric: tabular-nums; }
.diag-log__detail { margin-top: 4px; }
.diag-log__empty { color: var(--fg-muted); font-size: 13px; }

.card {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  margin-bottom: 16px;
  max-width: 720px;
  box-shadow: var(--shadow);
}

.notice {
  padding: 10px 12px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--bg-elevated);
  margin-bottom: 16px;
  max-width: 720px;
}
.notice--error { border-color: var(--danger); color: var(--danger); }
.notice--ok    { border-color: var(--ok); color: var(--ok); }
.notice--warn  { border-color: var(--warning); color: color-mix(in srgb, var(--warning) 85%, var(--fg)); }
/* External-inputs global kill-switch bar: text left, action button right. */
.ext-globalbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; }

.login-screen {
  display: grid;
  place-items: center;
  height: calc(100vh - var(--statusbar-h));
}
.login-card {
  width: 320px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 24px;
  box-shadow: var(--shadow);
}
.login-card h1 { margin-top: 0; font-size: 18px; }
/* Anlagenname unter der Überschrift: Wer mehrere Instanzen betreut, muss VOR
   dem Anmelden sehen, welche er vor sich hat. */
.login-site { margin: -6px 0 14px; font-size: 15px; font-weight: 600;
              color: var(--fg); }

/* Hide the rails on the login screen and collapse the layout to one column
   so the centered login card actually sits in the middle of the viewport. */
body[data-view="login"] .menu,
body[data-view="login"] .actions,
body[data-view="login"] .statusbar__live,
body[data-view="login"] .statusbar__backends,
body[data-view="login"] .statusbar__user,
body[data-view="login"] .statusbar__menu-toggle { display: none; }
body[data-view="login"] .layout { grid-template-columns: 1fr; }
body[data-view="login"] .view { padding: 0; }

/* Anlagenname in der Titelzeile: neben der Marke, aber erkennbar nachgeordnet. */
.statusbar__site { font-weight: 400; color: var(--fg-muted); }

/* Preisführung justieren: Zahlenfeld links, Wirkung rechts. Unter 720 px
   (Modal auf dem Telefon) rutscht die Vorschau unter das Feld. */
/* Die Preisvorschau steht seit 4.13.5 über die volle Breite unter den
   Schaltern (der Schwellwert ist zu ihnen gewandert). Ein Kurvenbild in einer
   Drittelspalte war zu schmal, um vier von zwölf Stunden zu unterscheiden. */

/* ---------- Ladekonto (V2-LK) ----------
   Eine eigene Seite ohne Menü und Statusleiste: Sie gehört dem Fahrer, nicht
   dem Betreiber. Deshalb blendet dieser Zustand dieselben Elemente aus wie der
   Login-Zustand — und zusätzlich alles, was zur Anlage gehört. */
body[data-view="account"] .menu,
body[data-view="account"] .actions,
body[data-view="account"] .statusbar__live,
body[data-view="account"] .statusbar__backends,
body[data-view="account"] .statusbar__user,
body[data-view="account"] .statusbar__menu-toggle,
body[data-view="account"] .statusbar,
body[data-view="account"] [data-bind="license-banner"] { display: none; }
body[data-view="account"] .layout { grid-template-columns: 1fr; }
body[data-view="account"] .view { padding: 0; }

.acct { max-width: 880px; margin: 0 auto; padding: 18px 16px 48px; }

/* ---------- Freigegebene eigene Ansicht (V2-LK-03) ----------
   Breiter als ein Ladekonto: Hier stehen Kacheln nebeneinander, und auf einem
   Laptop soll die Zeile nicht künstlich schmal bleiben. Das Kachelraster bringt
   seine Umbrüche selbst mit (900 px / 560 px) — hier braucht es nur Platz und
   Ränder, die auf dem Telefon nicht drücken. */
.shared { max-width: 1400px; margin: 0 auto; padding: 12px 12px 40px; }
@media (max-width: 560px) { .shared { padding: 8px 8px 32px; } }
.shared__preview { margin-bottom: 12px; }
.shared__foot { margin-top: 20px; padding-top: 12px; border-top: 1px solid var(--border); }
/* Kopf ohne Bedienelemente: nur Titel und Anlage. */
.d3-toolbar--shared { align-items: baseline; gap: 10px; flex-wrap: wrap; }
/* Der Hell/Dunkel-Schalter rückt ans rechte Ende — er gehört nicht zum Titel. */
.d3-theme { margin-left: auto; }
.acct-login { max-width: 380px; margin: 12vh auto 0; padding: 22px 24px;
              border: 1px solid var(--border); border-radius: var(--radius);
              background: var(--bg-elevated); box-shadow: var(--shadow-tile); }
.acct-login__title { margin: 0 0 4px; font-size: 20px; }
.acct-login__form { margin-top: 14px; }

.acct-head { display: flex; align-items: flex-start; justify-content: space-between;
             gap: 12px; margin-bottom: 14px; flex-wrap: wrap; }
.acct-head__title { font-size: 12px; letter-spacing: .04em; text-transform: uppercase;
                    color: var(--fg-muted); }
.acct-head__who { font-size: 20px; font-weight: 600; }
/* Drei Knöpfe statt zwei: Auf schmalen Geräten dürfen sie umbrechen,
   statt die Kopfzeile aus dem Bild zu schieben. */
.acct-head__actions { display: flex; gap: 6px; flex-wrap: wrap;
                      justify-content: flex-end; }
.acct-head__group { margin-top: 2px; }
/* Zustand und Tageslimit stehen nebeneinander: zwei kurze Karten, die zusammen
   die Frage „läuft es, und wie weit darf es noch" beantworten. */
/* **Ein** Raster für Zustand, Tageslimit, Ladeziel und Fahrzeug. Zwei getrennte
   Reihen hätten beim Ausblenden eine Lücke hinterlassen: Was in der ersten
   Reihe fehlt, kann die zweite nicht auffüllen. ``auto-fit`` ordnet an, was da
   ist — zwei Spalten, wo Platz ist, eine auf dem Telefon, und eine einzelne
   Karte bekommt die volle Breite, statt halb leer zu bleiben. */
.acct-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
             gap: 12px; align-items: stretch; margin-bottom: 12px; }
/* Gestreckt wird die HÜLLE (jede Karte sitzt in einer) — läge die Karte direkt
   im Raster, machte ``display: flex`` sie selbst zum Flex-Container und ihre
   Überschrift rutschte neben den Text. */
.acct-grid > div { display: flex; }
.acct-grid > div > .acct-card { flex: 1 1 auto; margin-bottom: 0; }
/* Der Ortsblock eines Ladepunktkontos: eine Zeile, die auf dem Telefon umbricht
   statt den Kopf zu sprengen. */
.acct-head__place { margin-top: 2px; line-height: 1.5; }
/* Mehrere laufende Vorgänge (Fuhrpark): je Vorgang ein Block, getrennt durch
   eine feine Linie statt durch Abstand allein. */
.acct-status__one + .acct-status__one { margin-top: 10px; padding-top: 10px;
                                        border-top: 1px solid var(--border); }
.acct-preview { margin-bottom: 12px; }
/* Ladeziel: zwei Felder nebeneinander, auf dem Telefon untereinander. */
.acct-goal__fields { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
@media (max-width: 560px) { .acct-goal__fields { grid-template-columns: 1fr; } }
.acct-goal__fields .field { max-width: none; margin-bottom: 4px; }
.acct-goal__default { display: flex; align-items: center; gap: 6px; }
.acct-goal__src { margin-top: 4px; }
/* API-Konsole: Erklärung unter dem Eingabefeld. Die Kopfzeile (Typ · Bereich ·
   Pflicht) steht abgesetzt, damit sie beim Überfliegen nicht mit dem Fließtext
   verschwimmt. */
.apidoc-hint { line-height: 1.35; }
.apidoc-hint__meta { font-family: var(--font-mono, monospace); opacity: .85; }
.apidoc-fields__list { margin: 4px 0 0; font-size: var(--fs-12); }
.apidoc-fields__list dt { font-family: var(--font-mono, monospace); margin-top: 4px; }
.apidoc-fields__list dd { margin: 0 0 0 12px; color: var(--fg-muted); }
/* Abfahrtsplan: sieben Zeilen Tag | Modus | Uhrzeit. Auf schmalen Schirmen
   bricht die Uhrzeit unter den Toggle, statt ihn zu quetschen. */
.week-plan { margin-top: 8px; }
.week-row { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; flex-wrap: wrap; }
.week-row__day { min-width: 2.4em; font-weight: 600; font-size: var(--fs-13); }
.week-row__time { flex: 0 1 9em; }
.week-row--ro { color: var(--fg-muted); font-size: var(--fs-13); }
.week-row .toggle-group .btn { padding: 3px 8px; font-size: var(--fs-12); }
/* ``display: flex`` nimmt einem <summary> sein Aufklapp-Dreieck — der Browser
   zeigt den Marker nur bei ``list-item``. Der Abschnitt war damit klappbar,
   sah aber aus wie eine Überschrift; niemand kam auf die Idee zu klicken.
   Deshalb ein eigener Pfeil, der sich beim Öffnen dreht — wie im Baum und in
   der Auslastung. */
.acct-week > summary { cursor: pointer; display: flex; gap: 8px; align-items: baseline;
  justify-content: space-between; padding: 6px 0; list-style: none; }
.acct-week > summary::-webkit-details-marker { display: none; }
.acct-week__caret { color: var(--fg-muted); font-size: 10px; transition: transform .12s ease;
                    display: inline-block; }
.acct-week[open] > summary .acct-week__caret { transform: rotate(90deg); }
.acct-week__name { margin-right: auto; }
.acct-veh { display: flex; flex-direction: column; gap: 4px; font-size: var(--fs-13); }
.acct-veh .pill { margin-left: 6px; }
@media (max-width: 560px) { .week-row__time { flex-basis: 100%; max-width: 12em; } }

.acct-card { border: 1px solid var(--border); border-radius: var(--radius);
             background: var(--bg-elevated); padding: 12px 14px; margin-bottom: 12px; }
.acct-card__title { font-size: 12px; letter-spacing: .03em; text-transform: uppercase;
                    color: var(--fg-muted); margin-bottom: 8px; }
/* Einklappbare Karte: Die Überschrift wird zum Knopf, ohne wie einer
   auszusehen — sie trägt schon die Bedeutung, ein Rahmen darum wäre Lärm.
   Eingeklappt tritt die Zusammenfassung an die Stelle des Inhalts. */
.acct-card__head { display: flex; align-items: baseline; gap: 8px; width: 100%;
                   background: none; border: 0; padding: 0; margin-bottom: 8px;
                   color: inherit; font: inherit; text-align: left; cursor: pointer; }
.acct-card__head:hover .acct-card__title { color: var(--fg); }
.acct-card__head .acct-card__title { margin-bottom: 0; }
/* Der Pfeil ist der einzige Hinweis darauf, dass die Karte etwas verbirgt —
   und zugleich die Trefferfläche. Feste Breite, damit die Überschrift beim
   Drehen nicht springt. */
.acct-card__caret { color: var(--fg-muted); font-size: 16px; line-height: 1;
                    width: 1em; flex: 0 0 auto; text-align: center;
                    transition: transform .12s ease; }
.acct-card__head:hover .acct-card__caret { color: var(--fg); }
/* Ausblenden: leise, bis man die Karte anfasst — es ist die seltenere der
   beiden Handlungen am Kopf und soll das Einklappen nicht überstrahlen. */
.acct-card__hide { flex: 0 0 auto; margin-left: 10px; background: none; border: 0;
                   padding: 0 4px; font-size: 18px; line-height: 1; cursor: pointer;
                   color: var(--fg-muted); opacity: 0; transition: opacity .12s ease; }
.acct-card__head:hover .acct-card__hide,
.acct-card__hide:focus-visible { opacity: 1; }
.acct-card__hide:hover { color: var(--danger); }
/* Auf Geräten ohne Zeigegerät gibt es kein „hover" — dort steht er immer. */
@media (hover: none) { .acct-card__hide { opacity: .6; } }
/* Die Leiste der ausgeblendeten Kacheln: Ohne sie wäre das Ausblenden eine
   Einbahnstraße. */
.acct-hidden { display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
               margin: 4px 0 12px; }
/* Eine Gruppe fasst Karten zusammen (Verbrauch: Tage, Monate, Summen). Ihre
   Kinder bekommen keinen zweiten Rahmen — verschachtelte Kästen sähen aus wie
   ein Fehler, und die Gruppe hat schon einen. */
.acct-card--group > .acct-card__body > .acct-card {
  border: 0; background: none; padding: 0; margin: 14px 0 0;
}
.acct-card--group > .acct-card__body > .acct-card[data-collapsed="1"] { padding: 0; }
.acct-card--group > .acct-card__body > .acct-sums { margin-top: 0; }
/* Eingeklappt ist die Zusammenfassung der Inhalt der Karte — nicht ihre
   Fußnote. Sie steht deshalb groß, in voller Textfarbe und mit
   Tabellenziffern, damit untereinanderstehende Zahlen fluchten. Die
   Überschrift bleibt klein und gedämpft: Sie sagt, WOVON die Zahl handelt. */
.acct-card__sum { margin-left: auto; font-size: clamp(20px, 5.5vw, 28px);
                  font-weight: 600; line-height: 1.1; color: var(--fg);
                  font-variant-numeric: tabular-nums; text-align: right; }
/* Auf schmalen Geräten bricht die Zahl unter die Überschrift, statt
   abgeschnitten zu werden — eine halbe Auskunft ist keine. */
@media (max-width: 520px) {
  .acct-card__head { flex-wrap: wrap; }
  .acct-card__sum { flex-basis: 100%; margin-left: 0; margin-top: 2px; }
}
.acct-card[data-collapsed="1"] .acct-card__caret { transform: rotate(-90deg); }
.acct-card[data-collapsed="1"] .acct-card__body { display: none; }
/* Eingeklappt trägt der Kopf die ganze Karte — die Grundlinien-Ausrichtung
   der aufgeklappten Zeile passt dann nicht mehr. */
.acct-card[data-collapsed="1"] .acct-card__head { align-items: center; margin-bottom: 0; }
.acct-card[data-collapsed="1"] { padding: 10px 14px; }
/* Aufgeklappt sagt die Zusammenfassung dasselbe wie der Inhalt darunter. */
.acct-card:not([data-collapsed="1"]) .acct-card__sum { display: none; }
.acct-card__foot { margin-top: 8px; }
.acct-card--status.is-charging { border-color: var(--ok); }
.acct-status__idle { color: var(--fg-muted); }
.acct-status__row { display: flex; align-items: center; gap: 8px; font-size: 16px; }
/* Ein laufender Vorgang: die Angaben links, der Zustand als Bild rechts.
   ``min-width: 0`` am Textteil, damit eine lange Kennung die Kachel nicht aus
   dem Kasten drückt. */
.acct-status__one { display: flex; align-items: flex-start; gap: 14px; }
.acct-status__main { flex: 1 1 auto; min-width: 0; }
.acct-status__vals { display: flex; flex-wrap: wrap; gap: 4px 18px; margin-top: 6px; }
/* Der geltende Preis steht über den Vorgängen, nicht in einem davon: Er
   gilt dem Anschluss, auch wenn gerade nichts geladen wird. */
.acct-status__price { display: flex; align-items: baseline; gap: 6px;
  flex-wrap: wrap; margin: 2px 0 8px; font-size: var(--fs-14); }
.acct-status__price strong { font-variant-numeric: tabular-nums; }

/* Der Zustand als farbiges Quadrat mit Symbol und einem Wort — lesbar aus der
   Entfernung, in der jemand vor der Station steht. Feste Breite, damit die
   Kachel bei jedem Zustand gleich aussieht: Ein Kasten, der mit „AUSSER
   BETRIEB" breiter wird als mit „LÄDT", wirkt wie ein anderes Element.
   Farbe und Vordergrund kommen paarweise aus den Tönen unten — Weiß auf Gelb
   wäre nicht lesbar, und ein einzelner Vordergrund für alle Töne führte genau
   dorthin. */
.acct-state {
  flex: 0 0 auto; width: 92px; min-height: 92px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px; padding: 8px 6px; border-radius: 10px;
  background: var(--st-bg); color: var(--st-fg);
  text-align: center;
}
.acct-state__icon { width: 30px; height: 30px; flex: none; }
.acct-state__word { font-size: 12px; font-weight: 700; letter-spacing: .04em;
                    line-height: 1.15; overflow-wrap: anywhere; }
/* Nur „lädt" pulsiert — der einzige Zustand, in dem gerade etwas passiert.
   Wer Bewegung abgestellt hat, bekommt keine. */
.acct-state.is-pulsing { animation: pause-pulse 2.2s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) { .acct-state.is-pulsing { animation: none; } }

/* Die Töne. Jeder trägt seinen eigenen Vordergrund; die dunkle Oberfläche
   bekommt eigene Werte, weil dieselben Flächen dort sonst leuchten. */
.acct-state--go   { --st-bg: #2c8a3e; --st-fg: #ffffff; }
.acct-state--soft { --st-bg: #9ad7a3; --st-fg: #14401f; }
.acct-state--info { --st-bg: #1f6feb; --st-fg: #ffffff; }
.acct-state--hold { --st-bg: #e5a400; --st-fg: #2a1d00; }
.acct-state--work { --st-bg: #e07b39; --st-fg: #2a1300; }
.acct-state--bad  { --st-bg: #c0392b; --st-fg: #ffffff; }
.acct-state--off  { --st-bg: #d8dadd; --st-fg: #4a4f57; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .acct-state--go   { --st-bg: #2f7d3f; }
  :root:not([data-theme="light"]) .acct-state--soft { --st-bg: #6fae7b; --st-fg: #0d2913; }
  :root:not([data-theme="light"]) .acct-state--info { --st-bg: #2f6fd0; }
  :root:not([data-theme="light"]) .acct-state--work { --st-bg: #c96a2e; --st-fg: #1d0d00; }
  :root:not([data-theme="light"]) .acct-state--bad  { --st-bg: #a83226; }
  :root:not([data-theme="light"]) .acct-state--off  { --st-bg: #3a3e46; --st-fg: #cfd3da; }
}
:root[data-theme="dark"] .acct-state--go   { --st-bg: #2f7d3f; }
:root[data-theme="dark"] .acct-state--soft { --st-bg: #6fae7b; --st-fg: #0d2913; }
:root[data-theme="dark"] .acct-state--info { --st-bg: #2f6fd0; }
:root[data-theme="dark"] .acct-state--work { --st-bg: #c96a2e; --st-fg: #1d0d00; }
:root[data-theme="dark"] .acct-state--bad  { --st-bg: #a83226; }
:root[data-theme="dark"] .acct-state--off  { --st-bg: #3a3e46; --st-fg: #cfd3da; }
/* Die gerade autorisierte Kennung im Ladepunktkonto: eigene Zeile, nicht in
   die Werte gemischt. Sie beantwortet eine andere Frage als „wie viel fließt" —
   nämlich WER hier lädt — und wird oft abgelesen und verglichen, deshalb
   Festbreitenschrift und Umbruch an beliebiger Stelle (eine eMAID ist länger
   als ein Telefon breit ist). */
.acct-status__ident { margin-top: 6px; font-size: var(--fs-13); }
.acct-status__ident strong { font-family: var(--font-mono, ui-monospace, monospace);
                             overflow-wrap: anywhere; }
.acct-hint { margin-top: 8px; font-size: var(--fs-12); color: var(--fg-muted); }
/* Eine Auskunft, die den Fahrer betrifft, statt einer Fußnote: Die Station ist
   in Wartung, sein Vorgang läuft noch zu Ende. Gleiche Größe, aber in der
   Warnfarbe — gedämpft gelesen würde sie überblättert. */
.acct-hint--warn { color: var(--warning); }

.acct-bar { height: 10px; border-radius: 999px; overflow: hidden;
            background: color-mix(in srgb, var(--fg) 10%, transparent); }
.acct-bar > span { display: block; height: 100%; background: var(--accent); }
.acct-limit__txt { margin-top: 6px; font-size: 14px; }
.acct-flag { display: inline-block; border-radius: 999px; padding: 1px 9px;
             font-size: 12px; background: color-mix(in srgb, var(--fg) 10%, transparent); }
.acct-flag--ok { background: color-mix(in srgb, var(--ok) 20%, transparent); color: var(--ok); }
.acct-flag--bad { background: color-mix(in srgb, var(--danger) 20%, transparent);
                  color: var(--danger); }

.acct-sums { display: grid; grid-template-columns: repeat(auto-fit, minmax(128px, 1fr));
             gap: 10px; margin-bottom: 6px; }
.acct-sum { border: 1px solid var(--border); border-radius: var(--radius);
            background: var(--bg-elevated); padding: 10px 12px; }
.acct-sum__lbl { font-size: 12px; color: var(--fg-muted); }
.acct-sum__val { font-size: 22px; font-weight: 700; line-height: 1.2;
                 font-variant-numeric: tabular-nums; }
.acct-sum__sub { font-size: 12px; }
.acct-kpis { font-size: var(--fs-12); margin: 0 0 12px 2px; }

.acct-months { display: flex; align-items: flex-end; gap: 6px; height: 120px; }
.acct-month { flex: 1 1 0; display: flex; flex-direction: column; align-items: center;
              height: 100%; }
.acct-month__bar { flex: 1 1 auto; width: 100%; display: flex; align-items: flex-end; }
.acct-month__bar > span { display: block; width: 100%; min-height: 2px;
                          background: var(--accent); border-radius: 3px 3px 0 0; }
.acct-month__lbl { font-size: 11px; color: var(--fg-muted); margin-top: 4px; }
/* Zwei Jahre sind 24 Balken: enger, schmalere Beschriftung, und die Jahreszahl
   nur an den Januaren — zwölfmal derselbe Monatsname sagte sonst nicht, welcher
   gemeint ist. */
.acct-months--long { gap: 2px; }
/* Das laufende Jahr in voller Akzentfarbe, das Vorjahr blasser. Nicht zwei
   verschiedene Farben: Es ist dieselbe Größe, nur älter — ein Farbwechsel
   behauptete einen Unterschied in der Sache. */
.acct-months--long .acct-month__bar > span { opacity: .45; }
.acct-months--long .acct-month.is-current-year .acct-month__bar > span { opacity: 1; }
.acct-months--long .acct-month.is-current-year .acct-month__lbl { color: var(--fg); }
/* **Alle Beschriftungen gleich hoch.** Die Jahreszahl macht die Beschriftung
   zweizeilig — und weil die Kachelhöhe fest ist, blieb in genau dieser Spalte
   weniger Platz für den Balken: Die Januare standen auf einer anderen
   Grundlinie als ihre Nachbarn, und die Höhen waren nicht mehr vergleichbar.
   Der Platz für die zweite Zeile wird deshalb in JEDER Spalte reserviert, ob
   sie ihn braucht oder nicht. */
.acct-months--long .acct-month__lbl { font-size: 9px; letter-spacing: -.02em;
                                      min-height: 22px; }
.acct-month__yr { display: block; font-size: 8px; opacity: .8; }
@media (max-width: 620px) {
  /* Auf schmalen Geräten trägt nur jeder zweite Balken seinen Namen — sonst
     überlagern sich die Beschriftungen zu einem grauen Band. */
  .acct-months--long .acct-month:nth-child(2n) .acct-month__lbl { visibility: hidden; }
  .acct-months--long .acct-month:nth-child(2n) .acct-month__yr { visibility: visible; }
}
/* Tagesreihe: schmaler als die Monatsbalken, damit zehn Tage nicht wie zwölf
   Monate aussehen. */
.acct-days { height: 96px; }
.acct-days .acct-month__lbl { font-size: 10px; }

.acct-fc-table { margin-top: 10px; }
.acct-sessions td, .acct-sessions th { white-space: normal; }
.acct-pager { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
.acct-report { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
               gap: 10px; align-items: end; }
.acct-report__btns { display: flex; gap: 6px; }
.acct-report__quick { display: flex; gap: 6px; margin-top: 8px; flex-wrap: wrap; }
.acct-pw { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
           gap: 10px; align-items: end; }
.acct-foot { margin-top: 18px; font-size: var(--fs-12); color: var(--fg-muted);
             text-align: center; }
.acct-foot__notice { margin-top: 6px; }

/* ---------- table ---------- */
.table-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
  margin-bottom: 12px;
}
.table-search {
  flex: 1;
  min-width: 200px;
  padding: 6px 10px;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 14px;
}
/* Search field with an inline clear (×) button. */
.search-wrap { position: relative; display: flex; align-items: center; }
/* Das Suchfeld steht in derselben Zeile wie Blättern, Zeilenzahl und die
   beiden Knöpfe rechts — es darf sie nicht auseinanderschieben. */
.table-toolbar .search-wrap { flex: 0 1 240px; min-width: 160px; }
/* Alles ab hier rückt nach rechts: Zeilen je Seite, dann „Filter löschen". */
.table-toolbar__gap { flex: 1 1 auto; }
.table-pager { display: inline-flex; align-items: center; gap: 4px; }
.table-pager:empty { display: none; }
.table-size { display: inline-flex; align-items: center; gap: 6px; }
.table-size:empty { display: none; }
.table-size__sel {
  padding: 3px 6px; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg); color: var(--fg); font-size: 12px;
}
/* Symbol und Wort gehören zusammen: Das Symbol allein ist beim ersten Mal
   nicht zu erraten, das Wort allein sticht in einer Leiste nicht heraus. */
.filters-clear { display: inline-flex; align-items: center; gap: 6px; }
.filters-clear svg { width: 16px; height: 16px; flex: none; }
.log-controls .search-wrap { flex: 1 1 200px; max-width: 320px; }
.search-wrap .table-search,
.search-wrap .log-search { width: 100%; min-width: 0; padding-right: 26px; }
.search-clear {
  position: absolute; right: 6px; border: 0; background: transparent;
  color: var(--fg-muted); cursor: pointer; font-size: 16px; line-height: 1; padding: 0 2px;
}
.search-clear:hover { color: var(--fg); }
.search-clear[hidden] { display: none; }
.search-wrap input::-webkit-search-cancel-button { -webkit-appearance: none; }
/* "Filter löschen" button in the table toolbar (right of the search box). */
.filters-clear { flex: none; white-space: nowrap; }
.filters-clear[hidden] { display: none; }
.table {
  width: 100%;
  border-collapse: collapse;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
.table th, .table td {
  text-align: left;
  padding: 8px 12px;
  border-bottom: 1px solid var(--border);
}
.table tbody tr { cursor: pointer; }
.table tbody tr:hover { background: color-mix(in srgb, var(--fg) 4%, transparent); }
.table th { white-space: nowrap; }
.th__label.is-sortable { cursor: pointer; user-select: none; }
.th__label.is-sortable:hover { color: var(--accent); }
.table th.is-sorted-asc  .th__label::after { content: " ▲"; color: var(--accent); font-size: 9px; }
.table th.is-sorted-desc .th__label::after { content: " ▼"; color: var(--accent); font-size: 9px; }
.th__filter {
  border: 0; background: transparent; color: var(--fg-muted);
  cursor: pointer; padding: 0 2px; margin-left: 4px;
  display: inline-flex; align-items: center;
}
.th__filter .icon-funnel { display: block; }
.th__filter:hover { color: var(--fg); }
.th__filter.is-active { color: var(--accent); }            /* temporary filter / hide */
.th__filter--persist { color: var(--danger); }            /* persistent hides */
.th__filter--both { color: var(--violet); }               /* persistent + temporary */

.filter-pop {
  position: absolute; z-index: 130;
  background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 6px; min-width: 160px; max-height: 320px; overflow: auto; font-size: 13px;
}
.filter-pop__actions { display: flex; gap: 6px; margin-bottom: 6px; }
.filter-pop__actions button {
  flex: 1; border: 1px solid var(--border); background: var(--bg); color: var(--fg);
  border-radius: var(--radius); padding: 3px 6px; cursor: pointer; font-size: 12px;
}
.filter-pop__search {
  display: block; width: 100%; box-sizing: border-box; margin-bottom: 6px;
  padding: 4px 8px; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg); color: var(--fg); font-size: 13px;
}
.filter-pop__list { display: flex; flex-direction: column; gap: 3px; }
.filter-pop__list label { display: flex; align-items: center; gap: 6px; white-space: nowrap; cursor: pointer; }
.filter-pop__list label[hidden] { display: none; }
/* Per-option row: checkbox/label + a hide (⊘) control. */
.filter-pop__opt { display: flex; align-items: center; gap: 6px; }
.filter-pop__opt[hidden] { display: none; }
.filter-pop__opt label { flex: 1; min-width: 0; }
.filter-pop__hide, .filter-pop__restore {
  flex: none; border: 0; background: transparent; color: var(--fg-muted);
  cursor: pointer; font-size: 13px; line-height: 1; padding: 0 3px; opacity: .55;
}
.filter-pop__hide:hover, .filter-pop__restore:hover { color: var(--fg); opacity: 1; }
/* Hidden-options section. */
.filter-pop__hidden { margin-top: 6px; border-top: 1px solid var(--border); padding-top: 6px; }
.filter-pop__hidden-title { font-size: 11px; color: var(--fg-muted); margin-bottom: 4px; }
.filter-pop__hopt { display: flex; align-items: center; gap: 8px; }
.filter-pop__hlabel {
  flex: 1; min-width: 0; color: var(--fg-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.filter-pop__persist { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; white-space: nowrap; cursor: pointer; }

/* Related OCPP messages (same message_id) in the detail modal. */
.log-related { margin-top: 14px; border-top: 1px solid var(--border); padding-top: 10px; }
.log-related__title { font-size: 12px; color: var(--fg-muted); margin-bottom: 6px; }
.log-related__item {
  display: grid; grid-template-columns: auto auto 1fr auto; gap: 10px; align-items: baseline;
  width: 100%; text-align: left; border: 0; background: transparent; color: var(--fg);
  padding: 4px 6px; border-radius: var(--radius); cursor: pointer; font-size: 12px;
}
.log-related__item:hover { background: color-mix(in srgb, var(--accent) 8%, transparent); }
.log-related__item.is-current { background: color-mix(in srgb, var(--accent) 16%, transparent); font-weight: 600; }

/* expandable table rows (config lists: children under a group/UV) */
.th-expand, .td-expand { width: 1.6em; padding-right: 0 !important; }
.row-expand { border: 0; background: transparent; color: var(--fg-muted); cursor: pointer; font-size: 10px; padding: 0; }
.row-expand:hover { color: var(--fg); }
.table tbody tr.detail-row { cursor: default; }
.table tbody tr.detail-row:hover { background: transparent; }
.detail-row > td { background: color-mix(in srgb, var(--fg) 3%, transparent); padding: 6px 12px 10px 38px; }
.child-list { display: flex; flex-direction: column; gap: 3px; }
.child-list__item { display: flex; align-items: center; gap: 8px; font-size: 12px; }
.table tbody tr:last-child td { border-bottom: 0; }

/* Notifications: right-aligned action buttons in the targets table. */
.notify-actions { text-align: right; white-space: nowrap; }
.notify-actions .btn { margin-left: 6px; }

/* Notifications: per-topic routing rows (one route = target + its own scope). */
.notify-topics { display: flex; flex-direction: column; gap: 8px; }
.notify-topic { border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; }
.notify-topic__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.notify-topic__routes { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
.notify-troute { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.notify-topic__level { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
.notify-url { font-size: 12px; word-break: break-all; color: var(--fg-muted); }
/* Compact settings: fields sit at their natural width (bounded), close together
   and wrapping — not stretched across the full tab width. */
.notify-settings { display: flex; flex-wrap: wrap; gap: 12px 28px; align-items: flex-start; }
.notify-settings > .field { margin: 0; max-width: 240px; }
.notify-narrow { max-width: 200px; }
.notify-quiet { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.notify-tgt { display: inline-flex; align-items: center; gap: 5px; font-size: 13px; min-width: 160px; }
.btn.btn--sm { padding: 2px 8px; font-size: 12px; }
/* Knopf, der ein Formularfeld vertritt (Auswahl über einen Dialog): Er steht
   in der Feldspalte und soll deren Breite haben, sonst sieht die Zeile aus
   wie ein verrutschtes Formular. */
.btn--wide { width: 100%; justify-content: flex-start; }
.table .row-actions { display: flex; gap: 6px; justify-content: flex-end; }

.toolbar {
  display: flex;
  gap: 8px;
  margin-bottom: 12px;
  align-items: center;
}

/* Der Speichern-Knopf eines Reiter-Formulars ist nach oben gewandert
   (hoistPanelSave) — der ursprüngliche im Panel bleibt im Markup (er trägt die
   Logik), zeigt sich aber nicht mehr. */
.is-hoisted { display: none !important; }

/* Beim Verschieben wandert der gezeichnete Verlauf sofort mit (CSS-Transform).
   Ohne diese Klammer schöbe er sich über den Rand der Kachel hinaus und läge
   auf der Tabelle daneben — der Ausschnitt ist das Diagramm, nicht die Seite. */
.d3-chart__plot.is-panning { overflow: hidden; }
/* „Ausgangsansicht": sitzt IM Diagramm, weil es dort verstellt wurde —
   und nur an einem verstellten (der Aufrufer zeichnet ihn gar nicht erst).
   Oben rechts, wo die Kurve am seltensten hinkommt. */
.chart-reset { position: absolute; top: 2px; right: 2px; z-index: 2;
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; padding: 0; cursor: pointer;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg-elevated); color: var(--fg-muted); opacity: .75; }
.chart-reset:hover { opacity: 1; color: var(--fg); }
.chart-reset svg { width: 14px; height: 14px; }
/* Während einer Geste darf der Knopf nicht mitwandern. */
.d3-chart__plot.is-panning .chart-reset { pointer-events: none; }

/* Netzsuche im Anschluss-Reiter: Die Netzauswahl nimmt den Platz, der Knopf
   bleibt einzeilig — sonst bricht „IP-Adressen finden" in drei Zeilen um und
   die Zeile wird höher als die Felder darüber. */
.field[data-bind="netscan"] { max-width: 560px; }
[data-bind="netscan"] .row-flex > select { flex: 1 1 auto; min-width: 0; }
[data-bind="netscan"] .row-flex > .btn { flex: 0 0 auto; white-space: nowrap; }

/* Messwert-Diagramm eines Ladepunkts (Reiter „Info"). Vier Kurven auf einer
   Fläche: drei Phasen und der angebotene Strom. Der Sollwert gestrichelt —
   er ist eine Vorgabe, keine Messung, und das soll man sehen. */
/* Die Ladepunkte liegen in eigenen Reitern, nicht mehr untereinander: keine
   Trennlinie zwischen den Abschnitten (die zeigte sich sonst beim zweiten
   Ladepunkt als Strich über dem Inhalt) und feste Höhen, damit der Dialog beim
   Umschalten nicht springt. */
.cp-info__table { max-width: 640px; min-height: 268px; }
.cp-info__table th { width: 42%; text-align: left; color: var(--fg-muted); font-weight: 500; }
.cp-info__windows { margin: 0; }
.cpc__legend { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-12); margin-bottom: 4px; }
.cpc__key { display: inline-flex; align-items: center; gap: 5px; }
.cpc__peak { margin-left: auto; }
.cpc__swatch { display: inline-block; width: 14px; height: 3px; border-radius: 2px; background: currentColor; }
.cpc__plot { height: 220px; cursor: grab; touch-action: none; }
.cpc__plot.is-panning { cursor: grabbing; }
.cpc__range { margin-left: var(--space-8); }
/* Die Bedienelemente stehen LINKS neben dem Diagramm, nicht darunter: Der
   Dialog hat seitlich Platz, in der Höhe nicht — untereinander schob die
   Leiste den Stationsdialog über den Bildschirmrand hinaus. Sechs Zeitfenster
   und zwei Ansichten passen als schmale Spalte daneben, ohne dem Verlauf
   Breite zu nehmen. */
.cp-info__plot { display: flex; align-items: flex-start; gap: var(--space-12); }
.cp-info__plot .cp-info__graph { flex: 1 1 auto; min-width: 0; }
.cp-info__controls {
  display: flex; flex-direction: column; align-items: stretch;
  flex: 0 0 auto; gap: var(--space-4);
}
/* Die Spalte muss NEBEN das Diagramm passen, nicht darüber hinaus: Ragte sie
   unten heraus, wäre der Dialog wieder so hoch wie mit der Leiste darunter und
   die Umstellung umsonst. Acht Knöpfe in 240 px heißt etwas enger als die
   waagerechte Variante. */
.cp-info__controls .seg--vert .seg__btn { padding: 3px 10px; }
.cp-info__reload { align-self: start; padding: 2px 10px; }
/* Schmal wird die Spalte zur Leiste: Nebeneinander bliebe fürs Diagramm nichts
   übrig. */
@media (max-width: 700px) {
  .cp-info__plot { flex-direction: column; }
  .cp-info__plot .cp-info__graph { align-self: stretch; width: 100%; }
  .cp-info__controls { flex-direction: row; flex-wrap: wrap; align-items: center; }
  .cp-info__controls .seg--vert { flex-direction: row; }
  .cp-info__controls .seg--vert .seg__btn { border-bottom: 0; border-right: 1px solid var(--border); }
  .cp-info__controls .seg--vert .seg__btn:last-child { border-right: 0; }
}
/* Fortschritt beim Laden: über dem Diagramm, damit die vorige Kurve stehen
   bleibt — ein leerer Kasten sähe aus, als gäbe es keine Daten. */
.cp-info__graph { position: relative; min-height: 240px; }
.cp-info__busy[hidden] { display: none; }
.cp-info__busy {
  position: absolute; inset: 0; z-index: 2;
  display: flex; align-items: center; justify-content: center; gap: var(--space-8);
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  color: var(--fg-muted);
}
.spinner {
  width: 16px; height: 16px; border-radius: 50%;
  border: 2px solid color-mix(in srgb, var(--fg) 25%, transparent);
  border-top-color: var(--accent);
  animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .spinner { animation-duration: 2.4s; } }
/* Auch ohne Messwerte dieselbe Höhe wie ein gezeichnetes Diagramm — sonst
   springt der Dialog beim Wechsel des Ladepunkts. */
.cpc--empty { display: flex; align-items: center; justify-content: center; height: 240px; }
.cpc-line { fill: none; stroke-width: 1.6; vector-effect: non-scaling-stroke; }
.cpc-line--l1 { stroke: #2f6fed; color: #2f6fed; }
.cpc-line--l2 { stroke: #21a179; color: #21a179; }
.cpc-line--l3 { stroke: #d98324; color: #d98324; }
.cpc-line--set { stroke: var(--fg-muted); color: var(--fg-muted); stroke-dasharray: 5 4; }
.cpc-line--pw { stroke: #2f6fed; color: #2f6fed; stroke-width: 2; }

/* Bewertungen der CRA-Analyse: Zeichen und Wort tragen die Aussage, die Farbe
   beschleunigt nur das Auffinden. Deshalb keine Farbe ohne Text — und Töne, die
   auch bei Rot-Grün-Schwäche unterscheidbar bleiben (Grün/Amber/Blau/Rot). */
.verdict { white-space: nowrap; font-weight: 600; }
.verdict--met { color: #1d7a4c; }
.verdict--partly { color: #a86a00; }
.verdict--operator { color: #2f6fed; }
.verdict--open { color: #c0392b; }
.verdict--na { color: var(--fg-muted); font-weight: 500; }
:root:not([data-theme="light"]) .verdict--met { color: #4ec38a; }
:root:not([data-theme="light"]) .verdict--partly { color: #e0a33a; }
:root:not([data-theme="light"]) .verdict--operator { color: #6ea8ff; }
:root:not([data-theme="light"]) .verdict--open { color: #ef7d6f; }
:root[data-theme="dark"] .verdict--met { color: #4ec38a; }
:root[data-theme="dark"] .verdict--partly { color: #e0a33a; }
:root[data-theme="dark"] .verdict--operator { color: #6ea8ff; }
:root[data-theme="dark"] .verdict--open { color: #ef7d6f; }

/* ---------- modal ---------- */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, .35);
  display: grid;
  place-items: center;
  z-index: 100;
  animation: overlay-fade .12s ease;
}
.modal {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  width: min(640px, 96vw);
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  box-shadow: 0 10px 30px rgba(0, 0, 0, .25);
  animation: modal-pop .14s ease;
}
@keyframes overlay-fade { from { opacity: 0; } to { opacity: 1; } }
/* „Wird geladen" auf dem Weg zu einem Dialog (siehe ui.js withBusy). Über der
   Anwendung, aber UNTER den Modalen: Erscheint der Dialog, verschwindet sie
   ohnehin — und soll ihn im Übergang nicht überdecken. Klicks gehen nicht
   hindurch, das ist der Zweck. */
.busy-veil {
  position: fixed; inset: 0; z-index: 90;
  display: flex; align-items: center; justify-content: center; gap: var(--space-8);
  background: color-mix(in srgb, var(--bg) 45%, transparent);
  color: var(--fg-muted); font-size: 13px;
  animation: overlay-fade .12s ease;
}
@keyframes modal-pop {
  from { opacity: 0; transform: translateY(6px) scale(.99); }
  to   { opacity: 1; transform: none; }
}
.modal__header, .modal__footer {
  padding: 12px 16px;
  display: flex;
  align-items: center;
  gap: 12px;
  border-bottom: 1px solid var(--border);
}
.modal__header h2 { margin: 0; font-size: 15px; flex: 1; }
.modal__footer  { border-top: 1px solid var(--border); border-bottom: 0; justify-content: flex-end; }
.modal__footer-left { margin-right: auto; }   /* delete action: left-aligned in the footer */
.modal__body { padding: 16px; overflow-y: auto; }

/* ---------- native alert / confirm dialog ---------- */
.modal-overlay--dialog { z-index: 120; }      /* always above a base modal */
/* Ein Dialog, der über einem Formular-Modal liegt (z. B. die Bussuche über den
   Geräteangaben) — aber unter den Hinweisdialogen (120), damit eine Meldung
   der Suche weiterhin obenauf liegt. */
.modal-overlay--nested { z-index: 110; }
.modal.dialog { width: min(440px, 94vw); }
.dialog__body {
  white-space: pre-wrap;            /* honour newlines in messages */
  line-height: 1.5;
  font-size: 13px;
}
.dialog--warning .modal__header h2 { color: var(--danger); }

/* ---------- Bussuche im Integrationsdialog ---------- */
.scan__head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* Adapter-Dienst: je Eigenschaft eine Zeile — Beschriftung, Zustand, Aktion. */
/* Zählermodell und sein Prüfstand nebeneinander: Die Auskunft gehört neben
   die Auswahl, nicht darunter — sonst schiebt sie die Phasenfelder weg. */
.profile-row { align-items: start; }
.profile-row .warn-text { line-height: 1.4; }

.svc { display: flex; flex-direction: column; gap: 6px; }
.svc__row {
  display: grid;
  grid-template-columns: 120px auto 1fr;
  align-items: center;
  gap: 10px;
  padding: 6px 8px;
  border: 1px solid var(--border);
  border-radius: 6px;
}
.svc__label { font-size: 13px; font-weight: 600; }
.svc__actions { display: flex; gap: 6px; justify-content: flex-end; flex-wrap: wrap; }

.scan__num { width: 62px; padding: 2px 6px; font-size: 12px; }
.scan__bar {
  height: 6px; margin-top: 6px; border-radius: 3px;
  background: color-mix(in srgb, var(--fg) 12%, transparent); overflow: hidden;
}
.scan__bar-fill {
  height: 100%; width: 0; border-radius: 3px;
  background: var(--accent); transition: width .2s linear;
}
.scan__list { list-style: none; margin: 4px 0 0; padding: 0; }
.scan__list li {
  display: flex; align-items: center; gap: 8px;
  padding: 3px 0; border-top: 1px solid var(--border);
}
.scan__list li:first-child { border-top: none; }

/* ---------- side menu sub-item ---------- */
.menu-link--indent { padding-left: 32px !important; }

/* ---------- structure tree ---------- */
.tree {
  display: flex;
  flex-direction: column;
  font-size: 12px;
  line-height: 1.4;
  --tree-line: color-mix(in srgb, var(--fg) 18%, transparent);
  --tree-indent: 18px;
}
.tree__node {
  background: transparent;
  border: 0;
  box-shadow: none;
  border-radius: 0;
}
.tree__node--unassigned > summary .tree__title {
  color: color-mix(in srgb, var(--warning) 85%, var(--fg));
}
.tree__header,
.tree__node > summary,
.tree__leaf {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 2px 6px;
  border-radius: var(--radius);
  list-style: none;
}
.tree__node > summary {
  cursor: pointer;
  user-select: none;
}
.tree__node > summary::-webkit-details-marker { display: none; }
.tree__node > summary:hover,
.tree__leaf:hover {
  background: color-mix(in srgb, var(--fg) 4%, transparent);
}
.tree__icon {
  display: inline-block;
  width: 12px;
  text-align: center;
  color: var(--fg-muted);
  transition: transform .12s ease;
  font-size: 10px;
}
.tree__node[open] > summary .tree__icon { transform: rotate(90deg); }
.tree__title {
  font-weight: 500;
  margin-right: 8px;
  white-space: nowrap;
}
.tree__pills {
  display: flex;
  gap: 4px;
  margin-left: auto;
  flex-wrap: wrap;
}
.tree__header--pcc .tree__title { font-weight: 600; }

/* Dashboard tree: align pills into fixed columns by type so, reading down,
   each column holds the same kind of value. The pill block is right-aligned
   (margin-left:auto) and every row shares the same right edge, so the fixed
   columns line up across nesting depths.
   Columns: EV state | max/assigned current | actual current | connection. */
.tree__pills--cols {
  display: grid;
  /* EV state column holds the A/B/C badge + its text label (e.g. "Suspended
     EVSE", "nicht angeschlossen"), so it is wide enough not to spill over. */
  grid-template-columns: 12em 7em 12.5em 7em;
  gap: 4px;
  align-items: center;
  justify-items: start;
}
.tree__col { display: flex; align-items: center; gap: 6px; min-width: 0; }
.tree__col--evstate { overflow: hidden; }
.tree__col--evstate .state-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.tree__col--measured { justify-self: stretch; }

/* tree connector lines — drawn per item so siblings link up and the last
   sibling cuts its vertical line at its own middle (└─ instead of ├─). */
.tree__children {
  padding: 0 0 0 var(--tree-indent);
  margin-left: 7px;
  display: flex;
  flex-direction: column;
}
.tree__children > .tree__node,
.tree__children > .tree__leaf {
  position: relative;
}
.tree__children > .tree__node::before,
.tree__children > .tree__leaf::before {
  content: "";
  position: absolute;
  left: calc(var(--tree-indent) * -1 + 5px);
  top: 0;
  bottom: 0;
  border-left: 1px solid var(--tree-line);
}
.tree__children > .tree__node::after,
.tree__children > .tree__leaf::after {
  content: "";
  position: absolute;
  left: calc(var(--tree-indent) * -1 + 5px);
  top: 12px;
  width: calc(var(--tree-indent) - 5px);
  border-top: 1px solid var(--tree-line);
}
/* JS marks the last real sibling (not the trailing slot) as is-last so
   the vertical line stops at the corner. */
.tree__children > .is-last::before { bottom: auto; height: 12px; }

.tree__empty {
  color: var(--fg-muted);
  font-style: italic;
  padding: 2px 0;
  font-size: 11px;
}

/* drop slots: layout-neutral markers between siblings.
   The slot itself has zero height; the hit zone is an absolute pseudo
   that overlaps the gap between rows, so dragging does NOT push the
   tree around. The visual drop-line is a second pseudo, also positioned
   absolutely on the slot's natural y. */
.tree__slot {
  height: 0;
  margin: 0;
  position: relative;
  pointer-events: none;
}
/* Hit zone: shows up only while a drag is active. ±7px around the slot
   means the user can hit it from either neighbour without layout shift. */
body.is-dragging .tree__slot::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -7px;
  height: 14px;
  pointer-events: auto;
  z-index: 2;
}
/* The visual indicator — invisible until is-drop-target turns it on. */
.tree__slot::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -1px;
  height: 2px;
  background: transparent;
  border-radius: 1px;
  transition: background .08s ease;
  z-index: 3;
}
.tree__slot.is-drop-target::after {
  background: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}

/* ---------- pills ---------- */
.pill {
  display: inline-flex;
  align-items: center;
  padding: 0 6px;
  font-size: 10px;
  line-height: 16px;
  background: color-mix(in srgb, var(--fg) 8%, transparent);
  color: var(--fg);
  border-radius: 4px;
  white-space: nowrap;
}
/* Werte-Marken (Soll-/Ist-Strom) im Dashboard: dieselbe Schriftgröße wie die
   Namen daneben — bei 10px las sich der wichtigste Wert der Zeile wie eine
   Fußnote. */
.pill--val { font-size: inherit; line-height: 1.5; padding: 0 8px; }
.pill--iso { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent); }
/* Noch nicht entschieden: dieselbe Marke, aber blass und gestrichelt umrandet.
   Sie soll sich von einer belegten Fassung unterscheiden lassen, ohne eine
   eigene Farbe zu erfinden — es ist derselbe Sachverhalt in einem früheren
   Zustand. */
.pill--iso.is-pending {
  background: color-mix(in srgb, var(--fg) 6%, transparent);
  color: var(--fg-muted);
  border: 1px dashed color-mix(in srgb, var(--accent) 45%, transparent);
}
.pill--plain   { background: color-mix(in srgb, var(--fg) 8%, transparent); color: var(--fg-muted); }
.pill--current {
  background: color-mix(in srgb, var(--ok) 18%, transparent);
  color: color-mix(in srgb, var(--ok) 80%, var(--fg));
}
.pill--time {
  background: color-mix(in srgb, var(--warning) 20%, transparent);
  color: color-mix(in srgb, var(--warning) 80%, var(--fg));
}
.pill--sim {
  background: var(--accent);
  color: var(--accent-fg);
}
.pill--connected,
.pill--ok {
  background: color-mix(in srgb, var(--ok) 22%, transparent);
  color: color-mix(in srgb, var(--ok) 85%, var(--fg));
}
.pill--offline {
  background: color-mix(in srgb, var(--danger) 22%, transparent);
  color: color-mix(in srgb, var(--danger) 88%, var(--fg));
}
.pill--warn {
  background: color-mix(in srgb, var(--warning) 22%, transparent);
  color: color-mix(in srgb, var(--warning) 85%, var(--fg));
}
/* External endpoints: scheduled validity windows shown as a list under a row. */
.ext-windows-row > td { padding-top: 2px; padding-bottom: 8px; }
.ext-windows-head { margin-bottom: 3px; }
.ext-windows { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px; }
.ext-window {
  display: flex; align-items: center; gap: 8px;
  padding: 2px 8px; font-size: 12px; border-radius: 4px;
  background: color-mix(in srgb, var(--fg) 6%, transparent);
  color: var(--fg-muted);
}
.ext-window--active { color: var(--fg); }
.ext-window__name { font-weight: 600; color: var(--fg); }
.ext-window__val { font-variant-numeric: tabular-nums; min-width: 92px; }
.ext-window__range { flex: 1; }
.btn-icon {
  border: none; background: transparent; cursor: pointer;
  color: var(--fg-muted); padding: 0 2px; line-height: 1; font-size: 13px;
}
.btn-icon:hover { color: var(--danger); }
/* Löschen in den Zeitregeln des Wochenplans: rot hinterlegt statt nur rot beim
   Überfahren. In einer Zeile aus sieben Häkchen, zwei Uhrzeiten und drei Zahlen
   geht ein graues ✕ unter — und es ist der einzige Knopf, der etwas wegnimmt. */
.btn-icon--danger {
  background: var(--danger); color: #fff;
  border-radius: var(--radius); padding: 2px 7px;
}
.btn-icon--danger:hover { color: #fff; filter: brightness(.93); }

/* Entity markers (tree): external input active (zap) + windowed (clock). */
.tree__ext { display: inline-flex; align-items: center; gap: 2px; margin-left: 4px; vertical-align: middle; }
.tree__ext-icon { width: 14px; height: 14px; }
.tree__ext-icon--zap { color: var(--accent); }
.tree__ext-icon--clock { color: var(--warning); }
.tree__ext-icon--hourglass { color: var(--warning); }
/* "acting" wins over --zap/--clock (declared last): a currently-applied external
   value, or an active time window, is shown in red. */
.tree__ext-icon--acting { color: var(--danger); }

/* ---------- dashboard: tabs / tiles / pcc header ---------- */
/* Tab switchers: a left-aligned horizontal tab strip directly above the area
   it controls. Full-width underline, tabs flush left. */
.tabs { display: flex; justify-content: flex-start; gap: 4px; border-bottom: 2px solid var(--border); margin: 4px 0 12px; }
.tabs .tab {
  border: 0;
  background: transparent;
  color: var(--fg-muted);
  padding: 8px 16px;
  cursor: pointer;
  font-size: 13px;
  border-bottom: 2px solid transparent;
  margin-bottom: -2px;               /* sit on top of the bar's bottom border */
  transition: color .12s ease, border-color .12s ease;
}
.tabs .tab:hover { color: var(--fg); }
.tabs .tab.is-active {
  color: var(--accent);
  border-bottom-color: var(--accent);
  font-weight: 600;
}
/* OCPP version sub-tabs inside the settings OCPP panel */
.tabs--sub { border-bottom-width: 1px; margin: 0 0 12px; }
.tabs--sub .tab { padding: 6px 12px; font-size: 12px; }
/* Tab panels: keep all in the DOM (so save reads every field), hide inactive.
   Explicit so a `.card { display:… }` rule can't override the [hidden] UA rule. */
.settings-panel[hidden], .settings-subpanel[hidden] { display: none; }
/* Reiter-Panels im Modal: ``flow-root`` verhindert, dass der Außenabstand des
   letzten Kindes aus dem Panel herausfällt. Sonst misst der Höhenausgleich zu
   wenig, und der Dialog springt beim Umschalten doch wieder um ein paar Pixel. */
.modal__body > section[data-panel] { display: flow-root; }
.modal__body > section[data-panel][hidden] { display: none; }

.dash-pcc {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  margin-bottom: 12px;
}

.list-group-row td { background: color-mix(in srgb, var(--fg) 5%, transparent); }

.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 12px;
}
.tile {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px 12px;
  box-shadow: var(--shadow);
  /* Nichts darf in die Nachbarkachel ragen: Die Kopfzeile war zu breit,
     sobald ein Abzeichen dazukam (z. B. „preisoptimiert"), und die daneben
     liegende Kachel malte über den Pause-Knopf der linken — der Klick landete
     dann auf einem Abzeichen und tat nichts. */
  overflow: hidden;
}
/* Lieber umbrechen als überlaufen; der Name gibt als Erstes nach. */
.tile__head { display: flex; align-items: center; justify-content: space-between;
              gap: 8px; flex-wrap: wrap; }
.tile__head > strong { min-width: 0; overflow-wrap: anywhere; }
.tile__sub { color: var(--fg-muted); font-size: 12px; margin: 2px 0 8px; }
.tile__cp { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding: 3px 0; }
.tile__cp > span:first-child { min-width: 42px; font-weight: 500; }

/* simulated CP: settings left, sliders right */
.sim-cp__grid {
  display: grid;
  grid-template-columns: minmax(220px, 1fr) minmax(240px, 1.2fr);
  gap: 16px;
  margin-top: 8px;
  align-items: start;
}
.sim-cp__left { display: flex; flex-direction: column; gap: 8px; }
@media (max-width: 720px) {
  .sim-cp__grid { grid-template-columns: 1fr; }
}

/* IEC 61851 live state badges — der Zustandsbuchstabe ist die kompakteste
   Information der Zeile und war mit 10px kleiner als der Text, den er
   zusammenfasst. `inherit` gilt überall gleich: Dashboard-Baum, Tabelle,
   Kacheln von „Meine Ansicht“. */
.live-state { font-weight: 600; min-width: 1.4em; justify-content: center;
              font-size: inherit; line-height: 1.5; }
.live-state--A { background: color-mix(in srgb, var(--fg) 14%, transparent); color: var(--fg-muted); }
.live-state--B { background: color-mix(in srgb, var(--warning) 22%, transparent); color: color-mix(in srgb, var(--warning) 80%, var(--fg)); }
.live-state--C, .live-state--D { background: color-mix(in srgb, var(--ok) 22%, transparent); color: color-mix(in srgb, var(--ok) 80%, var(--fg)); }
.live-state--E, .live-state--F { background: color-mix(in srgb, var(--danger) 22%, transparent); color: color-mix(in srgb, var(--danger) 85%, var(--fg)); }

/* ---------- drag & drop ---------- */
.tree__node > summary[draggable] { cursor: grab; }
.tree__node.is-dragging > summary { opacity: .4; }
.tree__node.is-drop-target > summary {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  outline: 1px dashed var(--accent);
  outline-offset: -1px;
}
/* Fallback for the case when the drag hovers a container without a slot */
.tree__children.is-drop-target {
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  outline: 1px dashed var(--accent);
  outline-offset: -1px;
  border-radius: var(--radius);
}

/* ---------- layout helpers (replace inline styles) ---------- */
/* Seitenüberschrift mit Symbol: dasselbe Bild wie im Menü, links vom Titel.
   Das h2 wird dafür selbst zum Flex-Behälter — sonst säße das SVG auf der
   Grundlinie und stünde einen Hauch zu tief. */
.toolbar h2 {
  margin: 0; flex: 1;
  display: flex; align-items: center; gap: var(--space-8); min-width: 0;
}
/* Globale Suche. Als Overlay über allem, weil sie von jeder Seite aus
   erreichbar sein soll — nicht als Feld in der Leiste, das auf schmalen
   Bildschirmen mit dem Anlagennamen um Platz ringt. */
.search {
  position: fixed; inset: 0; z-index: 60;
  background: color-mix(in srgb, #000 38%, transparent);
  display: flex; justify-content: center; align-items: flex-start;
  padding: 10vh 16px 16px;
}
.search__box {
  width: min(680px, 100%);
  background: var(--bg-elevated); color: var(--fg);
  border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: 0 18px 48px rgba(0, 0, 0, .35);
  overflow: hidden;
}
.search__input {
  width: 100%; box-sizing: border-box; border: 0; outline: 0;
  background: transparent; color: inherit;
  font-size: 17px; padding: 14px 16px;
  border-bottom: 1px solid var(--border);
}
.search__results { list-style: none; margin: 0; padding: 4px; max-height: 52vh; overflow-y: auto; }
.search__item {
  display: flex; align-items: baseline; gap: 10px;
  padding: 8px 12px; border-radius: var(--radius); cursor: pointer;
}
.search__item:hover, .search__item.is-active { background: color-mix(in srgb, var(--accent) 16%, transparent); }
/* Feste Breite für die Art, damit die Beschriftungen untereinander stehen und
   die Liste als Spalte lesbar bleibt. */
.search__kind {
  flex: none; width: 108px; font-size: 11px; color: var(--fg-muted);
  text-transform: uppercase; letter-spacing: .04em;
}
.search__text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Wohin der Treffer führt. „Ladekonto" gibt es an mehreren Stellen — ohne
   das Ziel stünde es mehrfach gleich da und man müsste raten. */
.search__where { flex: none; font-size: 11px; color: var(--fg-muted); white-space: nowrap; }
.search__empty { padding: 14px 16px; color: var(--fg-muted); font-size: 13px; }
.search__foot { padding: 8px 16px; border-top: 1px solid var(--border); }
.statusbar__search { padding: 2px 6px; line-height: 1; }
.statusbar__search svg { width: 16px; height: 16px; display: block; }

/* Kurz hervorheben, wohin ein Suchtreffer gesprungen ist. Ohne das landet man
   mitten in einer Maske und sucht das Feld ein zweites Mal — diesmal mit den
   Augen. Der Rahmen verschwindet von selbst; eine dauerhafte Markierung wäre
   beim nächsten Blick auf die Seite nur noch rätselhaft. */
.is-found {
  animation: found-pulse 2.4s ease-out;
  border-radius: var(--radius);
}
@keyframes found-pulse {
  0%, 60% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 55%, transparent); }
  100%    { box-shadow: 0 0 0 3px transparent; }
}
@media (prefers-reduced-motion: reduce) {
  .is-found { animation: none; box-shadow: 0 0 0 3px var(--accent); }
}

.page-icon {
  flex: none; width: 22px; height: 22px;
  color: var(--fg-muted);
}
/* Verweis auf die jeweils andere Ansicht (Anlage ↔ Meine Ansicht).
   Bewusst leise: ein Hinweis am Rand, kein Knopf, der mit der
   Werkzeugleiste um Aufmerksamkeit ringt. */
.toolbar__hint { text-decoration: none; white-space: nowrap; }
.toolbar__hint:hover { text-decoration: underline; }

/* spacing — see --space-* scale in :root */
.m-0 { margin: 0; }
.mt-4 { margin-top: var(--space-4); }
.mt-8 { margin-top: var(--space-8); }
.mb-2 { margin-bottom: var(--space-2); }
.mb-6 { margin-bottom: var(--space-6); }
.mb-8 { margin-bottom: var(--space-8); }
.mb-10 { margin-bottom: var(--space-10); }
.my-8 { margin: var(--space-8) 0; }
.ml-4 { margin-left: var(--space-4); }
.ml-8 { margin-left: var(--space-8); }

/* fl/grid */
.flex-1 { flex: 1; }
.ta-right { text-align: right; }
.row-flex { display: flex; gap: var(--space-8); align-items: center; }
.row-flex--wrap { flex-wrap: wrap; }
.col-flex { display: flex; flex-direction: column; }
.gap-8 { gap: var(--space-8); }
.gap-10 { gap: var(--space-10); }
.gap-12 { gap: var(--space-12); }
.form-grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 16px; }
.form-grid-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0 16px; }

/* Compact settings panels: the field grids pack narrow fields from the left and
   wrap, instead of stretching each field across a full-width column. Row spacing
   comes from the .field margin.

   Ausgenommen sind die OCPP-Endpunktreiter (v16/v201/v21): Dort stehen Pfade,
   Backend-URLs und die Beispieladressen zum Kopieren — die brauchen die ganze
   Spaltenbreite. Bis 4.12.16 hieß das Panel schlicht "ocpp"; seit der Aufteilung
   in eigene Reiter je Version sind es drei. */
.settings-panel:not([data-panel="v16"]):not([data-panel="v201"]):not([data-panel="v21"]) .form-grid-2,
.settings-panel:not([data-panel="v16"]):not([data-panel="v201"]):not([data-panel="v21"]) .form-grid-3 {
  display: flex;
  flex-wrap: wrap;
  gap: 0 24px;
}
.settings-panel:not([data-panel="v16"]):not([data-panel="v201"]):not([data-panel="v21"]) .form-grid-2 > *,
.settings-panel:not([data-panel="v16"]):not([data-panel="v201"]):not([data-panel="v21"]) .form-grid-3 > * {
  flex: 0 1 210px;
}

/* Untereinander statt nebeneinander: Wo wenige Werte zusammengehören und jeder
   erklärt werden will, liest sich eine Spalte besser als eine Reihe. */
.form-stack { display: flex; flex-direction: column; }
.form-stack > * { max-width: 320px; }

/* Rechtematrix: viele Zeilen, fünf schmale Spalten. Der Bereichsname bleibt
   links stehen, wenn quer gescrollt wird — sonst weiß man nach der dritten
   Spalte nicht mehr, worüber man entscheidet. */
.acl-table th.acl-section {
  position: sticky;
  left: 0;
  background: var(--bg-elevated);
  text-align: left;
  font-weight: 500;
  white-space: nowrap;
}
.acl-table tr.acl-theme th {
  text-align: left;
  padding-top: 14px;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--fg-muted);
  background: transparent;
}
.acl-table th.acl-section--d1 { padding-left: 22px; font-weight: 400; }
.acl-table th.acl-section--d2 { padding-left: 40px; font-weight: 400; }
.acl-table th.acl-section--d1::before,
.acl-table th.acl-section--d2::before { content: "└ "; opacity: .5; }
.acl-table td { text-align: center; }
.input--sm { padding: 2px 4px; font-size: 12px; }

/* Rechtematrix, eine Zelle: vier Zustände als Knopfgruppe. Aufklappmenüs
   verbergen bei 320 Zellen genau das, was man sehen will — hier trägt die
   Farbe die Aussage, und der Zustand ist ohne Klick ablesbar.

   Die Symbole sind schlichte Glyphen (– ● ◐ ✕), keine Emoji: Sie rendern auf
   jedem System gleich und bleiben bei 11 px lesbar. Die Farbe kommt dazu, ist
   aber nie die einzige Information — Form und Titel sagen dasselbe, damit die
   Matrix auch für Farbenblinde bedienbar bleibt. */
/* Fahrzeugdaten im Kennungsdialog: schmale Beschriftungsspalte, Werte links
   bündig. Zwei solche Tabellen untereinander trennen „was gemeldet wurde" von
   „woher es kam". */
.veh-facts { width: auto; }
.veh-facts th { text-align: left; font-weight: 400; color: var(--fg-muted);
                white-space: nowrap; padding-right: 14px; }
.veh-facts td { white-space: nowrap; }

.acl-cell { display: inline-flex; gap: 1px; }
.acl-btn {
  width: 22px; height: 20px; padding: 0;
  border: 1px solid var(--border);
  background: var(--bg-elevated);
  color: var(--fg-muted);
  font-size: 11px; line-height: 1; cursor: pointer;
}
.acl-btn:first-child { border-radius: 3px 0 0 3px; }
.acl-btn:last-child { border-radius: 0 3px 3px 0; }
.acl-btn:hover { border-color: var(--accent); }
.acl-btn.is-on { color: #fff; border-color: transparent; font-weight: 700; }
.acl-btn--def.is-on    { background: var(--acl-def); }
.acl-btn--rw.is-on     { background: var(--acl-rw); color: #08130c; }
.acl-btn--ro.is-on     { background: var(--acl-ro); color: #201a00; }
.acl-btn--hidden.is-on { background: var(--acl-hidden); }

/* Die **Vorgabe** in gedimmter Farbe: Steht eine Zelle auf „Vorgabe", bekommt
   der Knopf der Stufe, die dann gilt, einen blassen Anstrich. Damit liest sich
   die Matrix als tatsächliche Rechtelage — kräftig, wo jemand entschieden hat,
   blass, wo geerbt wird. Ohne das müsste man jede Zelle antippen, um zu sehen,
   was ohne Eingriff passiert. */
.acl-btn.is-default { color: var(--accent-fg); font-weight: 600; opacity: .55; }
.acl-btn--rw.is-default     { background: var(--acl-rw); }
.acl-btn--ro.is-default     { background: var(--acl-ro); }
.acl-btn--hidden.is-default { background: var(--acl-hidden); }
/* Abweichungen vom Standard hervorheben — sonst sucht man in 64 Zeilen, wo
   überhaupt etwas gesetzt ist. */
.acl-cell--set { outline: 1px solid var(--accent); outline-offset: 1px; border-radius: 4px; }
.table-scroll { overflow-x: auto; }

/* Zwei Spalten in einem Einstellungs-Panel. auto-fit statt fester Zweispaltigkeit:
   Unter ~600 px Inhaltsbreite fällt es von selbst auf eine Spalte zurück, statt
   zwei unlesbar schmale zu erzwingen. */
.settings-cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 0 32px;
  align-items: start;
}

/* Feinabstimmung: eigener Reiter für die Werte, die beim Einrichten einmal
   gesetzt und danach in Ruhe gelassen werden. Die Untergruppen trennen, wonach
   man sucht — sonst wären es acht gleich aussehende Zahlenfelder. */
.tuning h4 {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--fg-muted);
  margin: 12px 0 4px;
}
/* simulated CP current sliders: stacked, label left / value right */
.cp-sliders { display: flex; flex-direction: column; gap: 6px; }
.cp-slider {
  display: grid;
  grid-template-columns: 1.8em 1fr 3em;
  align-items: center;
  gap: 8px;
  margin: 0;
}
.cp-slider__label { color: var(--fg-muted); font-size: 12px; }
.cp-slider input[type="range"] { width: 100%; }
.cp-slider output { text-align: right; font-size: 12px; font-variant-numeric: tabular-nums; }

/* text — see --fs-* scale in :root */
.muted { color: var(--fg-muted); }
.fs-12 { font-size: var(--fs-12); }
.fs-13 { font-size: var(--fs-13); }
/* key/value rows (e.g. certificate metadata): fixed-width label column */
.kv-key { min-width: 9rem; }
.clickable { cursor: pointer; }
.minw-48 { min-width: 48px; }

/* components */
.card--wide { max-width: none; }
.hist-card { min-width: 160px; flex: 1 1 160px; margin-bottom: 0; }
.hist-card__val { font-size: 22px; font-weight: 600; margin-top: 2px; }
.cp-row { padding: 6px; border: 1px dashed var(--border); border-radius: var(--radius); }
.sim-cp-row { border: 1px dashed var(--border); border-radius: var(--radius); padding: 8px 10px; }

/* Drei Darstellungen. „Groß" ist der Block je Ladepunkt, „kompakt" presst ihn
   in EINE Zeile innerhalb der Stationskachel, „Liste" ist eine echte Tabelle
   über alle Stationen. Die Bedienelemente sind in allen dreien dieselben
   Felder mit derselben Verdrahtung — in den engen Ansichten werden Status und
   Kommunikationsart nur als Auswahlfeld statt als Knopfreihe gezeichnet. */
.sim-fold { padding: 0 6px; line-height: 1.2; color: var(--fg-muted); }
.card.is-folded [data-bind="sim-cps"] { display: none; }
.card.is-folded { padding-bottom: 8px; }

/* Kompakt: ein Ladepunkt = eine Zeile; der Regler bekommt den Rest der Breite.
   Der Status bleibt hier eine Knopfreihe — dafür ist der Platz da. */
.sim-view--compact .card { padding: 8px 10px; }
.sim-view--compact .sim-cp-row {
  display: grid;
  grid-template-columns: minmax(140px, auto) minmax(300px, auto) 1fr;
  align-items: center;
  gap: 12px;
  padding: 5px 8px;
  border-style: solid;
}
.sim-view--compact .toggle-group { flex-wrap: nowrap; }
.sim-view--compact .toggle-group button { padding: 2px 7px; }
.sim-view--compact .sim-cp-row > .row-flex { margin: 0; gap: 6px; }
.sim-view--compact .sim-cp__grid { display: contents; }
.sim-view--compact .sim-cp__left {
  flex-direction: row; align-items: center; gap: 6px; min-width: 0;
}
.sim-view--compact .sim-cp__left .field { margin: 0; min-width: 0; }
.sim-view--compact .sim-cp__left label { display: none; }
.sim-view--compact .sim-cp__left select { min-width: 64px; }
.sim-view--compact .sim-cp__left input[type="text"] { width: 9em; }
.sim-view--compact .sim-cp__right {
  display: flex; align-items: center; gap: 10px; min-width: 0;
}
.sim-view--compact .sim-cp__right > .muted { display: none; }
.sim-view--compact .cp-sliders { flex: 1; min-width: 0; gap: 2px; }
/* Ein Regler für alle drei Phasen — er bekommt die volle Breite der Spalte. */
.sim-view--compact .cp-slider { grid-template-columns: 1.8em 1fr 3em; }

/* Liste: eine Tabelle über alle Stationen, eine Zeile je Ladepunkt. */
.sim-list { width: 100%; }
.sim-list th, .sim-list td { padding: 4px 8px; vertical-align: middle; }
.sim-list .field { margin: 0; }
.sim-list label:not(.linked-label) { display: none; }
.sim-list select { min-width: 64px; }
/* Die Kennung ist selten länger als eine RFID-UID; 20 Zeichen reichen und
   lassen den Platz den Spalten, die man beim Einstellen wirklich liest. */
.sim-list__uid { width: 20ch; min-width: 0; max-width: 20ch; }
.sim-list__station { white-space: nowrap; }
.sim-list__pills { white-space: nowrap; font-size: var(--fs-11); }
/* Stromwunsch als Zahl: In einer Tabellenzeile ist ein Schieber entweder zu
   schmal zum Treffen oder zu breit für die Spalte. Der Wert gilt für alle
   Phasen — ein Feld, ein Wert. */
.sim-list__demand { white-space: nowrap; }
.sim-list__amps { width: 5.5em; text-align: right; }
/* Gekoppelte Phasen: ein Regler genügt — die anderen tragen denselben Wert. */
.sim-cp-row.is-linked .cp-sliders .cp-slider:not(:first-child) { display: none; }
.sim-view--large .sim-cp-row.is-linked .cp-slider { display: grid; }

.sim-live-label { display: flex; align-items: center; gap: 6px; color: var(--fg-muted); font-size: 12px; margin-left: 12px; }
.linked-label { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; font-size: 12px; color: var(--fg-muted); }
.menu-logout { margin-top: 24px; color: var(--fg-muted); }

/* clickable rows / tiles */
.row-link { cursor: pointer; }
tr.row-link:hover { background: color-mix(in srgb, var(--fg) 5%, transparent); }
.tile.row-link:hover { border-color: var(--accent); }
.putil__card.row-link:hover { border-color: var(--accent); }

/* Gear button that opens an entity's dialog from a dashboard-tree node. It sits
   at the far right of the summary row (after the right-aligned pills); the PCC
   header has no pills, so it pushes itself right. */
.tree__open {
  flex: none;
  margin-left: 8px;
  padding: 1px 5px;
  background: none;
  border: none;
  border-radius: var(--radius);
  color: var(--fg-muted);
  font-size: 13px;
  line-height: 1.2;
  cursor: pointer;
  opacity: .55;
}
.tree__open:hover {
  opacity: 1;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}
.tree__header--pcc .tree__open { margin-left: auto; }

/* segmented toggle (e.g. dashboard "group by") */
.seg { display: inline-flex; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
.seg__btn {
  border: 0;
  border-right: 1px solid var(--border);
  background: var(--bg-elevated);
  color: var(--fg);
  padding: 5px 12px;
  cursor: pointer;
  font-size: 13px;
}
.seg__btn:last-child { border-right: 0; }
.seg__btn.is-active { background: var(--accent); color: var(--accent-fg); }
/* Senkrecht gestapelt: Die Trennlinie wandert von rechts nach unten, sonst
   stünde sie quer zur Stapelrichtung. */
.seg--vert { flex-direction: column; }
.seg--vert .seg__btn { border-right: 0; border-bottom: 1px solid var(--border); text-align: center; }
.seg--vert .seg__btn:last-child { border-bottom: 0; }

/* dashboard phase-utilization overview: collapsible row of compact cards with
   vertical faders (PCC / UV / group), each scaled to its own max. */
.putil { margin-bottom: 12px; }
.putil__toggle {
  border: 0; background: transparent; color: var(--fg); cursor: pointer;
  font-size: 13px; font-weight: 500; padding: 2px 0;
  display: flex; align-items: center; gap: 4px;
}
.putil__cards { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 6px; }
.putil__card {
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 6px 8px;
}
.putil__name {
  font-size: 11px; font-weight: 500; max-width: 130px; margin-bottom: 4px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  display: flex; align-items: center; gap: 4px;
}
.putil__bars { display: flex; gap: 6px; align-items: flex-end; }
.putil__max { font-size: 10px; text-align: center; margin-top: 3px; }
.vbar { display: flex; flex-direction: column; align-items: center; gap: 2px; }
.vbar__num { font-size: 10px; font-variant-numeric: tabular-nums; color: var(--fg-muted); }
.vbar__track {
  width: 10px; height: 40px;
  background: color-mix(in srgb, var(--fg) 12%, transparent);
  border-radius: 3px; overflow: hidden;
  display: flex; align-items: flex-end;
}
.vbar__fill { width: 100%; background: var(--accent); }
.vbar__fill--over { background: var(--danger); }
.vbar__lbl { font-size: 9px; color: var(--fg-muted); }

/* ---- Tools: Logs (System / Lastmanagement / OCPP) ---- */
.log-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 8px 0; }
/* Zeitfenster über den Logs: eigene Zeile, damit die Chips nicht mit den
   Level-Chips verschmelzen. */
.log-controls--time { margin-top: 4px; }
/* Zeitfelder der Logs und der Auswertung: Ein `datetime-local` braucht in
   Chromium 14,8em für „10.09.2026, 13:16" samt Kalendersymbol (gemessen, in
   de und en gleich). Bei 12,5em fielen die Minuten weg. Der Selektor nennt das
   Element, weil dieselbe Klasse unten die Zeitspalte der Tabelle trägt. */
input.log-time { width: 15.5em; }
.log-ctl-grow { flex: 1 1 auto; }
.log-chips { display: inline-flex; gap: 4px; flex-wrap: wrap; }
.log-chip {
  padding: 3px 10px; border: 1px solid var(--border); border-radius: 999px;
  background: var(--bg-elevated); color: var(--fg); cursor: pointer; font-size: 12px;
}
.log-chip:hover { border-color: var(--accent); }
.log-chip.is-active { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }
/* Ausgesetzter Takt: Der gewählte Knopf pulsiert, statt dass irgendwo daneben
   ein Satz steht. Der Knopf IST die Uhr — wer sieht, dass er blinkt, weiß, dass
   nichts nachläuft, und klickt genau dorthin, um sie wieder anzuwerfen. */
.log-chip--paused { animation: log-chip-pause 1.6s ease-in-out infinite; }
@keyframes log-chip-pause { 0%, 100% { opacity: 1; } 50% { opacity: .4; } }
@media (prefers-reduced-motion: reduce) {
  .log-chip--paused { animation: none; outline: 2px dashed var(--accent); outline-offset: 2px; }
}
.log-search { flex: 1 1 200px; max-width: 320px; }
.log-meta { margin-bottom: 6px; }
.log-time { white-space: nowrap; font-variant-numeric: tabular-nums; color: var(--fg-muted); }
.log-badge {
  display: inline-block; padding: 1px 8px; border-radius: 999px;
  font-size: 11px; font-weight: 600; border: 1px solid transparent;
}
.log-badge--info {
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 40%, transparent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.log-badge--warning {
  color: var(--warning);
  border-color: color-mix(in srgb, var(--warning) 40%, transparent);
  background: color-mix(in srgb, var(--warning) 12%, transparent);
}
.log-badge--error {
  color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 40%, transparent);
  background: color-mix(in srgb, var(--danger) 12%, transparent);
}
.log-badge--debug {
  color: var(--fg-muted); border-color: var(--border);
  background: color-mix(in srgb, var(--fg-muted) 8%, transparent);
}
.log-dropped { color: var(--warning); }
.log-detail { display: flex; flex-direction: column; gap: 8px; }
.log-kv { display: grid; grid-template-columns: 170px 1fr; gap: 12px; align-items: baseline; }
.log-kv__k { color: var(--fg-muted); font-size: 12px; }
/* Beschriftung mit Knopf daneben (Datenfeld im Log-Detail): Der Knopf
   sitzt rechts auf derselben Zeile, damit er nicht in die Daten rutscht. */
.log-kv__k--action { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.log-kv__v { word-break: break-word; }
.log-kv--block { grid-template-columns: 1fr; }
.log-payload {
  margin: 4px 0 0; padding: 10px; background: var(--bg);
  border: 1px solid var(--border); border-radius: var(--radius);
  overflow: auto; max-height: 320px; font-size: 12px;
  white-space: pre-wrap; word-break: break-word;
}

/* Copyable example field (e.g. proxy URL) with an inline copy button. */
.proxy-url { display: flex; gap: 6px; align-items: center; }
.proxy-url__text {
  flex: 1; min-width: 0; white-space: normal; overflow-wrap: anywhere;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13px;
  background: color-mix(in srgb, var(--fg-muted) 10%, transparent);
  color: var(--fg); border-radius: var(--radius); padding: 5px 8px;
}
.proxy-url__ph { color: var(--fg-muted); opacity: .55; }
.copy-btn { flex: none; padding: 6px 10px; }

/* Anleitung (rechte Spalte) im Dialog für externe Eingänge. */
.ext-guide__title { font-weight: 600; }
.ext-guide__item { margin-top: 10px; }
.ext-guide__label { font-size: 12px; color: var(--fg-muted); margin-bottom: 3px; }
.ext-guide .proxy-url__text { font-size: 12px; }
.ext-guide__seg .btn { padding: 4px 12px; font-size: 12px; }

/* HT/NT-Zonen-Editor: eine Regelzeile = Wochentage + Zeitbereich. */
/* Wochenregel-Zeile — geteilt vom HT/NT-Zoneneditor (.zone-rule) und vom
   Wochenplan der externen Eingänge (.week-rule). Gleiche Bedienung, gleiches
   Aussehen; die zwei Namen halten die Aufrufstellen lesbar. */
.zone-rule, .week-rule {
  display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  padding: 6px 0; border-bottom: 1px solid var(--border);
}
.zone-rule:last-child, .week-rule:last-child { border-bottom: 0; }
.zone-rule__days, .week-rule__days { display: flex; flex-wrap: wrap; gap: 2px; }
.zone-rule__day, .week-rule__day {
  display: inline-flex; align-items: center; gap: 3px;
  font-size: 12px; padding: 2px 6px; border: 1px solid var(--border);
  border-radius: var(--radius); cursor: pointer; user-select: none;
}
.zone-rule__day:has(input:checked), .week-rule__day:has(input:checked) {
  background: color-mix(in srgb, var(--accent, #E41D24) 14%, transparent);
  border-color: color-mix(in srgb, var(--accent, #E41D24) 45%, var(--border));
}
.zone-rule__day input, .week-rule__day input { margin: 0; }
.zone-rule__times, .week-rule__times { display: flex; align-items: center; gap: 6px; }
.zone-rule__times input[type="time"], .week-rule__times input[type="time"] { width: auto; }
.week-rule__times input[type="datetime-local"] { width: auto; }
.week-rule__values { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* Strom-/Leistungswerte einer Zeitregel: vier Zeichen genügen — 16, 32, 6.5.
   Die Spinner sind global abgeschaltet, deshalb zählt nur Text + Innenabstand. */
.week-rule__values input[type="number"] { width: calc(4ch + 18px); }
.week-rule__values input[type="text"] { width: 140px; }

/* Zeitplan im Endpunkt-Dialog — seit den Reitern ein eigener Reiter, deshalb
   ohne eigenen Rahmen. Geblieben sind die Zwischenüberschrift und der Trenner
   vor den Einmalfenstern. */
.ext-sched__subtitle { font-weight: 600; font-size: 13px; }
.ext-sched__windows { border-top: 1px dashed var(--border); padding-top: 8px; }

/* Mehrzeiliger Beispielaufruf (Preis-Ingest): eigener Block mit horizontalem
   Scroll — ein langer curl-Aufruf darf Dialog oder Kachel nicht sprengen. */
.code-block {
  margin: 0; padding: 8px 10px; max-width: 100%; overflow-x: auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px;
  line-height: 1.45; white-space: pre;
  background: color-mix(in srgb, var(--fg-muted) 10%, transparent);
  color: var(--fg); border-radius: var(--radius);
}

/* OCPP-WS-TLS-Widget (Sicherheit-Tab). */
hr.sep { border: 0; border-top: 1px solid var(--border); margin: 16px 0; }
.ok-text { color: var(--ok, #2e7d32); font-weight: 600; }
textarea.fld-pem {
  width: 100%; min-height: 64px; resize: vertical;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px;
}

/* ---- Numeric inputs: no spinner, right-aligned, ~8 characters wide ---- */
input[type="number"] {
  appearance: textfield;
  -moz-appearance: textfield;
  text-align: right;
  width: calc(8ch + 18px);   /* 8 chars + padding(16) + border(2), box-sizing: border-box */
}
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
/* Station name input: ~20 characters wide */
.field input.fld-w20 { width: calc(20ch + 18px); max-width: 100%; }
/* Maximalstrom eines Ladepunkts: fünf Zeichen breit — mehr als „10000" kann da
   nicht stehen. ``flex: 0 0 auto`` ist nötig, weil das Feld in einer Flex-Zeile
   sitzt und sonst wieder auf die volle Breite gezogen würde. */
input.fld-w5 { width: calc(5ch + 18px); flex: 0 0 auto; }

/* Bearbeitbarer Konfigurationsbericht: Das Aussehen der Felder kommt von
   ``.input`` — hier steht nur, was in einer Tabellenzelle anders ist. Ohne die
   Breitenbremse zieht ein Textfeld die Wertspalte über die halbe Tabelle. */
.cfg-in { max-width: 22ch; }
/* Der Bericht kann 90 Zeilen haben: Die Tabelle scrollt im Modal, Kopf und
   Fuss bleiben stehen. Ohne das waechst der Dialog ueber den Bildschirm und
   der Senden-Knopf liegt ausserhalb. */
.cfg-table-wrap { max-height: 52vh; overflow: auto; }
.cfg-col-pick { width: 2.5em; }
.cfg-werte { max-height: 22vh; overflow: auto; }
.cfg-wert { display: inline-flex; align-items: center; gap: 6px; }
.cfg-status:not(:empty) { margin-right: 6px; }

/* ---- Two-column modal layout (station dialog) ---- */
.modal--wide { width: min(880px, 96vw); }
/* Die Bussuche braucht keine Formularbreite: eine Zeile Bedienung, ein Balken
   und eine kurze Trefferliste. */
.modal--narrow { width: min(560px, 94vw); }
.modal-cols { display: flex; gap: 28px; align-items: flex-start; flex-wrap: wrap; }
.modal-col { flex: 1 1 300px; min-width: 0; }

/* Tabbed modals (station/group): all panes share one grid cell, so the modal
   keeps the height of the TALLEST tab — switching tabs no longer resizes it.
   Inactive panes stay in layout (visibility:hidden), not removed (display:none). */
.modal-tabs { display: grid; align-items: start; }
.modal-tabs > .modal-tabpane { grid-area: 1 / 1; min-width: 0; }
.modal-tabs > .modal-tabpane[hidden] {
  /* Override the global `[hidden] { display: none !important }` (line ~50) so the
     pane keeps its grid space — hidden via visibility, not removed. The more
     specific selector + !important wins. */
  display: block !important;
  visibility: hidden;
  pointer-events: none;
}

/* ---- Hilfe → Dokumentation (in-app manual, M12) ---- */
.help { display: flex; gap: 24px; align-items: flex-start; }
.help__nav {
  flex: 0 0 240px;
  position: sticky;
  top: 0;
  max-height: calc(100vh - var(--statusbar-h) - 40px);
  overflow: auto;
}
/* Flex statt Block, damit der Spinner beim Erzeugen neben dem Text steht
   statt darueber. Mit nur einem Textknoten sieht es aus wie vorher. */
.help__pdf {
  display: flex; align-items: center; justify-content: center;
  gap: var(--space-8); margin-bottom: 10px;
}
/* Waehrend des Erzeugens ist der Knopf tot — ohne das bleibt er anklickbar
   und sieht nur beschaeftigt aus. */
.help__pdf[aria-disabled="true"] { pointer-events: none; opacity: 0.75; }
.help__search {
  width: 100%;
  box-sizing: border-box;
  margin-bottom: 14px;
  padding: 7px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-elevated);
  color: var(--fg);
}
.help__section { margin-bottom: 16px; }
.help__section[hidden] { display: none; }
.help__section-title {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--fg-muted);
  margin: 0 0 6px;
}
.help__list { list-style: none; margin: 0; padding: 0; }
.help__list li[hidden] { display: none; }
.help__list a {
  display: block;
  padding: 5px 10px;
  border-radius: var(--radius);
  border-left: 3px solid transparent;
  color: var(--fg);
  text-decoration: none;
  font-size: 14px;
}
.help__list a:hover { background: color-mix(in srgb, var(--fg) 5%, transparent); }
.help__link--active {
  border-left-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  font-weight: 600;
}

/* Full-text search results (help) */
.help__results-count { margin: 0 0 12px; }
.help__results-empty { margin: 4px 0; }
.help__result { padding: 10px 0; border-bottom: 1px solid var(--border); }
.help__result:last-child { border-bottom: 0; }
.help__result-title { font-weight: 600; margin-right: 8px; }
.help__result-section { font-size: 12px; color: color-mix(in srgb, var(--fg) 55%, transparent); }
.help__snippet { margin: 6px 0 0; font-size: 14px; line-height: 1.5; }
mark {
  background: color-mix(in srgb, var(--accent) 30%, transparent);
  color: inherit;
  padding: 0 .1em;
  border-radius: 2px;
}

/* Tools → Systeminfo */
.sysinfo-h { margin: 18px 0 6px; }
.sysinfo-h:first-child { margin-top: 2px; }
.sysinfo-table { max-width: 760px; }
.sysinfo-table th[scope="row"] {
  width: 38%;
  text-align: left;
  font-weight: 500;
  white-space: nowrap;
  color: color-mix(in srgb, var(--fg) 70%, transparent);
}
.sysinfo-serial {
  font-size: 15px;
  margin: 2px 0 14px;
  padding: 8px 12px;
  border-radius: 6px;
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
}
.sysinfo-serial strong { font-variant-numeric: tabular-nums; letter-spacing: .04em; }

/* Lizenzspalte: dezenter, themekonformer Link auf den Lizenztext statt rohem Browser-Link */
.lic-link {
  color: var(--accent);
  text-decoration: none;
  font-weight: 500;
  white-space: nowrap;
  border-bottom: 1px dotted color-mix(in srgb, var(--accent) 45%, transparent);
  transition: color .12s, border-color .12s;
}
.lic-link:hover,
.lic-link:focus-visible {
  color: color-mix(in srgb, var(--accent) 80%, var(--fg));
  border-bottom-color: currentColor;
}
.lic-link__icon {
  font-size: .8em;
  margin-left: 3px;
  opacity: .65;
  vertical-align: baseline;
}
.lic-plain { color: color-mix(in srgb, var(--fg) 60%, transparent); }

/* Pause / resume control on dashboard nodes (reuses the tree gear-button box). */
.pause-btn { line-height: 1; }
.pause-btn:hover { color: var(--danger); }
.pause-btn.is-paused { color: #16a34a; font-size: 1.05em; }   /* ▶ = resume, strong green */
.pause-btn.is-paused:hover { color: #22c55e; }
.pause-btn[disabled] { opacity: .5; cursor: default; }

/* Von oben geerbte Pause: gestrichelter Rahmen am Knopf — er wirkt hier nur
   für diesen Knoten, die Ebene darüber bleibt angehalten. */
.pause-btn.is-inherited { outline: 1px dashed var(--danger); outline-offset: 1px; }

/* Der ⏸-Knopf ist Beiwerk und zeigt sich nur, wo die Maus steht — er stand
   sonst neben jedem Namen und wurde mit dem Zahnrad verwechselt. Die Aktion
   selbst liegt im Kontextmenü (rechte Maustaste). Zwei Ausnahmen: ein
   PAUSIERTER Knoten zeigt seinen ▶ immer (sonst fände man den Weg zurück
   nicht), und per Tastatur bleibt er erreichbar. Auf Touch-Geräten gibt es
   kein Hover — dort bleibt alles sichtbar. */
@media (hover: hover) {
  .pause-btn { opacity: 0; transition: opacity .12s ease-in-out; }
  .pause-btn.is-paused,
  .pause-btn:focus-visible,
  tr:hover .pause-btn,
  .tile:hover .pause-btn,
  summary:hover .pause-btn,
  .d3-h4:hover .pause-btn,
  .putil__card:hover .pause-btn { opacity: 1; }
}

/* Kontextmenü (rechte Maustaste) auf Knoten des Dashboards. */
.ctxmenu {
  position: fixed;
  z-index: 1200;
  min-width: 210px;
  padding: 4px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 10px 28px rgba(0, 0, 0, .28);
}
.ctxmenu__item {
  display: block;
  width: 100%;
  padding: 6px 10px;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--fg);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.ctxmenu__item:hover:not([disabled]),
.ctxmenu__item:focus-visible { background: color-mix(in srgb, var(--fg) 10%, transparent); }
.ctxmenu__item[disabled] { opacity: .5; cursor: default; }
.ctxmenu__item.is-danger { color: var(--danger); }
.ctxmenu__hint { display: block; font-size: 11px; opacity: .7; }
.ctxmenu__sep { height: 1px; margin: 4px 6px; background: var(--border); }

/* The "paused" badge pulses gently while a node is (effectively) stopped. */
@keyframes pause-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }
.pill--danger.is-pulsing { animation: pause-pulse 1.6s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) { .pill--danger.is-pulsing { animation: none; } }

/* Hilfe → Tests: the regression-test list grouped by commit gate. */
.tests-file { padding: 3px 0; border-top: 1px solid color-mix(in srgb, var(--fg) 8%, transparent); }
.tests-file > summary { cursor: pointer; font-size: 13px; }
.tests-path { font-family: var(--mono, monospace); }
.tests-list { margin: 6px 0 8px 18px; padding: 0; list-style: none; }
.tests-list code { font-size: 12px; color: color-mix(in srgb, var(--fg) 80%, transparent); }
/* One test with its auto-extracted description / parameters / criteria. */
.tests-item {
  padding: 6px 0 7px; border-top: 1px dashed color-mix(in srgb, var(--fg) 8%, transparent);
}
.tests-item:first-child { border-top: 0; }
.tests-item__name code { font-size: 12.5px; color: var(--fg); font-weight: 600; }
.tests-item__doc { margin: 3px 0 0; font-size: 12px; color: var(--fg-muted); }
.tests-item__row { display: flex; gap: 8px; margin-top: 4px; align-items: baseline; }
.tests-item__tag {
  flex: none; font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.03em;
  color: var(--fg-muted); border: 1px solid var(--border); border-radius: 999px;
  padding: 1px 7px; margin-top: 1px;
}
.tests-item__vals { margin: 0; padding: 0; list-style: none; min-width: 0; }
.tests-item__vals li { padding: 1px 0; }
.tests-item__vals code { font-size: 11.5px; }
.tests-item__params code { color: color-mix(in srgb, var(--accent) 85%, var(--fg)); }
.tests-item__checks code { color: color-mix(in srgb, var(--fg) 78%, transparent); }

.help__content {
  flex: 1 1 auto;
  min-width: 0;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 24px 28px;
}
.help__loading { color: var(--fg-muted); }
/* Manual name shown permanently above the PDF download in the help sidebar. */
.help__manual {
  font-weight: 600;
  font-size: 15px;
  margin-bottom: 10px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--border);
}
.help__md h1:first-child { margin-top: 0; }

/* Rendered markdown */
.help__md { line-height: 1.6; }
.help__md h1, .help__md h2, .help__md h3, .help__md h4 {
  line-height: 1.25;
  margin: 1.4em 0 .5em;
}
.help__md h1 { font-size: 1.6em; }
.help__md h2 {
  font-size: 1.3em;
  border-bottom: 1px solid var(--border);
  padding-bottom: .25em;
}
.help__md h3 { font-size: 1.1em; }
.help__md p, .help__md ul, .help__md ol { margin: .6em 0; }
.help__md ul, .help__md ol { padding-left: 1.5em; }
.help__md li { margin: .25em 0; }
/* Screenshot figures: generous spacing, framed image with a soft blurred shadow,
   caption (the image alt text) centred below. */
.help__md .doc-figure { margin: 24px 0; text-align: center; }
.help__md .doc-figure img {
  max-width: 100%; height: auto;
  border: 1px solid var(--border); border-radius: 6px;
  box-shadow: 0 3px 12px rgba(0, 0, 0, .14);
}
.help__md .doc-figure figcaption {
  margin-top: 10px; font-size: .85em; font-style: italic; color: var(--fg-muted);
}
.help__md a { color: var(--accent); }
.help__md a.help__link--dead {
  color: var(--fg-muted);
  text-decoration: none;
  cursor: default;
}
.help__md code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .9em;
  background: color-mix(in srgb, var(--fg) 8%, transparent);
  padding: .1em .35em;
  border-radius: 4px;
}
.help__md pre {
  background: color-mix(in srgb, var(--fg) 6%, transparent);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 12px 14px;
  overflow: auto;
}
.help__md pre code { background: none; padding: 0; font-size: .85em; }
.help__md blockquote {
  margin: .8em 0;
  padding: .2em 1em;
  border-left: 3px solid var(--border);
  color: var(--fg-muted);
}
.help__md table {
  border-collapse: collapse;
  margin: 1em 0;
  font-size: .92em;
  display: block;
  overflow-x: auto;
}
.help__md th, .help__md td {
  border: 1px solid var(--border);
  padding: 6px 10px;
  text-align: left;
}
.help__md th { background: color-mix(in srgb, var(--fg) 5%, transparent); }
.help__md hr { border: none; border-top: 1px solid var(--border); margin: 1.5em 0; }
.help__md img { max-width: 100%; }

@media (max-width: 720px) {
  .help { flex-direction: column; }
  .help__nav { position: static; flex-basis: auto; max-height: none; width: 100%; }
}

/* ---- Konfigurationen: save form (left) + saved-config list (right) ---- */
.cfg-cols { display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap; }
.cfg-col { flex: 1 1 320px; min-width: 0; }
.cfg-col--list { flex: 2 1 420px; }

/* ---- OCPP-log correlation marker: same message id ⇒ same code + colour ---- */
.msg-corr {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px;
  font-weight: 600;
  padding: 1px 6px;
  border-radius: 10px;
  color: #fff;
  white-space: nowrap;
  cursor: default;
}
.corr-0 { background: #1f6feb; }
.corr-1 { background: #2c8a3e; }
.corr-2 { background: #b5359c; }
.corr-3 { background: #c0392b; }
.corr-4 { background: #0d8a8a; }
.corr-5 { background: #7c3aed; }
.corr-6 { background: #b8740f; }
.corr-7 { background: #4a5568; }
/* Hovering a correlation code highlights every row of the same dialog. */
tr.msg-related > td { background: color-mix(in srgb, var(--accent) 16%, transparent); }

/* ---- Struktur: Hierarchie-Tab + Diagramm-Tab ---- */
/* The hierarchy tree shrinks to its content instead of stretching full-width. */
.tree--bounded { width: max-content; max-width: 100%; }
.struct-toolbar { display: flex; gap: 8px; margin: 0 0 10px; }

.diagram { display: flex; flex-direction: column; }
.diagram__toolbar {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin: 0 0 8px;
}
.diagram__toolbar .btn.is-active { border-color: var(--accent); color: var(--accent); }
.diagram__legend { display: inline-flex; gap: 12px; align-items: center; }
.diagram__legitem { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; color: var(--fg-muted); }
.diagram__swatch { width: 12px; height: 4px; border-radius: 2px; display: inline-block; }
.diagram__hint { font-size: 12px; color: var(--fg-muted); margin-left: auto; }

.diagram__canvas {
  position: relative;
  /* Sensible default; mountDiagram() overrides height precisely (top→window
     bottom) via an inline style and keeps it in sync on resize. */
  height: calc(100vh - var(--statusbar-h) - 150px);
  min-height: 360px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  overflow: hidden;
  touch-action: none;
}
.diagram__svg { width: 100%; height: 100%; display: block; cursor: grab; }
.diagram__svg:active { cursor: grabbing; }
.diagram__grid-line { stroke: color-mix(in srgb, var(--fg) 9%, transparent); stroke-width: 1; vector-effect: non-scaling-stroke; }
.diagram__edge { stroke-width: 2; fill: none; vector-effect: non-scaling-stroke; }
.diagram__rubber {
  fill: color-mix(in srgb, var(--accent) 12%, transparent);
  stroke: var(--accent); stroke-width: 1; stroke-dasharray: 4 3;
  vector-effect: non-scaling-stroke;
}
/* Per-entity "phase 1" marker dot, sitting on the uplink (parent) side. */
.diagram__phase1 { stroke: var(--bg-elevated); stroke-width: 1.5; }

/* Node bodies are HTML inside <foreignObject> so they reuse normal layout/CSS. */
.dnode {
  position: relative;
  box-sizing: border-box;
  width: 100%; height: 100%;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-left: 4px solid var(--fg-muted);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 5px 8px;
  font-size: 12px;
  line-height: 1.25;
  cursor: grab;
  user-select: none;
  overflow: hidden;
}
.dnode.is-dragging { cursor: grabbing; box-shadow: 0 4px 12px rgba(0, 0, 0, .25); }
/* Collapse/expand toggle, top-right corner of a node. */
.dnode__collapse {
  position: absolute; top: 2px; right: 2px;
  width: 15px; height: 15px; padding: 0; line-height: 13px;
  font-size: 13px; font-weight: 700; text-align: center;
  border: 1px solid var(--border); border-radius: 3px;
  background: var(--bg-elevated); color: var(--fg-muted); cursor: pointer;
}
.dnode__collapse:hover { color: var(--fg); border-color: var(--fg-muted); }
/* Selected (rubber-band) nodes. */
.dnode.is-selected { outline: 2px solid var(--accent); outline-offset: 1px; }
.dnode--pcc { border-left-color: var(--violet); }
.dnode--uv { border-left-color: var(--accent); }
.dnode--group { border-left-color: var(--ok); }
.dnode--station { border-left-color: var(--warning); }
.dnode--cp { border-left-color: var(--fg-muted); }
.dnode__head { display: flex; flex-direction: column; gap: 1px; }
.dnode__kindrow { display: flex; align-items: center; justify-content: flex-start; gap: 4px; padding-right: 17px; }
.dnode__kind { font-size: 10px; text-transform: uppercase; letter-spacing: .04em; color: var(--fg-muted); }
/* Non-load-managed entities are highlighted; "managed" (default) is unchanged. */
.dnode.is-unmanaged { border-color: var(--warning); box-shadow: inset 0 0 0 1px var(--warning); }
.dnode.is-off { border-color: var(--danger); box-shadow: inset 0 0 0 1px var(--danger);
  background: color-mix(in srgb, var(--danger) 8%, var(--bg-elevated)); }
/* Charging-state background: B (connected) → yellow, C/D (charging) → green. */
.dnode.is-state-b { background: color-mix(in srgb, var(--warning) 18%, var(--bg-elevated)); }
.dnode.is-state-c { background: color-mix(in srgb, var(--ok) 16%, var(--bg-elevated)); }
.dnode__mode { font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em;
  padding: 1px 5px; border-radius: 8px; color: #fff; white-space: nowrap; }
.dnode__mode--unmanaged { background: var(--warning); }
.dnode__mode--off { background: var(--danger); }
.dnode__name {
  font-weight: 600; color: var(--fg);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dnode__live { display: flex; flex-direction: column; gap: 1px; margin-top: 4px; }
.dnode__stat { font-weight: 600; }
.dnode__stat.is-ok { color: var(--ok); }
.dnode__stat.is-off { color: var(--fg-muted); }
.dnode__nums { color: var(--fg-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Soll (one value) / Ist (per phase) lines. */
.dnode__soll, .dnode__ist { color: var(--fg-muted); white-space: nowrap; }
.dnode__ph { font-weight: 700; font-style: normal; }
/* CP charging-state text, colour-coded by IEC state. */
.dnode__state { font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dnode__state.is-a { color: var(--fg-muted); }   /* not connected */
.dnode__state.is-b { color: var(--warning); }     /* connected → yellow */
.dnode__state.is-cd { color: var(--ok); }         /* charging → green */
.dnode__state.is-ef { color: var(--danger); }     /* fault → red */
/* CPs are rendered compact (smaller, denser). */
.dnode--cp { font-size: 11px; padding: 4px 7px; }
.dnode--cp .dnode__name { font-weight: 700; }
.dnode--cp .dnode__live { margin-top: 2px; }

/* === API-Dokumentation (Hilfe → admin/config) === */
.apidoc-group { margin: 30px 0 6px; padding-bottom: 3px; border-bottom: 2px solid var(--border); }
.apidoc-sec__intro { margin: 0 0 8px; max-width: 74ch; }
.apidoc-op { border: 1px solid var(--border); border-radius: var(--radius); padding: 8px 10px; margin-bottom: 8px; }
.apidoc-op__head { display: flex; align-items: center; gap: 8px; font-size: 13px; flex-wrap: wrap; }
.apidoc-method { font: 600 11px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace; padding: 2px 6px; border-radius: 4px; color: #fff; }
.apidoc-method--get { background: #2e7d32; }
.apidoc-method--post { background: #1565c0; }
.apidoc-method--put, .apidoc-method--patch { background: #b8860b; }
.apidoc-method--delete { background: var(--danger); }
.apidoc-table { width: 100%; border-collapse: collapse; margin: 4px 0; font-size: 11px; }
.apidoc-table th, .apidoc-table td { border: 1px solid var(--border); padding: 2px 5px; text-align: left; vertical-align: top; }
.apidoc-table th { background: color-mix(in srgb, var(--fg-muted) 10%, transparent); }
.apidoc-mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.apidoc-json {
  background: color-mix(in srgb, var(--fg-muted) 8%, transparent);
  border-radius: var(--radius); padding: 6px 8px; font-size: 11px;
  overflow-x: auto; margin: 4px 0; white-space: pre-wrap; word-break: break-word;
}

@media print {
  .statusbar, .menu, .no-print { display: none !important; }
  .layout { display: block !important; }
  .apidoc-group { break-after: avoid; }
  .apidoc-op { break-inside: avoid; border-color: #aaa; }
  body { background: #fff !important; }
}
.apidoc-theme { margin: 22px 0 8px; padding-bottom: 4px; border-bottom: 3px solid var(--accent); font-size: 18px; }
.apidoc-search { width: 100%; max-width: 440px; margin-bottom: 12px; }
@media print { .apidoc-theme { break-before: auto; } }
.apidoc-urlbar { word-break: break-all; }
.apidoc-urlbar code { font-size: 12px; }
summary.apidoc-theme { cursor: pointer; list-style-position: inside; }
summary.apidoc-theme::-webkit-details-marker { color: var(--accent); }
.apidoc-theme-block { margin-bottom: 4px; }
.apidoc-theme-block[open] > summary.apidoc-theme { margin-bottom: 16px; }

/* ============================================================
   UI polish: keyboard focus, transitions, toasts
   ============================================================ */

/* Unified keyboard-focus ring. Pointer interaction does NOT trigger
   :focus-visible, so the ring never shows on mouse use. Text inputs match
   :focus-visible on click too and keep their own (more specific) inset ring,
   so they are never double-ringed; we only suppress the browser default on
   elements that get :focus but not :focus-visible (e.g. clicked buttons). */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
:focus:not(:focus-visible) { outline: none; }

/* Subtle hover transitions on the remaining interactive surfaces (buttons carry
   their own transition; tabs already animate). */
.menu a, .table tbody tr, .tree__node > summary {
  transition: background-color .12s ease, color .12s ease;
}

/* ---------- toasts (non-blocking success / info feedback) ---------- */
.toast-host {
  position: fixed;
  top: calc(var(--statusbar-h) + 12px);
  right: 16px;
  z-index: 140;                /* above modals/dialogs (120) */
  display: flex;
  flex-direction: column;
  gap: 8px;
  pointer-events: none;        /* clicks pass through the gaps */
}
.toast {
  pointer-events: auto;
  min-width: 200px;
  max-width: min(360px, 90vw);
  padding: 10px 14px;
  background: var(--bg-elevated);
  color: var(--fg);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius);
  box-shadow: 0 6px 20px rgba(0, 0, 0, .18);
  font-size: 13px;
  animation: toast-in .16s ease;
}
.toast--ok     { border-left-color: var(--ok); }
.toast--error  { border-left-color: var(--danger); }
.toast--warn   { border-left-color: var(--warning); }
.toast.is-leaving { animation: toast-out .16s ease forwards; }
@keyframes toast-in  { from { opacity: 0; transform: translateX(12px); } to { opacity: 1; transform: none; } }
@keyframes toast-out { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateX(12px); } }

/* Respect reduced-motion: drop entrance/exit movement, keep instant fades. */
@media (prefers-reduced-motion: reduce) {
  .modal-overlay, .modal, .toast, .toast.is-leaving { animation: none; }
  .btn:active { transform: none; }
}

/* ---------- themed scrollbars (thin, theme-aware) ---------- */
* { scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--fg-muted) 55%, transparent) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--fg-muted) 50%, transparent);
  border-radius: 8px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
*::-webkit-scrollbar-thumb:hover {
  background: var(--fg-muted);
  border: 2px solid transparent;
  background-clip: padding-box;
}

/* ============================================================
   Responsive: collapse the menu to an icon rail, reflow actions
   ============================================================ */

/* Phone: no room for the right action column — float the Abbrechen/Neu/Speichern
   buttons as a fixed bottom bar. The menu column follows --menu-col (rail or full
   per the data-menu attribute), so a pinned-open menu still gets its full width. */
@media (max-width: 720px) {
  .layout { grid-template-columns: var(--menu-col) 1fr; }
  .actions {
    position: fixed;
    inset: auto 0 0 0;          /* top:auto right:0 bottom:0 left:0 */
    height: auto;
    flex-direction: row;
    justify-content: flex-end;
    border-left: 0;
    border-top: 1px solid var(--border);
    box-shadow: 0 -2px 10px rgba(0, 0, 0, .12);
    z-index: 60;                /* above content, below modals (100) */
  }
  .actions[data-visible="false"] { display: none; }
  /* keep the last bit of content reachable above the floating bar when shown */
  .layout:has(.actions[data-visible="true"]) .view { padding-bottom: 76px; }
}

/* ---------- global top progress bar (route loading) ---------- */
.topbar-progress {
  position: fixed; top: 0; left: 0; right: 0; height: 2px;
  z-index: 200;                 /* above toasts (140) and modals (100) */
  pointer-events: none; overflow: hidden;
}
.topbar-progress.is-active::before {
  content: ""; position: absolute; top: 0; bottom: 0; width: 40%;
  background: var(--accent);
  animation: topbar-progress 1s ease-in-out infinite;
}
@keyframes topbar-progress { from { left: -40%; } to { left: 100%; } }
@media (prefers-reduced-motion: reduce) {
  .topbar-progress.is-active::before { animation: none; width: 100%; left: 0; opacity: .55; }
}

/* ---------- empty states ---------- */
.empty-state {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px; text-align: center; padding: 36px 16px; color: var(--fg-muted);
}
.empty-state__icon { width: 40px; height: 40px; opacity: .5; margin-bottom: 4px; }
.empty-state__title { font-size: var(--fs-14); color: var(--fg); }
.empty-state__hint { font-size: var(--fs-12); max-width: 38ch; }
.empty-state__action { margin-top: 8px; }
.table .empty-row { cursor: default; }
.table .empty-row:hover { background: transparent; }
.table .empty-row td { border-bottom: 0; }

/* clickable common name in the issued-certificates list (opens detail modal) */
.cert-cn { color: var(--accent); cursor: pointer; text-decoration: underline; }
.cert-cn:hover { filter: brightness(1.12); }

/* issued-certificates dependency tree (root CA → sub-CAs → leaf certs) */
.cert-tree { list-style: none; margin: 8px 0 0; padding-left: 0; }
.cert-tree ul { list-style: none; margin: 0; margin-left: 9px; padding-left: 18px; border-left: 1px solid var(--border); }
.cert-tree li { padding: 1px 0; }
.cert-tree__node { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 4px 0; }
.cert-tree__actions { display: inline-flex; gap: 6px; align-items: center; margin-left: auto; }
.cert-badge { font-size: 11px; line-height: 1.4; padding: 1px 7px; border-radius: 999px; white-space: nowrap;
  background: color-mix(in srgb, var(--fg-muted) 18%, transparent); color: var(--fg-muted); }
.cert-badge--subca  { background: color-mix(in srgb, var(--accent) 20%, transparent);  color: var(--accent); }
.cert-badge--root   { background: color-mix(in srgb, var(--warning) 24%, transparent); color: var(--fg); }
.cert-badge--server { background: color-mix(in srgb, var(--ok) 20%, transparent);      color: var(--ok); }

/* dashboard: filter bar + EV-state text */
.dash-filter { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin: 8px 0; }
.dash-filter__scope { display: inline-flex; align-items: center; gap: 6px; }
.dash-filter__scope select { min-width: 180px; }
.dash-filter__states { display: flex; flex-wrap: wrap; gap: 6px; }
.chip { font-size: 12px; line-height: 1.5; padding: 2px 10px; border-radius: 999px;
  border: 1px solid var(--border); background: transparent; color: var(--fg-muted); cursor: pointer; }
.chip:hover { border-color: var(--accent); }
.chip.is-active { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }
.chip--clear { border-style: dashed; }
.state-text { font-size: 12px; color: var(--fg-muted); white-space: nowrap; }
.ta-center { text-align: center; }
.p-16 { padding: 16px; }
.dash-scope-pop { max-height: 60vh; overflow-y: auto; min-width: 248px; }
/* Scope filter as a tidy tree: type icons + guide lines instead of bare indents. */
.scope-tree { display: flex; flex-direction: column; gap: 1px; }
.scope-children {
  margin-left: 9px; padding-left: 8px;
  border-left: 1px solid var(--border);
  display: flex; flex-direction: column; gap: 1px;
}
.scope-row {
  display: flex; align-items: center; gap: 7px;
  padding: 3px 6px; border-radius: var(--radius);
  white-space: nowrap; cursor: pointer;
}
.scope-row:hover { background: color-mix(in srgb, var(--fg) 6%, transparent); }
.scope-row input[type="checkbox"] { flex: none; margin: 0; }
.scope-row__icon { width: 15px; height: 15px; flex: none; color: var(--fg-muted); }
.scope-row__label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.scope-row--uv > .scope-row__label { font-weight: 600; }
.scope-row--g > .scope-row__label { font-weight: 500; }
.scope-row.is-off > .scope-row__label { color: var(--fg-muted); }
.scope-row.is-off > .scope-row__icon { opacity: 0.45; }

/* ===== Dashboard 3 (frei konfigurierbares Kachel-Dashboard) ================= */
.d3-toolbar { flex-wrap: wrap; gap: 8px; align-items: center; }
.d3-select { padding: 5px 8px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg); color: var(--fg); }
.d3-empty { padding: 32px 8px; display: flex; gap: 12px; align-items: center; }

.d3-sec { margin: 0 0 18px; }
.d3-sec__head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.d3-sec__head h3 { margin: 0; font-size: 14px; text-transform: uppercase; letter-spacing: .03em; color: var(--fg-muted); }
.d3-sec__title { font-size: 14px; font-weight: 600; padding: 4px 8px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg); color: var(--fg); }

/* Eine Zeile, eine Höhe: Die Zeilenhöhe richtet sich nach der höchsten Kachel
   der Zeile, alle anderen ziehen mit (align-items: stretch, der Grid-Default).
   Feste Mindesthöhen gibt es nicht — eine Zeile aus lauter kurzen Kacheln
   bleibt flach. Kacheln mit .d3-tile--fill geben den gewonnenen Platz nach
   innen weiter (Diagramm wächst, Ereignisliste scrollt länger); die übrigen
   lassen ihn unter dem Inhalt stehen, damit die Zeile als Block ruhig wirkt. */
.d3-grid { display: grid; grid-template-columns: repeat(24, 1fr); gap: 12px;
           min-height: 8px; align-items: stretch; }
.d3-tile { border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg); padding: 12px 14px; position: relative; overflow: hidden; box-shadow: var(--shadow-tile); transition: box-shadow .12s, opacity .12s; }
.d3-tile--dragging { opacity: .4; outline: 2px dashed var(--accent); outline-offset: -2px; box-shadow: 0 8px 24px rgba(0, 0, 0, .25); }
.d3-tile--xs { grid-column: span 3; }   /* half of S */
.d3-tile--s { grid-column: span 6; }
.d3-tile--m { grid-column: span 12; }
.d3-tile--l { grid-column: span 24; }
.d3-tile--xs { padding: 10px; }
@media (max-width: 900px) { .d3-tile--xs { grid-column: span 6; } .d3-tile--s { grid-column: span 12; } .d3-tile--m, .d3-tile--l { grid-column: span 24; } }
@media (max-width: 560px) { .d3-tile { grid-column: span 24 !important; } }
.d3-tile[draggable="true"] { cursor: grab; }
.d3-tile__chrome { position: absolute; top: 4px; right: 4px; display: flex; gap: 4px; align-items: center; z-index: 2; }
.d3-tile__drag { color: var(--fg-muted); cursor: grab; padding: 0 2px; }
.d3-tile__btn { border: 1px solid var(--border); background: var(--bg); color: var(--fg-muted); border-radius: 4px; font-size: 11px; line-height: 1.4; padding: 0 5px; cursor: pointer; }
.d3-tile__btn:hover { color: var(--fg); border-color: var(--accent); }
.d3-tile__size { border: 1px solid var(--border); background: var(--bg); color: var(--fg-muted); border-radius: 4px; font-size: 11px; line-height: 1.4; padding: 0 2px; cursor: pointer; }
.d3-tile__size:hover { color: var(--fg); border-color: var(--accent); }
.d3-tile__body { min-height: 36px; }
.d3-h4 { margin: 0 0 8px; font-size: 12px; text-transform: uppercase; letter-spacing: .03em; color: var(--fg-muted); }

.d3-big { text-align: left; }
.d3-big__val { font-size: 30px; font-weight: 700; line-height: 1.1; font-variant-numeric: tabular-nums; }
.d3-big__lbl { font-size: 12px; color: var(--fg-muted); margin-top: 2px; }
.d3-big--accent .d3-big__val { color: var(--accent); }
.d3-big--ok .d3-big__val { color: var(--ok); }
.d3-big--warn .d3-big__val { color: var(--warning); }
.d3-big--danger .d3-big__val { color: var(--danger); }
.d3-mc { display: flex; gap: 18px; }

.d3-node__head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 6px; }
.d3-node__kw { font-weight: 600; font-variant-numeric: tabular-nums; }

.d3-row { display: grid; grid-template-columns: 1fr auto auto; gap: 10px; align-items: center; padding: 4px 2px; border-bottom: 1px solid color-mix(in srgb, var(--fg) 6%, transparent); }
.d3-row:last-child { border-bottom: 0; }
/* .d3-row__name is a vertical stack (primary line + optional sub, see line ~1772).
   The primary line (.d3-row__line) truncates on one line; the sub wraps. */
.d3-row__line { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.d3-row__kw { font-variant-numeric: tabular-nums; white-space: nowrap; }

.d3-cp { margin-bottom: 4px; }
.d3-note { white-space: pre-wrap; }
.d3-note__edit { width: 100%; min-height: 70px; resize: vertical; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg); color: var(--fg); padding: 6px 8px; font: inherit; }
.d3-stale { padding: 8px 0; font-style: italic; }

.d3-chart__head { display: flex; justify-content: space-between; align-items: center; gap: 8px; flex-wrap: wrap; }
.d3-chart__wins { display: flex; gap: 4px; }
.d3-chart__max { font-size: 11px; text-align: right; }
/* power chart with axis grid + HTML-overlay labels (crisp despite stretched svg) */
/* ``--pan-x`` ist die Verschiebung waehrend einer Ziehbewegung; das Zoom-Modul
   setzt sie am Element, hier steht die Ruhelage. */
.d3-chart__plot { position: relative; height: 130px; margin: 8px 10px 20px 38px;
  --pan-x: 0px; }
.d3-chart__svg { width: 100%; height: 100%; display: block; overflow: visible; }
/* Sichtfenster: Das SVG deckt den geholten Vorrat ab und ist deshalb breiter
   als das Diagramm — hier wird es beschnitten. Die Beschriftung steht
   AUSSERHALB (links die Werte, unten die Uhrzeiten) und bleibt darum ausserhalb
   des Fensters, sonst waere sie mit abgeschnitten. */
.cpc__view { position: absolute; inset: 0; overflow: hidden; }
.cpc__view > .d3-chart__svg { position: absolute; top: 0; height: 100%; }
.d3-chart__grid { stroke: var(--border); stroke-width: 1; vector-effect: non-scaling-stroke; opacity: .55; }
/* Power-curve fill/line (shared by the Dashboard-3 chart and the spark tiles).
   Without these rules SVG paths default to a solid black fill. */
.d2-spark__area { fill: color-mix(in srgb, var(--accent) 20%, transparent); }
.d2-spark__line { fill: none; stroke: var(--accent); stroke-width: 2; vector-effect: non-scaling-stroke; }
.d3-chart__ylab { position: absolute; right: 100%; margin-right: 6px; transform: translateY(-50%); font-size: 10px; color: var(--fg-muted); font-variant-numeric: tabular-nums; white-space: nowrap; pointer-events: none; }
/* ``--pan-x`` setzt das Zoom-Modul waehrend einer Geste: Die Uhrzeiten der
   Zeitachse wandern mit der Kurve, statt stehen zu bleiben — sonst sieht das
   Diagramm waehrend des Ziehens kaputt aus statt bewegt. Ohne Geste ist die
   Eigenschaft nicht gesetzt und der Rueckfallwert 0px gilt. */
.d3-chart__xlab { position: absolute; top: 100%; margin-top: 4px;
  transform: translateX(calc(-50% + var(--pan-x, 0px)));
  font-size: 10px; color: var(--fg-muted); font-variant-numeric: tabular-nums;
  white-space: nowrap; pointer-events: none; }
.d3-chart__xlab--start { transform: translateX(var(--pan-x, 0px)); }
.d3-chart__xlab--end { transform: translateX(var(--pan-x, 0px)); }

/* Preisvorschau: Treppenkurven mehrerer Tarife in einem Diagramm. Die vier
   Farben kommen aus der bestehenden Palette, damit beide Themes ohne eigene
   Variablen stimmen. Alle Linien sind durchgezogen — gestrichelte Kurven
   zerfielen bei einer Treppe optisch in Einzelstriche. */
.pf__plot { height: 150px; }
.pf__plot--l { height: 210px; }
.pf__midnight { opacity: 1; stroke-width: 1.5; }
.pf__line { fill: none; stroke-width: 2; vector-effect: non-scaling-stroke; }
.pf__line--c0 { stroke: var(--accent); }
.pf__line--c1 { stroke: var(--violet); }
.pf__line--c2 { stroke: var(--ok); }
.pf__line--c3 { stroke: var(--warning); }
.pf__area--c0 { fill: color-mix(in srgb, var(--accent) 18%, transparent); }
.pf__area--c1 { fill: color-mix(in srgb, var(--violet) 18%, transparent); }
.pf__area--c2 { fill: color-mix(in srgb, var(--ok) 18%, transparent); }
.pf__area--c3 { fill: color-mix(in srgb, var(--warning) 18%, transparent); }
.pf__spark { width: 100%; height: 42px; display: block; overflow: visible; }
/* Prognoseband: die Zeiträume, in denen die Preisführung drosseln wird. Warm
   und blass — es liegt hinter der Kurve und soll sie nicht überstimmen. Ein
   noch vorläufiges Fenster wird schwächer gezeichnet. */
.pf__band { fill: color-mix(in srgb, var(--warning) 22%, transparent); }
.pf__band--soft { fill: color-mix(in srgb, var(--warning) 10%, transparent); }
/* Zurückliegender Zeitraum: neutral grau über Raster und Prognoseband, aber
   unter den Kurven — der Preisverlauf von vorhin ist eine Messung und bleibt
   lesbar; nur die Aussage „hier wird gedrosselt" gilt rückwärts nicht mehr.
   Ohne dieses Feld sieht Vergangenes aus wie „günstig", weil günstige Stunden
   gar nicht eingefärbt werden. */
.pf__past { fill: color-mix(in srgb, var(--fg-muted) 16%, transparent); }
.pf__past-lab { position: absolute; left: 0; top: 2px; text-align: center;
  font-size: 10px; color: var(--fg-muted); pointer-events: none;
  letter-spacing: .02em; }
.pf__picks--band { margin-top: 4px; align-items: center; }
.table--compact th, .table--compact td { padding: 2px 6px; font-size: var(--fs-12); }
.nowrap { white-space: nowrap; }
.pf__now-line { stroke: var(--fg-muted); stroke-width: 1; stroke-dasharray: 3 3; vector-effect: non-scaling-stroke; }
.pf__now-lab { position: absolute; top: -14px; transform: translateX(-50%); font-size: 10px; color: var(--fg-muted); white-space: nowrap; pointer-events: none; }
.pf__picks { display: flex; flex-wrap: wrap; gap: 4px; }
.pf__chip--c0.is-active { border-color: var(--accent); }
.pf__chip--c1.is-active { border-color: var(--violet); }
.pf__chip--c2.is-active { border-color: var(--ok); }
.pf__chip--c3.is-active { border-color: var(--warning); }
.pf__legend { margin-top: 6px; display: flex; flex-direction: column; gap: 3px; }
.pf__row { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; font-size: var(--fs-12); }
.pf__dot { width: 10px; height: 3px; border-radius: 2px; flex: 0 0 auto; }
.pf__dot--c0 { background: var(--accent); }
.pf__dot--c1 { background: var(--violet); }
.pf__dot--c2 { background: var(--ok); }
.pf__dot--c3 { background: var(--warning); }
.pf__name { color: var(--fg-muted); }
.pf__zone { font-size: var(--fs-11); padding: 0 5px; border: 1px solid var(--border); border-radius: 999px; color: var(--fg-muted); }
.pf__now { font-weight: 600; font-variant-numeric: tabular-nums; }
.pf__stat { color: var(--fg-muted); font-variant-numeric: tabular-nums; }
.pf__stat::before { content: "·"; margin-right: 6px; }
.pf__horizon { margin-top: 4px; font-size: var(--fs-11); }

/* Blätterleiste der Lade-Historie: oben und unten dieselbe Leiste. Der
   Seitenwähler rechts, damit die Knöpfe links an fester Stelle stehen. */
.row-flex__spacer { flex: 1 1 auto; }

/* Ableser am Diagramm (Klick): senkrechte Marke + Kasten mit Zeit und Werten.
   Bewusst auf Klick statt Hover — ein Tooltip beim Vorbeiziehen der Maus
   meldet sich ungefragt und ist auf einem Tablet gar nicht erreichbar. */
.d3-chart__plot[data-pick] { cursor: crosshair; }
.chart-pick__line {
  position: absolute; top: 0; bottom: 0; width: 0;
  border-left: 1px solid var(--accent); pointer-events: none;
}
.chart-pick {
  position: absolute; top: 2px; transform: translateX(6px);
  display: flex; flex-direction: column; gap: 1px;
  padding: 4px 7px; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg); box-shadow: var(--shadow-tile);
  font-size: var(--fs-11); line-height: 1.4; white-space: nowrap;
  pointer-events: none; z-index: 2;
}
.chart-pick--left { transform: translateX(calc(-100% - 6px)); }
.chart-pick__v { font-variant-numeric: tabular-nums; }

.d3-palette { margin-top: 10px; padding: 10px; border: 1px dashed var(--border); border-radius: var(--radius); }
.d3-palette__row { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.d3-palette__cat { margin-bottom: 10px; }
.d3-palette__catlbl { font-size: 10px; text-transform: uppercase; letter-spacing: .03em; color: var(--fg-muted); margin-bottom: 4px; }
.d3-addtile { margin-top: 10px; }
.d3-addsec { margin-top: 4px; }

/* tile titles/labels wrap instead of overflowing the tile */
.d3-h4, .d3-big__lbl { overflow-wrap: anywhere; }
.d3-node__head { flex-wrap: wrap; }
.d3-node__head strong { overflow-wrap: anywhere; min-width: 0; }
/* Knoten-Kacheln von „Meine Ansicht“: Phasenbalken und Zustands-Plaketten.
   Das Markup gab es, die Gestaltung fehlte — dadurch klebten Beschriftung,
   Balken und Wert aneinander („L116 A"), der Balken war unsichtbar und die
   Plaketten sahen aus wie ein durchlaufender Satz („Proxy aktiv1.6 · 1…"). */
.d2-pbar {
  display: grid;
  grid-template-columns: 2em 1fr 3.5em;
  align-items: center;
  gap: 8px;
  margin: 3px 0;
  font-size: var(--fs-12);
}
.d2-pbar__lbl { color: var(--fg-muted); }
.d2-pbar__track {
  height: 6px; border-radius: 3px;
  background: color-mix(in srgb, var(--fg) 10%, transparent);
  overflow: hidden;
}
.d2-pbar__fill { display: block; height: 100%; background: var(--accent); }
.d2-pbar__fill.is-over { background: var(--danger); }
.d2-pbar__val { text-align: right; font-variant-numeric: tabular-nums; }

.d2-health { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.d2-badge {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 1px 8px; border-radius: 999px;
  border: 1px solid var(--border);
  font-size: var(--fs-12); white-space: nowrap;
}
.d2-badge.is-ok {
  background: color-mix(in srgb, var(--ok) 18%, transparent);
  color: color-mix(in srgb, var(--ok) 85%, var(--fg)); border-color: transparent;
}
.d2-badge.is-off {
  background: color-mix(in srgb, var(--danger) 18%, transparent);
  color: color-mix(in srgb, var(--danger) 85%, var(--fg)); border-color: transparent;
}
.d2-badge.is-warn {
  background: color-mix(in srgb, var(--warning) 20%, transparent);
  color: color-mix(in srgb, var(--warning) 85%, var(--fg)); border-color: transparent;
}
.d2-badge.is-neutral { color: var(--fg-muted); }
.d2-spark__empty { font-size: var(--fs-12); padding: 6px 0; }

/* Ereignis-Kachel: bis zu 50 Einträge, neueste oben. Die Liste FÜLLT die
   Kachel und scrollt in sich — vorher stand sie auf fester Höhe und ließ in
   einer hohen Kachelzeile darunter Leerraum. */
.d3-tile--fill { display: flex; flex-direction: column; align-self: stretch; }
.d3-tile--fill .d3-tile__body { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
/* Ereignisliste: zehn Zeilen sind sichtbar, alles Weitere scrollt. Zehn, weil
   die Kachel damit auf einen Blick lesbar bleibt und trotzdem nicht die halbe
   Seite belegt. */
.d3-events { --d3-event-rows: 10;
             flex: 1 1 auto; min-height: 0;
             max-height: calc(var(--d3-event-rows) * (1.45 * var(--fs-12) + 4px));
             overflow-y: auto; }
/* Nur die halbbreite XS-Kachel zeigt weniger — dort passt schlicht nicht mehr. */
.d3-tile--xs .d3-events { --d3-event-rows: 6; }
/* Diagramm: feste Grundhöhe, wächst aber mit, wenn die Kachelzeile höher ist. */
.d3-tile--fill .d3-chart__plot { flex: 1 1 130px; height: auto; min-height: 130px; }

/* Drosselungs-Vorschau: Die Liste bekommt die Resthöhe der Kachel und
   schneidet ab — welche Zeilen stehen bleiben, entscheidet fitOutlook (ganze
   Zeilen, nie eine halbe). Kein Scrollbalken: Die Kachel beantwortet „was
   kommt als Nächstes", das liest man im Vorbeigehen. */
.d3-tile--fill .d3-ol { flex: 1 1 auto; min-height: 0; overflow: hidden; }
/* Grundhöhe je Kachelgröße: Ohne sie bestimmte die Liste die Zeilenhöhe und
   zöge die ganze Reihe in die Länge. Mit ihr ist die Kachel von sich aus so
   hoch — und wächst, wenn eine höhere Kachel daneben die Zeile vorgibt. */
.d3-tile--xs .d3-ol { flex-basis: 130px; }
.d3-tile--s .d3-ol { flex-basis: 138px; }
.d3-tile--m .d3-ol { flex-basis: 202px; }
.d3-tile--l .d3-ol { flex-basis: 290px; }
.d3-ol__more { flex: 0 0 auto; margin-top: 2px; }
/* XS: nur das Zeitfenster, die Farbe sagt den Rest (gelb = gedrosselt bzw.
   pausiert, grün = normal). Für Dauer und Zustandstext ist dort kein Platz. */
.d3-ol__win {
  font-size: var(--fs-12);
  line-height: 1.55;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.d3-ol__win.is-warn { color: var(--warning); }
.d3-ol__win.is-ok { color: var(--ok); }
/* Ausgehaltene Preisspitze: geladen, aber teuer — weder Warnfarbe (es
   wird ja geladen) noch die ruhige Farbe der günstigen Zeiten. */
.d3-ol__win.is-info { color: var(--accent); }

/* Ereigniszeile: Uhrzeit als feste erste Spalte, der Text daneben. Bricht er
   um, rückt die Folgezeile ein (grid-Spalte), damit die Uhrzeit als
   Zeilenanfang erkennbar bleibt. Die Dringlichkeit steckt in der Textfarbe. */
.d2-event {
  display: grid;
  grid-template-columns: 3.2em 1fr;
  gap: 6px;
  padding: 2px 0;
  font-size: var(--fs-12);
  line-height: 1.45;
}
.d2-event__t { color: var(--fg-muted); font-variant-numeric: tabular-nums; }
.d2-event__s { overflow-wrap: anywhere; }
.d2-event--error .d2-event__s { color: var(--danger); font-weight: 500; }
.d2-event--warn .d2-event__s,
.d2-event--warning .d2-event__s { color: var(--warning); }
/* per-tile representation toggle (Zahl/Liste), edit mode */
.d3-tile__disp { display: flex; align-items: center; flex-wrap: wrap; gap: 4px; margin-top: 8px; padding-top: 6px; border-top: 1px dashed var(--border); }
.d3-tile__disp-lbl { font-size: 10px; text-transform: uppercase; letter-spacing: .03em; color: var(--fg-muted); margin-right: 2px; }

/* Dashboard 3 — size-progressive content */
.d3-sub { display: flex; flex-wrap: wrap; gap: 3px 12px; margin-top: 6px; font-size: 12px; color: var(--fg-muted); }
.d3-sub > span { white-space: nowrap; }
.d3-toplist { margin-top: 8px; }
.d3-row__name { display: flex; flex-direction: column; min-width: 0; }
.d3-row__sub { font-size: 11px; color: var(--fg-muted); }
.d3-node + .muted { margin-top: 4px; }

/* Dashboard 3 — XS tile (single value, half of S) */
.d3-big--xs { display: flex; flex-direction: column; justify-content: center; min-height: 40px; }
.d3-big--xs .d3-big__val { font-size: 18px; line-height: 1.15; }
.d3-tile--xs .d3-mc { gap: 8px; }
.d3-tile--xs .d3-big--xs .d3-big__val { font-size: 16px; }
.d3-big__lbl--xs { font-size: 10px; line-height: 1.2; margin-top: 1px; color: var(--fg-muted); overflow-wrap: anywhere; }

/* Config snapshots — import section */
.cfg-import { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border); }

/* Cert tools — PKI transfer (per-artifact upload) */
.cert-imp { border: 1px solid var(--border); border-radius: var(--radius); padding: 10px 12px; }
.cert-imp__pem { width: 100%; font-family: var(--mono, monospace); font-size: 12px; margin: 4px 0; resize: vertical; }

/* Warnhinweis in Dialogen (z. B. „In teuren Stunden pausieren"): derselbe Ton
   wie die Warn-Marken, damit riskante Optionen sichtbar riskant aussehen. */
.warn-text { color: var(--warning); }

/* Lizenz-Angaben (Systeminfo): Funktionen und Kontingente untereinander statt
   in einer langen Kommazeile — in einer Tabellenzelle braucht die Liste weder
   Außenabstand noch Standard-Einzug. */
.lic-list { margin: 0; padding-left: 1.1em; }
.lic-list li { margin: 0; }

/* Werkzeugknopf, der nur ein Symbol trägt: quadratisch, damit vier davon
   nebeneinander eine ruhige Reihe ergeben statt vier verschieden breiter
   Kästchen. Die Beschriftung steckt im title/aria-label. */
.btn--icon { padding: 6px; width: 32px; height: 32px; }
.btn__icon { width: 16px; height: 16px; flex: none; }

/* Ladehistorie: Blättern, Suche und Zeilenzahl in EINER Zeile. Die Suchleiste
   gehört der Tabellenkomponente und wird in den Platzhalter gerendert; dort
   darf sie keinen eigenen Abstand mehr aufspannen. Schmaler als über einer
   Tabelle, weil sie hier neben den Knöpfen steht und nicht allein. */

/* Symbol in einer Marke: klein, auf der Grundlinie des Textes, mit etwas Luft
   dahinter. ``flex: none``, damit es in einer engen Spalte nicht gequetscht
   wird — ein zusammengedrücktes Symbol ist schlechter als keins. */
.pill__icon {
  width: 12px; height: 12px; flex: none;
  vertical-align: -2px; margin-right: 4px;
}

/* Warum liegt der Ist-Strom, wo er liegt? Ein Symbol neben dem Messwert:
   Decke = die Anlage begrenzt, Auto = das Fahrzeug ruft weniger ab. Gedämpft
   und ohne Rahmen — es ist eine Fußnote zum Wert daneben, kein zweites
   Abzeichen, und in einer Baumzeile stehen schon genug davon. Die Aussage
   selbst steht im ``title`` bzw. ``aria-label``. */
.limit-mark { display: inline-flex; align-items: center; color: var(--fg-muted); }
.limit-mark--lm { color: var(--warning); }
.limit-mark__icon { width: 13px; height: 13px; flex: none; vertical-align: -2px; }

/* „Keine Verbindung" — ganz oben, über allem, unübersehbar. Kein Toast: Der
   verschwindet nach vier Sekunden, der Zustand aber nicht. Die Leiste blockiert
   nichts (man darf weiterlesen), sie sagt nur, dass das Gelesene stehengeblieben
   ist. Der Punkt pulst, damit man erkennt, dass etwas läuft — nämlich der
   Neuversuch. */
.conn-lost { position: fixed; top: 0; left: 0; right: 0; z-index: 3000;
             display: none; align-items: center; justify-content: center;
             gap: 10px; padding: 8px 14px;
             background: var(--danger); color: #fff;
             box-shadow: 0 2px 8px rgba(0, 0, 0, .3); }
.conn-lost.is-visible { display: flex; }
/* Sperrfläche: Über ALLEM (Dialoge liegen bei 100, Toasts bei 140, die
   Kontextmenüs bei 200) und unter der Meldung. Sie ist bewusst sichtbar —
   eine unsichtbare Sperre wirkt wie eine kaputte Oberfläche, eine gedämpfte
   Seite wie eine angehaltene. */
.conn-scrim { position: fixed; inset: 0; z-index: 2900; display: none;
              background: color-mix(in srgb, var(--bg) 55%, transparent);
              backdrop-filter: saturate(.6); cursor: not-allowed; }
.conn-scrim.is-visible { display: block; }
.conn-lost__dot { width: 10px; height: 10px; border-radius: 50%;
                  background: #fff; animation: pause-pulse 1.4s ease-in-out infinite; }
.conn-lost__text { font-weight: 600; }
.conn-lost .btn { background: transparent; color: #fff; border-color: #fff; }
@media (prefers-reduced-motion: reduce) { .conn-lost__dot { animation: none; } }
/* Die Leiste liegt fest oben und verdeckte sonst die Statusleiste. Statt sie
   einzeln zu verschieben, rückt die ganze Seite nach — das gilt dann auch für
   das Ladekonto und den Anmeldeschirm, die eine andere Hülle haben. */
body.is-offline { padding-top: 38px; }
@media (max-width: 560px) { .conn-lost { flex-wrap: wrap; text-align: center; } }

/* Bedienbare Kopfzeile der Dashboard-Liste: klicken sortiert, ziehen verschiebt,
   der Griff am rechten Rand ändert die Breite. Der Griff ist 6 px breit und
   unsichtbar — sichtbar wird er erst unter dem Mauszeiger, sonst stünden in
   jeder Kopfzeile acht Striche. */
/* Die Kopfzeile JEDER Tabelle ist bedienbar (``renderTable`` in ui.js und die
   handgebaute Dashboard-Liste benutzen dieselben Klassen): klicken sortiert,
   ziehen verschiebt, der Strich am rechten Rand ändert die Breite, Doppelklick
   stellt die Vorgaben her. */
.dash-th { position: relative; user-select: none; }
.dash-th.is-sortable { cursor: pointer; }
.dash-th.is-drop { box-shadow: inset 2px 0 0 var(--accent); }
.th-sort { margin-left: 4px; font-size: 10px; opacity: .7; }
/* Der Griff ist 8 px breit (Trefferfläche), sichtbar ist davon ein feiner
   Strich in der Mitte: Ein unsichtbarer Griff wird nicht benutzt, weil niemand
   ihn sucht. Unter dem Mauszeiger wird er kräftig und farbig. */
.th-grip { position: absolute; top: 0; right: -4px; width: 8px; height: 100%;
           cursor: col-resize; z-index: 1; }
.th-grip::before { content: ""; position: absolute; top: 20%; bottom: 20%;
                   left: 50%; width: 1px; background: var(--border); }
.th-grip:hover::before { top: 0; bottom: 0; width: 2px; background: var(--accent); }
/* Die letzte Spalte hat keinen rechten Nachbarn — dort säße der Strich am
   Tabellenrand und sähe aus wie ein Rahmenfehler. */
.dash-th:last-child .th-grip { display: none; }
/* Beim Ziehen darf die Tabelle nicht mitwandern. */
.table--cols th { white-space: nowrap; }

/* Spalte „Identität": Name (oder Kennung) oben, Ladestand und fehlende Energie
   klein darunter. Zwei Zeilen statt einer Wurst — der Name ist die Antwort auf
   „wer lädt hier", die Zahlen sind die Fußnote dazu. Die Spalte bleibt schmal,
   damit sie den Ladepunkten keine Breite nimmt. */
.dash-ident { max-width: 220px; }
.dash-ident__who { display: block; overflow-wrap: anywhere; }
/* Die Kennung neben dem Namen: klein und zurueckhaltend. Sie ist die
   Nachschlage-Auskunft, nicht die Antwort auf „wer laedt hier". */
.dash-ident__id { display: block; font-size: var(--fs-12); overflow-wrap: anywhere; }
.dash-ident__soc { display: block; font-size: var(--fs-12); }
/* Spalte „Betrieb" der Dashboard-Liste: Abzeichen und die beiden Bedienknöpfe
   in einer Reihe, umbruchfähig. Ohne ``white-space`` zerrissen die Abzeichen
   bei schmalem Fenster mitten im Wort; ohne die Ausrichtung stünden Knöpfe und
   Marken unterschiedlich hoch, weil die Knöpfe eine andere Zeilenhöhe haben. */
.dash-ops {
  white-space: nowrap;
}
.dash-ops > * { vertical-align: middle; }
.dash-ops .pill + .pill,
.dash-ops .pill + .tree__open,
.dash-ops .tree__open + .tree__open { margin-left: 4px; }

/* „Jetzt normal laden" als Umschalter: Läuft ein Boost, bleibt der Knopf stehen,
   färbt sich und trägt die Restlaufzeit. Ohne die Färbung wäre nicht zu sehen,
   dass der Klick jetzt das Gegenteil tut. */
.boost-btn.is-on { color: var(--accent); border-color: var(--accent); }
.boost-btn__rest { margin-left: 4px; font-size: 11px; font-variant-numeric: tabular-nums; }

/* Abzeichen neben dem Modal-Titel: kleiner als die Überschrift, damit sie
   diese begleiten und nicht mit ihr konkurrieren. */
.modal__badges { font-size: 12px; font-weight: 400; white-space: nowrap; }
.modal__badges .pill { vertical-align: middle; }

/* „Gruppieren nach" ist in die (einklappbare) Filterleiste gewandert; es steht
   rechts, damit die Zeile von links nach rechts vom Was zum Wie liest. */
.dash-filter__group { display: flex; align-items: center; gap: 8px; margin-left: auto; }

/* Einstellungsliste (Netzübergabepunkt): Bezeichnung links, Wert rechts, ohne
   Tabelle — so bleiben lange Bezeichnungen umbruchfähig und die Werte in einer
   Flucht. */
.kv { display: grid; grid-template-columns: minmax(10rem, max-content) 1fr;
      gap: 2px 16px; margin: 8px 0 0; }
.kv dt { color: var(--fg-muted); font-size: 13px; }
.kv dd { margin: 0; font-size: 13px; }

/* Feldbreiten nach Inhalt statt nach Spalte: Ein Anlagenname braucht keine
   halbe Bildschirmbreite, eine Basis-Adresse etwas mehr. ``ch`` ist hier das
   passende Maß — es zählt Zeichen, nicht Pixel. */
.settings-general .field .input { max-width: 40ch; }
.field--ch60 .input { max-width: 60ch; }
/* Ein Feld für EIN Zeichen: sechs Zeichen breit, Inhalt mittig — schmal genug,
   dass man sieht, worum es geht, und breit genug, dass Rahmen und Zeichen nicht
   aneinanderkleben. Länger als ein Zeichen darf die Eingabe nicht sein; ein
   „Trennzeichen" aus drei Zeichen wäre keins mehr. */
.field--ch6 .input { max-width: 6ch; }
.input--mitte { text-align: center; }

/* Balkendiagramme des Ladekontos mit Achse: Beschriftung links, Rasterlinien
   HINTER den Balken. Ohne Achse war das Bild nur eine Rangfolge — der höchste
   Balken reichte immer bis oben, ob er 3 kWh bedeutete oder 300. */
.acct-chart { display: grid; grid-template-columns: auto 1fr; gap: 6px;
              position: relative; }
.acct-chart__y { position: relative; width: 2.5rem; }
.acct-chart__ylab { position: absolute; right: 0; transform: translateY(50%);
                    font-size: 10px; color: var(--fg-muted); line-height: 1;
                    /* Die Beschriftung sitzt auf der Linie, nicht darüber:
                       Sonst zeigte sie bei "0" unter den Diagrammboden. */ }
/* Bündig mit dem Balkenbereich: Achsenbreite PLUS Spaltenabstand — sonst ragt
   das Gitter sechs Pixel unter die Beschriftung. */
.acct-chart__grid { position: absolute; left: calc(2.5rem + 6px); right: 0; top: 0;
                    /* Die Labelzeile unter den Balken gehört nicht zur Skala. */
                    bottom: 19px; pointer-events: none; }
.acct-days + .acct-chart__grid, .acct-chart:has(.acct-days) .acct-chart__grid { bottom: 18px; }
.acct-chart__grid i { position: absolute; left: 0; right: 0; height: 1px;
                      background: var(--border); opacity: .6; }

/* Zustandsgründe im Ladekonto: Bezeichnung und Wert paarweise untereinander.
   Als Fließtext hinter den Messwerten läse man drei getrennte Aussagen als
   einen Satz — „angehalten weiter ab 14:00 geplante Abfahrt 18:00". */
.acct-why { display: grid; grid-template-columns: max-content 1fr; gap: 2px 12px;
            margin: 8px 0 0; padding-top: 8px; border-top: 1px solid var(--border); }
.acct-why dt { color: var(--fg-muted); font-size: 12px; }
.acct-why dd { margin: 0; font-size: 13px; }


/* Bezugsangabe der Kennzahlenzeile im Ladekonto: eigene Zeile, damit sie nicht
   in der Aufzählung untergeht — sie gilt für alles dahinter. */
.acct-kpis__scope { display: block; font-weight: 600; }

/* ---------------------------------------------------------------------------
   Meldungs-Glocke (Statusleiste) + Posteingang

   Der Zustand steckt in `data-state` am Knopf: none | info | warning. Die Farbe
   allein trüge die Aussage nicht — für Rot-Grün-Blinde und auf blassen
   Bildschirmen ist sie unzuverlässig. Deshalb kommt bei einer Warnung ein
   zweites Merkmal dazu: Die Glocke bekommt Schwingungsbögen (im Markup) und
   den Zähler in Rot. Rot statt Gelb, weil Gelb auf hellem Grund zu leise ist —
   eine Warnung in der Statusleiste muss von der Seite auffallen.
   --------------------------------------------------------------------------- */
.inbox-bell {
  position: relative;
  flex: none;
  padding: 4px 6px;
  line-height: 1;
  color: var(--fg-muted);
}
.inbox-bell__icon { width: 18px; height: 18px; display: block; }
.inbox-bell[data-state="info"] { color: var(--accent); }
.inbox-bell[data-state="warning"] { color: var(--danger); }
.inbox-bell__count {
  position: absolute;
  top: -1px;
  right: -3px;
  min-width: 15px;
  padding: 0 3px;
  border-radius: 8px;
  background: var(--accent);
  color: var(--accent-fg);
  font-size: 10px;
  font-weight: 600;
  line-height: 15px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.inbox-bell[data-state="warning"] .inbox-bell__count { background: var(--danger); color: #fff; }
/* Eine Warnung darf einmal auf sich aufmerksam machen — aber nicht dauerhaft
   wackeln: Ein Symbol, das nie zur Ruhe kommt, wird abgeklebt statt beachtet. */
.inbox-bell[data-state="warning"] .inbox-bell__icon { animation: inbox-ring 1.2s ease-in-out 1; }
@keyframes inbox-ring {
  0%, 100% { transform: rotate(0deg); }
  15% { transform: rotate(-12deg); }
  30% { transform: rotate(10deg); }
  45% { transform: rotate(-7deg); }
  60% { transform: rotate(4deg); }
}
@media (prefers-reduced-motion: reduce) {
  .inbox-bell[data-state="warning"] .inbox-bell__icon { animation: none; }
}

.inbox-panel {
  position: fixed;
  top: calc(var(--statusbar-h) + 6px);
  right: 12px;
  z-index: 1200;
  width: min(380px, calc(100vw - 24px));
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 10px 28px rgba(0, 0, 0, .28);
  overflow: hidden;
}
.inbox-panel__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
}
.inbox-panel__title { font-weight: 600; }
.inbox-panel__seen { font-size: var(--fs-12); padding: 2px 8px; }
.inbox-panel__list { list-style: none; margin: 0; padding: 0; max-height: 60vh; overflow-y: auto; }
.inbox-panel__empty { margin: 0; padding: 18px 10px; color: var(--fg-muted); text-align: center; }
/* Der Weg ins Protokoll ist eine Aktion, kein Fließtext-Verweis — also ein
   Knopf wie überall sonst, nicht ein blauer HTML-Link. */
.inbox-panel__foot { padding: 6px; border-top: 1px solid var(--border); }
.inbox-panel__all { display: block; width: 100%; text-align: center; font-size: var(--fs-12); }

.inbox-item {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 8px 10px;
  cursor: pointer;
  border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
}
.inbox-item:last-child { border-bottom: none; }
.inbox-item:hover { background: color-mix(in srgb, var(--fg) 6%, transparent); }
.inbox-item__dot {
  flex: none;
  width: 8px;
  height: 8px;
  margin-top: 5px;
  border-radius: 50%;
  background: var(--fg-muted);
}
.inbox-item--warning .inbox-item__dot { background: var(--danger); }
.inbox-item--info .inbox-item__dot { background: var(--accent); }
.inbox-item__main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.inbox-item__event { font-size: var(--fs-13); font-weight: 600; }
.inbox-item__meta { font-size: var(--fs-11); color: var(--fg-muted); }

/* Themen tragen zwei Schalter: Versand (braucht ein Ziel) und Glocke (braucht
   keins). Beschriftet, weil ein unbeschriftetes Paar nicht zu erraten wäre. */
.notify-topic__switches { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }

/* Glocken-Knopf je Thema: gedrückt = läutet. Ein Knopf statt „Ein/Aus", weil
   das Symbol die Aussage schon trägt — und weil es dasselbe Symbol ist, das
   danach in der Statusleiste steht. */
.topic-bell { padding: 5px 7px; line-height: 1; color: var(--fg-muted); }
.topic-bell svg { width: 17px; height: 17px; display: block; }
.topic-bell[aria-pressed="true"] {
  color: var(--accent-fg);
  background: var(--accent);
  border-color: var(--accent);
}
/* Aus: die Glocke bleibt sichtbar, aber durchgestrichen — ein blasses Symbol
   allein liest sich als „deaktiviert", nicht als „laeutet nicht". */
.topic-bell[aria-pressed="false"] { position: relative; }
.topic-bell[aria-pressed="false"]::after {
  content: "";
  position: absolute;
  left: 18%;
  right: 18%;
  top: 50%;
  height: 2px;
  background: currentColor;
  transform: rotate(-45deg);
}

/* Simulator-Generator: ein Formular in zwei Spalten, damit die acht Felder
   nicht als Schlange untereinander stehen. */
.simgen-form { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 10px 16px; }

/* Szenarien-Leiste über den Simulator-Stationen: eine Zeile, kein Reiter —
   man ruft ein Szenario auf, um es danach zu SEHEN. */
.sim-scenarios { padding: 8px 10px; }
.sim-scenarios__pick { min-width: 200px; max-width: 340px; }
.sim-scenarios__star { font-size: 15px; line-height: 1; padding: 4px 7px; color: var(--fg-muted); }
.sim-scenarios__star[aria-pressed="true"] { color: var(--warning); }
/* Favoriten als Marken: anklickbar, deshalb Knöpfe — die Marke ist nur die
   Form. Eigene Zeile, damit die Bedienleiste darüber nicht umbricht. */
.sim-scenarios__favs { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.sim-scenarios__fav {
  border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--accent);
  cursor: pointer;
  font: inherit;
  font-size: var(--fs-12);
}
.sim-scenarios__fav:hover { background: color-mix(in srgb, var(--accent) 22%, transparent); }

/* Ungelesene Warnung: Der Weg zur Ursache führt über die Protokolle, also wird
   der Menüpunkt mitmarkiert, solange die Glocke rot ist. Farbe UND Balken —
   eine reine Einfärbung im Menü übersieht man beim Überfliegen. */
:root[data-inbox-warning="1"] .menu-link[href="/tools/logs"] {
  color: var(--danger);
  background: color-mix(in srgb, var(--danger) 12%, transparent);
  box-shadow: inset 3px 0 0 var(--danger);
}
:root[data-inbox-warning="1"] .menu-link[href="/tools/logs"]:hover {
  background: color-mix(in srgb, var(--danger) 20%, transparent);
}

/* Ist der Abschnitt mit den Protokollen zugeklappt, sieht man den roten
   Menüpunkt darunter nicht — dann trägt die Überschrift das Signal. Nur dann:
   Aufgeklappt spricht der Eintrag für sich, und zweimal dasselbe zu sagen macht
   es nicht deutlicher. `aria-expanded` wird beim Zuklappen direkt gesetzt, die
   Regel greift also ohne Neuzeichnen des Menüs. */
:root[data-inbox-warning="1"] .menu-section[data-menu-alert="1"] .menu-section__btn[aria-expanded="false"] {
  color: var(--danger);
  background: color-mix(in srgb, var(--danger) 12%, transparent);
  box-shadow: inset 3px 0 0 var(--danger);
}

/* Zwei Glocken je Thema: Information und Warnung getrennt schaltbar. Der
   kleine Buchstabe unterscheidet sie auch dann, wenn die Farbe nicht ankommt —
   gedrückt trägt jede die Farbe, die sie später in der Statusleiste hätte. */
.topic-bell { position: relative; }
.topic-bell__lvl {
  position: absolute;
  right: 1px;
  bottom: 0;
  font-size: 9px;
  font-weight: 700;
  line-height: 1;
}
.topic-bell--info[aria-pressed="true"] {
  color: var(--accent-fg); background: var(--accent); border-color: var(--accent);
}
.topic-bell--warning[aria-pressed="true"] {
  color: #fff; background: var(--danger); border-color: var(--danger);
}

/* ---------------------------------------------------------------------------
   Zuteilungs-Trace
   Eine Liste von Entscheidungen, je Eintrag aufklappbar. Bewusst keine flache
   Tabelle: Ein Tick betrifft mehrere Ladepunkte, und die Zusammengehörigkeit
   ist die Aussage — wer 9 A bekam, bekam sie WEIL ein anderer 10 bekam.
   --------------------------------------------------------------------------- */
.trace-list { list-style: none; margin: 0; padding: 0; display: flex;
  flex-direction: column; gap: 6px; }
.trace-tick { border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg-elevated); overflow: hidden; }
.trace-tick__head { display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 8px 10px; background: none; border: 0; cursor: pointer;
  text-align: left; color: inherit; font: inherit; flex-wrap: wrap; }
.trace-tick__head:hover {
  background: color-mix(in srgb, var(--fg-muted) 10%, transparent); }
.trace-tick__caret { display: inline-block; transition: transform .12s ease;
  color: var(--fg-muted); }
.trace-tick.is-open .trace-tick__caret { transform: rotate(90deg); }
.trace-tick__time { font-variant-numeric: tabular-nums; font-weight: 600; }
.trace-tick__strategy { font-family: var(--font-mono, ui-monospace, monospace);
  font-size: var(--fs-12); color: var(--fg-muted); }
/* Zugeklappt bleibt nur die Kopfzeile — der Rumpf wird nicht bloß versteckt,
   sondern gar nicht erst gerendert (display:none), damit lange Rohdaten die
   Seite nicht in die Länge ziehen. */
.trace-tick__body { display: none; padding: 0 10px 10px; }
.trace-tick.is-open .trace-tick__body { display: block; }
.trace-table { width: 100%; }
/* Die geänderten Zeilen tragen den Tick; die übrigen sind Kontext. */
.trace-table tr.is-changed {
  background: color-mix(in srgb, var(--accent) 8%, transparent); }
.trace-place { font-weight: 600; }
.trace-delta { font-variant-numeric: tabular-nums; white-space: nowrap; }
.trace-delta.is-up { color: var(--ok); }
.trace-delta.is-down { color: var(--warning); }
.trace-raw__pre { max-height: 320px; overflow: auto; font-size: var(--fs-11, 11px);
  background: color-mix(in srgb, var(--fg-muted) 10%, transparent); padding: 8px;
  border-radius: 4px; margin-top: 4px; }

/* ---- Tools → Logs → Auswertung (gestapeltes Balkendiagramm) ----
   Zwei Spalten: links die Werte der Y-Achse in ihrer eigenen Spur, rechts die
   Zeichenfläche. Vorher lagen die Werte ÜBER den Balken. Die Beschriftungen
   sind HTML, nicht SVG: Die Zeichenfläche wird in die Breite gezogen
   (``preserveAspectRatio="none"``), und mitgezogener Text sieht breitgelaufen
   aus — die Y-Werte in normaler Schrift daneben machten den Unterschied
   sichtbar. */
.logstats__plot { display: grid; grid-template-columns: 3.6em 1fr; gap: 0 8px;
                  margin: 8px 0 4px; }
.logstats__ylabs { position: relative; height: 240px; }
.logstats__ylab { position: absolute; right: 0; transform: translateY(50%);
                  font-size: var(--fs-11); color: var(--fg-muted);
                  font-variant-numeric: tabular-nums; white-space: nowrap; }
.logstats__area { position: relative; }
.logstats__svg { display: block; width: 100%; height: 240px; }
.logstats__grid { stroke: var(--border); stroke-width: 1; vector-effect: non-scaling-stroke; }
.logstats__bar { shape-rendering: crispEdges; }
/* Die Trefferflächen liegen über den Balken, tragen den Hinweis und führen
   beim Klick ins OCPP-Log (Ausschnitt + Filter des Balkens). */
.logstats__hit { fill: transparent; cursor: pointer; }
.logstats__hit:hover { fill: color-mix(in srgb, var(--fg) 6%, transparent); }
/* Zeitachse: je Marke eine Spalte, mittig unter ihrem Balken. Datum oben,
   Uhrzeit darunter — das Datum nur, wenn es wechselt. */
.logstats__xlabs { position: relative; height: 2.6em; margin-top: 2px; }
.logstats__xlab { position: absolute; top: 0; transform: translateX(-50%);
                  display: flex; flex-direction: column; align-items: center; line-height: 1.25;
                  font-size: var(--fs-11); color: var(--fg-muted);
                  font-variant-numeric: tabular-nums; white-space: nowrap; }
.logstats__xlab b { font-weight: 600; color: var(--fg); }
.logstats__legend { display: flex; flex-wrap: wrap; gap: 4px 14px; margin: 8px 0 0;
                    font-size: var(--fs-12); }
.logstats__leg { display: inline-flex; align-items: center; gap: 6px; }
.logstats__dot { width: 10px; height: 10px; border-radius: 2px; display: inline-block; }
.logstats__leer { padding: 32px 0; text-align: center; }
.logstats__bar--c0, .logstats__dot--c0 { fill: var(--accent);  background: var(--accent); }
.logstats__bar--c1, .logstats__dot--c1 { fill: var(--ok);      background: var(--ok); }
.logstats__bar--c2, .logstats__dot--c2 { fill: var(--violet);  background: var(--violet); }
.logstats__bar--c3, .logstats__dot--c3 { fill: var(--warning); background: var(--warning); }
.logstats__bar--c4, .logstats__dot--c4 { fill: var(--danger);  background: var(--danger); }
.logstats__bar--c5, .logstats__dot--c5 { fill: color-mix(in srgb, var(--accent) 55%, var(--fg-muted));
                                         background: color-mix(in srgb, var(--accent) 55%, var(--fg-muted)); }
.logstats__bar--c6, .logstats__dot--c6 { fill: color-mix(in srgb, var(--ok) 55%, var(--fg-muted));
                                         background: color-mix(in srgb, var(--ok) 55%, var(--fg-muted)); }
.logstats__bar--c7, .logstats__dot--c7 { fill: var(--fg-muted); background: var(--fg-muted); }

/* Filter der Auswertung: Klappfelder nebeneinander (Häkchenliste bzw. Baum). */
.ls-dd { position: relative; }
.ls-dd__btn { display: inline-flex; align-items: center; gap: 6px; }
.ls-dd__btn::after { content: "▾"; font-size: 10px; color: var(--fg-muted); }
.ls-dd__n { color: var(--fg-muted); font-size: 12px; }
.ls-dd__pop { position: absolute; z-index: 40; top: calc(100% + 4px); left: 0; min-width: 240px;
              max-width: 360px; background: var(--bg-elevated); border: 1px solid var(--border);
              border-radius: var(--radius); box-shadow: var(--shadow-tile); padding: 6px; }
.ls-dd__head { display: flex; gap: 6px; padding: 0 2px 6px; border-bottom: 1px solid var(--border); }
.ls-dd__list { max-height: 320px; overflow: auto; padding-top: 4px; }
/* Über dem Modal statt darin: Im Konfigurationsbericht scrollt die Tabelle in
   einem eigenen Bereich, und ein absolut positioniertes Klappfeld darin wird an
   dessen Kante abgeschnitten. Mit ``fixed`` hängt es an keinem Scrollbereich
   mehr; Ort und Höhe rechnet die Ansicht aus der Knopfgeometrie. */
.ls-dd__pop--frei { position: fixed; top: auto; left: auto; z-index: 130;
                    display: flex; flex-direction: column; }
.ls-dd__pop--frei .ls-dd__list { max-height: none; flex: 1 1 auto; }
.ls-dd__row { display: flex; align-items: center; gap: 8px; padding: 3px 4px;
              border-radius: var(--radius); cursor: pointer; }
.ls-dd__row:hover { background: color-mix(in srgb, var(--fg) 6%, transparent); }
.ls-dd__lbl { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ls-dd__leer { padding: 8px 4px; }

/* Unbestimmter Fortschritt: VACUUM meldet unterwegs nichts, ein Prozentwert
   wäre erfunden. Der Balken sagt „es läuft", die Zeit daneben sagt, wie lange. */
/* Fortschritt des Aufräumens und des Verdichtens (Einstellungen → Persistenz). */
.cleanup-bar { height: 8px; border-radius: 4px; background: var(--border); overflow: hidden; }
.cleanup-bar > span { display: block; height: 100%; background: var(--accent);
                      transition: width .4s ease; }
.cleanup-bar--unbestimmt > span { width: 35%; animation: cleanup-lauf 1.4s ease-in-out infinite; }
@keyframes cleanup-lauf {
  0%   { margin-left: 0; }
  50%  { margin-left: 65%; }
  100% { margin-left: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .cleanup-bar--unbestimmt > span { animation: none; width: 100%; opacity: .5; }
}


/* Die Klappfelder der Auswertung stehen in einer ``log-controls``-Zeile und
   sollen beim Umbruch nicht zerreißen. */
.ls-dd__btn { white-space: nowrap; }
