﻿/* =====================================================================
   R.DiagX.Web — Design-System "Calibrated Instrument Bench"
   Alle Komponenten & Tokens handgebaut (kein MudBlazor).
   ===================================================================== */

/* =====================================================================
   Design Tokens – ROSI Corporate Identity (Basis #265aa6)
   14 Themes, gruppiert wie im Auswahlfenster (siehe ThemeCatalog)
     dunkel:   rosi-dark · midnight · graphite · petrol · indigo · amber
     hell:     rosi-light · paper · nordic · zinc · sage · solar
     kontrast: contrast-dark · contrast-light
   ===================================================================== */

/* ---------- Theme-unabhängige Tokens ---------- */
:root {
    --brand:     #265aa6;   /* ROSI-CI (Referenz) */
    --brand-600: #1f4d8f;
    --brand-400: #4a83d0;

    /* Die dunkelblaue CI-Fläche. Sie liegt hinter dem ROSI-Logo oben links in der
       Navigation und in der Kopfzeile von Ro.Ki – beide sollen dieselbe sein, und
       zweimal dieselben Hexwerte zu tippen heißt, dass sie beim nächsten Mal
       auseinanderlaufen. */
    --brand-grad: linear-gradient(135deg, #1c4278 0%, #0f2340 100%);

    --radius:    10px;
    --radius-sm: 6px;
    --font-ui:   'Space Grotesk', system-ui, -apple-system, 'Segoe UI', sans-serif;
    --font-body: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
    --font-mono: 'JetBrains Mono', 'Cascadia Code', 'Consolas', ui-monospace, monospace;

    /* gemeinsame Signal-/Status-Palette (Amber bleibt Warn-Signal) */
    --s-amber:  #F5A623; --s-cyan: #38BDF8; --s-violet: #A78BFA;
    --s-green:  #34D399; --s-rose: #FB7185; --s-slate: #94A3B8;
}

/* ---------- ROSI Dark (Standard) ---------- */
:root,
:root[data-theme="rosi-dark"] {
    --bg:            #0A0E15;
    --bg-grid:       rgba(120,170,240,0.022);
    --panel:         #111823;
    --panel-raised:  #18212E;
    --panel-inset:   #0D131C;
    --line:          #212C3B;
    --line-strong:   #2C3A4D;
    --text:          #E6EDF6;
    --text-dim:      #8A98AB;
    --text-faint:    #586475;

    --accent:        #3E7BD1;   /* aufgehelltes CI-Blau für Dark */
    --accent-hover:  #5590de;
    --accent-soft:   rgba(62,123,209,0.15);
    --accent-glow:   rgba(62,123,209,0.5);
    --on-accent:     #FFFFFF;

    --ok:            #34D399;
    --warn:          #F5A623;
    --fault:         #F0483E;
    --stale:         #6B7684;
    --info:          #4A9FE8;

    --shadow:        0 18px 40px -20px rgba(0,0,0,0.72);
    color-scheme: dark;
}

/* ---------- ROSI Light ---------- */
:root[data-theme="rosi-light"] {
    --bg:            #EDF1F6;
    --bg-grid:       rgba(38,90,166,0.04);
    --panel:         #FFFFFF;
    --panel-raised:  #F5F8FC;
    --panel-inset:   #EEF3F9;
    --line:          #D9E1EC;
    --line-strong:   #C2CEDD;
    --text:          #13233A;
    --text-dim:      #536379;
    --text-faint:    #8A99AD;

    --accent:        #265AA6;   /* reines CI-Blau */
    --accent-hover:  #1f4d8f;
    --accent-soft:   rgba(38,90,166,0.10);
    --accent-glow:   rgba(38,90,166,0.35);
    --on-accent:     #FFFFFF;

    --ok:            #0F9D63;
    --warn:          #C77D0A;
    --fault:         #D3382E;
    --stale:         #94A3B8;
    --info:          #265AA6;

    --shadow:        0 14px 34px -22px rgba(20,40,70,0.34);
    color-scheme: light;
}

/* ---------- Paper (warmes Off-White, hoher Kontrast) ---------- */
:root[data-theme="paper"] {
    --bg:            #F7F6F3;
    --bg-grid:       rgba(60,50,40,0.035);
    --panel:         #FFFFFF;
    --panel-raised:  #FBFAF8;
    --panel-inset:   #F2F0EC;
    --line:          #E0DDD6;
    --line-strong:   #C8C4BA;
    --text:          #1A1815;
    --text-dim:      #5A554D;
    --text-faint:    #8C867C;

    --accent:        #265AA6;   /* reines CI-Blau auf warmem Grund */
    --accent-hover:  #1f4d8f;
    --accent-soft:   rgba(38,90,166,0.10);
    --accent-glow:   rgba(38,90,166,0.32);
    --on-accent:     #FFFFFF;

    --ok:            #0E7A50;
    --warn:          #A8620A;
    --fault:         #C22F26;
    --stale:         #8C867C;
    --info:          #265AA6;

    --shadow:        0 14px 34px -22px rgba(40,34,26,0.32);
    color-scheme: light;
}

/* ---------- Nordic (kühles Blaugrau, gedämpft) ---------- */
:root[data-theme="nordic"] {
    --bg:            #E9EEF4;
    --bg-grid:       rgba(46,109,164,0.045);
    --panel:         #F7FAFD;
    --panel-raised:  #FFFFFF;
    --panel-inset:   #E4EBF3;
    --line:          #D2DCE7;
    --line-strong:   #B4C3D3;
    --text:          #1B2A3A;
    --text-dim:      #55677C;
    --text-faint:    #8496A9;

    --accent:        #2E6DA4;
    --accent-hover:  #245A8B;
    --accent-soft:   rgba(46,109,164,0.11);
    --accent-glow:   rgba(46,109,164,0.34);
    --on-accent:     #FFFFFF;

    --ok:            #0F8A5F;
    --warn:          #B06E09;
    --fault:         #C9382D;
    --stale:         #8496A9;
    --info:          #2E6DA4;

    --shadow:        0 14px 34px -22px rgba(27,42,58,0.30);
    color-scheme: light;
}

/* ---------- Solar (helles Beige, Amber-Akzent – Pendant zu amber) ---------- */
:root[data-theme="solar"] {
    --bg:            #F6F1E7;
    --bg-grid:       rgba(140,100,20,0.05);
    --panel:         #FFFDF8;
    --panel-raised:  #FFFFFF;
    --panel-inset:   #F1EADB;
    --line:          #E3D9C4;
    --line-strong:   #CBBE9F;
    --text:          #241D10;
    --text-dim:      #6B5D45;
    --text-faint:    #9C8D72;

    --accent:        #B4770E;   /* abgedunkeltes Amber – lesbar auf Hell */
    --accent-hover:  #96620A;
    --accent-soft:   rgba(180,119,14,0.12);
    --accent-glow:   rgba(180,119,14,0.35);
    --on-accent:     #FFFFFF;

    --ok:            #0E7A50;
    --warn:          #B4770E;
    --fault:         #C22F26;
    --stale:         #9C8D72;
    --info:          #2E6DA4;

    --shadow:        0 14px 34px -22px rgba(60,45,15,0.30);
    color-scheme: light;
}

/* ---------- Midnight (tiefblau, passend zum Login-Hintergrund) ---------- */
:root[data-theme="midnight"] {
    --bg:            #050B15;
    --bg-grid:       rgba(90,150,230,0.03);
    --panel:         #0B1728;
    --panel-raised:  #122238;
    --panel-inset:   #081221;
    --line:          #16283F;
    --line-strong:   #21395A;
    --text:          #DCE8F8;
    --text-dim:      #7C90AE;
    --text-faint:    #4C607D;

    --accent:        #4C86D6;
    --accent-hover:  #6699e2;
    --accent-soft:   rgba(76,134,214,0.16);
    --accent-glow:   rgba(76,134,214,0.55);
    --on-accent:     #04101F;

    --ok:            #34D399;
    --warn:          #F5A623;
    --fault:         #FB5D53;
    --stale:         #5A6E8C;
    --info:          #4C86D6;

    --shadow:        0 20px 44px -20px rgba(0,0,0,0.8);
    color-scheme: dark;
}

/* ---------- Graphite (neutral dunkel) ---------- */
:root[data-theme="graphite"] {
    --bg:            #131417;
    --bg-grid:       rgba(255,255,255,0.02);
    --panel:         #1B1D21;
    --panel-raised:  #23262B;
    --panel-inset:   #17181B;
    --line:          #2B2F35;
    --line-strong:   #3A3F47;
    --text:          #E7E9ED;
    --text-dim:      #9AA0A9;
    --text-faint:    #666C75;

    --accent:        #4A83D0;
    --accent-hover:  #5f95dc;
    --accent-soft:   rgba(74,131,208,0.15);
    --accent-glow:   rgba(74,131,208,0.5);
    --on-accent:     #FFFFFF;

    --ok:            #3DD39B;
    --warn:          #F5A623;
    --fault:         #F0483E;
    --stale:         #6B7178;
    --info:          #4A83D0;

    --shadow:        0 18px 40px -20px rgba(0,0,0,0.75);
    color-scheme: dark;
}

/* ---------- Amber (Legacy-Look) ---------- */
:root[data-theme="amber"] {
    --bg:            #0B0F14;
    --bg-grid:       rgba(255,255,255,0.018);
    --panel:         #12181F;
    --panel-raised:  #1A2029;
    --panel-inset:   #0E131A;
    --line:          #232B36;
    --line-strong:   #2E3846;
    --text:          #E6EDF3;
    --text-dim:      #8B97A5;
    --text-faint:    #5B6672;

    --accent:        #F5A623;
    --accent-hover:  #ffb734;
    --accent-soft:   rgba(245,166,35,0.14);
    --accent-glow:   rgba(245,166,35,0.45);
    --on-accent:     #10130A;

    --ok:            #34D399;
    --warn:          #F5A623;
    --fault:         #F0483E;
    --stale:         #6B7684;
    --info:          #38BDF8;

    --shadow:        0 18px 40px -20px rgba(0,0,0,0.7);
    color-scheme: dark;
}

/* ---------- Petrol (dunkel) ---------- */
/* Blaugruen statt Blau: derselbe kuehle Eindruck, aber deutlich von ROSI Dark und
   Midnight zu unterscheiden - und Petrol ist keine Statusfarbe, kollidiert also nicht mit
   gruen/bernstein/rot. */
:root[data-theme="petrol"] {
    --bg:            #081316;
    --bg-grid:       rgba(120,220,235,0.022);
    --panel:         #0E1D21;
    --panel-raised:  #14272C;
    --panel-inset:   #0A171A;
    --line:          #1B3036;
    --line-strong:   #254149;
    --text:          #E2EEF0;
    --text-dim:      #88A3A9;
    --text-faint:    #566D73;

    --accent:        #2A9DB5;
    --accent-hover:  #3BB4CD;
    --accent-soft:   rgba(42,157,181,0.15);
    --accent-glow:   rgba(42,157,181,0.45);
    --on-accent:     #04191E;

    --ok:            #34D399;
    --warn:          #F5A623;
    --fault:         #F0483E;
    --stale:         #63787E;
    --info:          #38BDF8;

    --shadow:        0 18px 40px -20px rgba(0,0,0,0.72);
    color-scheme: dark;
}

/* ---------- Indigo (dunkel) ---------- */
/* Violettblau. Der einzige Akzent im Katalog, der nicht aus der blau-gruenen Haelfte des
   Farbkreises kommt - und trotzdem keine Statusfarbe. Fuer Arbeitsplaetze, an denen
   mehrere Portale nebeneinander offen sind: Man erkennt das Fenster an der Farbe. */
:root[data-theme="indigo"] {
    --bg:            #0B0A14;
    --bg-grid:       rgba(170,160,255,0.024);
    --panel:         #15131F;
    --panel-raised:  #1D1B2A;
    --panel-inset:   #100E19;
    --line:          #252236;
    --line-strong:   #332F47;
    --text:          #E9E7F4;
    --text-dim:      #9994AE;
    --text-faint:    #635E78;

    --accent:        #8B7BE8;
    --accent-hover:  #9E90F0;
    --accent-soft:   rgba(139,123,232,0.16);
    --accent-glow:   rgba(139,123,232,0.45);
    --on-accent:     #0D0A1A;

    --ok:            #34D399;
    --warn:          #F5A623;
    --fault:         #F0483E;
    --stale:         #6E6885;
    --info:          #8B7BE8;

    --shadow:        0 18px 40px -20px rgba(0,0,0,0.75);
    color-scheme: dark;
}

/* ---------- Zinc (hell) ---------- */
/* Neutralgrau ohne Farbstich. Paper ist warm, Nordic kuehl - hier ist die Flaeche
   ausdruecklich farblos, damit die Statusfarben und die Diagramme das einzige Farbige auf
   dem Bildschirm sind. Die sachlichste der hellen Fassungen. */
:root[data-theme="zinc"] {
    --bg:            #F2F3F5;
    --bg-grid:       rgba(30,32,36,0.035);
    --panel:         #FFFFFF;
    --panel-raised:  #F7F8F9;
    --panel-inset:   #F0F1F3;
    --line:          #DCDEE2;
    --line-strong:   #C4C7CD;
    --text:          #1A1C1F;
    --text-dim:      #5A5F67;
    --text-faint:    #8B9098;

    --accent:        #2F5FA8;
    --accent-hover:  #264F8E;
    --accent-soft:   rgba(47,95,168,0.10);
    --accent-glow:   rgba(47,95,168,0.32);
    --on-accent:     #FFFFFF;

    --ok:            #0F9D63;
    --warn:          #C77D0A;
    --fault:         #D3382E;
    --stale:         #8B9098;
    --info:          #2F5FA8;

    --shadow:        0 14px 34px -22px rgba(20,25,35,0.30);
    color-scheme: light;
}

/* ---------- Sage (hell) ---------- */
/* Graugruen mit Petrol-Akzent. Der Grund ist leicht gruenlich, der AKZENT bleibt Petrol
   und nicht Gruen: Gruen ist Statusfarbe (siehe ThemeCatalog), und ein gruener Knopf
   neben einer gruenen Statuslampe heisst zweimal etwas anderes. */
:root[data-theme="sage"] {
    --bg:            #EDF2EF;
    --bg-grid:       rgba(25,60,50,0.035);
    --panel:         #FBFDFB;
    --panel-raised:  #F3F7F4;
    --panel-inset:   #E9F0EC;
    --line:          #D5E0DA;
    --line-strong:   #BCCCC3;
    --text:          #152220;
    --text-dim:      #4F6159;
    --text-faint:    #82948B;

    --accent:        #1F7C8C;
    --accent-hover:  #186675;
    --accent-soft:   rgba(31,124,140,0.11);
    --accent-glow:   rgba(31,124,140,0.32);
    --on-accent:     #FFFFFF;

    --ok:            #0F9D63;
    --warn:          #C77D0A;
    --fault:         #D3382E;
    --stale:         #82948B;
    --info:          #1F7C8C;

    --shadow:        0 14px 34px -22px rgba(20,40,35,0.28);
    color-scheme: light;
}

/* ---------- Kontrast Dunkel ---------- */
/* Reines Schwarz, reines Weiss, und Raender, die man sieht. Das ist kein dunkles Theme
   mit mehr Kontrast, sondern eine Bedienhilfe: Jede Textfarbe erreicht gegen ihren Grund
   mindestens 7:1 (WCAG AAA fuer Fliesstext), und --text-dim ist nicht mehr grau, sondern
   nur wenig abgesetzt - gedaempfte Schrift ist genau das, was hier nicht helfen wuerde.

   Die Statusfarben sind aufgehellt: #34D399 auf Schwarz ist lesbar, #0F9D63 nicht. */
:root[data-theme="contrast-dark"] {
    --bg:            #000000;
    --bg-grid:       rgba(255,255,255,0.03);
    --panel:         #0A0A0A;
    --panel-raised:  #141414;
    --panel-inset:   #050505;
    --line:          #5A5A5A;
    --line-strong:   #8A8A8A;
    --text:          #FFFFFF;
    --text-dim:      #D6D6D6;
    --text-faint:    #ABABAB;

    --accent:        #5AB0FF;
    --accent-hover:  #82C4FF;
    --accent-soft:   rgba(90,176,255,0.22);
    --accent-glow:   rgba(90,176,255,0.6);
    --on-accent:     #000000;

    --ok:            #57E6A6;
    --warn:          #FFC24D;
    --fault:         #FF7A6E;
    --stale:         #ABABAB;
    --info:          #5AB0FF;

    --shadow:        none;
    color-scheme: dark;
}

/* ---------- Kontrast Hell ---------- */
:root[data-theme="contrast-light"] {
    --bg:            #FFFFFF;
    --bg-grid:       rgba(0,0,0,0.035);
    --panel:         #FFFFFF;
    --panel-raised:  #F2F2F2;
    --panel-inset:   #EBEBEB;
    --line:          #6A6A6A;
    --line-strong:   #3A3A3A;
    --text:          #000000;
    --text-dim:      #2E2E2E;
    --text-faint:    #555555;

    --accent:        #0B4FA8;
    --accent-hover:  #093C80;
    --accent-soft:   rgba(11,79,168,0.14);
    --accent-glow:   rgba(11,79,168,0.45);
    --on-accent:     #FFFFFF;

    --ok:            #056B43;
    --warn:          #8A5200;
    --fault:         #A5201A;
    --stale:         #555555;
    --info:          #0B4FA8;

    --shadow:        none;
    color-scheme: light;
}

/* ---------- Was die Kontrastthemes ueber Farben hinaus aendern ----------
   Farbtoken allein reichen nicht. Diese Oberflaeche traegt einen Teil ihrer Gliederung in
   duennen Linien, halbdurchsichtigen Flaechen und Schatten - und genau das ist es, was bei
   eingeschraenktem Sehen zuerst verschwindet. Deshalb hier:

     * Raender einen Punkt dicker. Ein 1px-Rand bei 125 % Skalierung auf einem
       Billigmonitor ist kein Rand.
     * Keine Schatten und keine Weichzeichner. Sie ersetzen einen Rand durch eine Ahnung.
     * Der Tastaturfokus doppelt so kraeftig. Wer den Kontrastmodus braucht, bedient die
       Seite oefter mit der Tastatur.
     * Der animierte Hintergrund bleibt aus. Er ist Zierde, und Zierde unter Text ist
       genau das Gegenteil von Kontrast. */
:root[data-theme="contrast-dark"] .panel,
:root[data-theme="contrast-light"] .panel,
:root[data-theme="contrast-dark"] .table-wrap,
:root[data-theme="contrast-light"] .table-wrap,
:root[data-theme="contrast-dark"] .btn,
:root[data-theme="contrast-light"] .btn,
:root[data-theme="contrast-dark"] .input,
:root[data-theme="contrast-light"] .input,
:root[data-theme="contrast-dark"] .select,
:root[data-theme="contrast-light"] .select,
:root[data-theme="contrast-dark"] .chip,
:root[data-theme="contrast-light"] .chip {
    border-width: 2px;
}

:root[data-theme="contrast-dark"] *,
:root[data-theme="contrast-light"] * {
    box-shadow: none !important;
    backdrop-filter: none !important;
}

:root[data-theme="contrast-dark"] :focus-visible,
:root[data-theme="contrast-light"] :focus-visible {
    outline: 3px solid var(--accent);
    outline-offset: 2px;
}

/* Der Hintergrund bleibt im Kontrastmodus aus - unabhaengig von der Einstellung. Das ist
   bewusst nicht abwaehlbar: Wer Kontrast waehlt, waehlt gegen Zierde unter Text. */
:root[data-theme="contrast-dark"] .cx-quiet,
:root[data-theme="contrast-light"] .cx-quiet { display: none; }

/* ---------- Der animierte Hintergrund, abschaltbar ----------
   Als Attribut am Wurzelelement und nicht als Klasse an einem Element im Baum: Es wird
   genauso frueh gesetzt wie data-theme (siehe js/app.js), noch bevor Blazor laeuft. Sonst
   liefe der Hintergrund beim Laden einen Moment mit und verschwaende dann - und genau das
   waere bei jemandem, der Bewegung abbestellt hat, das Aergerlichste. */
:root[data-backdrop="off"] .cx-quiet { display: none; }

/* ---------- Reset / Base ---------- */
*, *::before, *::after { box-sizing: border-box; }
html, body { height: 100%; }
body {
    margin: 0;
    font-family: var(--font-body);
    background-color: var(--bg);
    background-image:
        linear-gradient(var(--bg-grid) 1px, transparent 1px),
        linear-gradient(90deg, var(--bg-grid) 1px, transparent 1px);
    background-size: 40px 40px;
    color: var(--text);
    font-size: 14px;
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
}
h1,h2,h3,h4 { font-family: var(--font-ui); font-weight: 600; letter-spacing: -0.01em; margin: 0; }
a { color: inherit; text-decoration: none; }
button { font-family: inherit; }
::selection { background: var(--accent-soft); }

/* Scrollbars */
* { scrollbar-width: thin; scrollbar-color: var(--line-strong) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 6px; border: 2px solid transparent; background-clip: content-box; }

/* Focus */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }

/* ---------- Micro-Label (Kanal-Stil) ---------- */
.eyebrow {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--text-dim);
}

/* ---------- Mono-Readout (Signatur) ---------- */
.readout { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.readout-lg { font-size: 2rem; font-weight: 700; letter-spacing: -0.02em; }
.readout-unit { font-size: 0.55em; color: var(--text-dim); margin-left: 0.35em; font-weight: 500; }
@keyframes phosphor { 0% { color: var(--accent); text-shadow: 0 0 14px var(--accent-glow); } 100% { color: var(--text); text-shadow: none; } }
.readout.tick { animation: phosphor 0.9s ease-out; }
@media (prefers-reduced-motion: reduce) {
    /* Die Leiterplatte hört als Erstes auf, sich zu bewegen – auf der Anmeldeseite wie
       auf dem Dashboard, denn beide benutzen dieselben Klassen (siehe CircuitBackdrop).
       Ihre Bewegung ist Zierde, und Zierde ist das Erste, was jemand abbestellt, dem
       Bewegung schadet. */
    .lb-orb, .cx-glow use, .cx-pulse use { animation: none; }
    /* Ohne Lauf kein Strichmuster: Der helle Abschnitt bliebe sonst irgendwo auf seiner
       Bahn liegen, an einer Stelle, die nichts bedeutet. Stattdessen verschwindet der
       Strom, und es bleibt eine Leiterplatte – die ist im Stillstand richtig. */
    .cx-glow, .cx-pulse { display: none; }
 .readout.tick { animation: none; } }

/* ---------- Panel (mit Eck-Ticks) ---------- */
.panel {
    position: relative;
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
}
.panel::before, .panel::after {
    content: ''; position: absolute; width: 8px; height: 8px; pointer-events: none;
    border-color: var(--line-strong); border-style: solid;
}
.panel::before { top: 6px; left: 6px; border-width: 1px 0 0 1px; }
.panel::after  { bottom: 6px; right: 6px; border-width: 0 1px 1px 0; }
.panel-head {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    padding: 12px 16px; border-bottom: 1px solid var(--line);
}
.panel-title { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.panel-title h3 { font-size: 14px; }
.panel-body { padding: 16px; }

/* ---------- Status-LED ---------- */
.led { display: inline-flex; align-items: center; gap: 7px; font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-dim); }
.led .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--stale); box-shadow: 0 0 0 3px rgba(255,255,255,0.03); flex: none; }
.led.ok    .dot { background: var(--ok);    box-shadow: 0 0 8px var(--ok); }
.led.warn  .dot { background: var(--warn);  box-shadow: 0 0 8px var(--warn); }
.led.fault .dot { background: var(--fault); box-shadow: 0 0 8px var(--fault); }
.led.ok, .led.fault, .led.warn { color: var(--text); }
@keyframes ledpulse { 0%,100% { opacity: 1; } 50% { opacity: 0.45; } }
.led.live .dot { animation: ledpulse 1.6s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) { .led.live .dot { animation: none; } }

/* ---------- Buttons ---------- */
.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 8px;
    height: 38px; padding: 0 16px; border-radius: var(--radius-sm);
    background: var(--panel-raised); color: var(--text); border: 1px solid var(--line-strong);
    font-family: var(--font-ui); font-size: 13px; font-weight: 500; cursor: pointer;
    transition: background .15s, border-color .15s, transform .05s;
    white-space: nowrap;
}
.btn:hover { border-color: var(--accent); background: var(--panel-inset); }
.btn:active { transform: translateY(1px); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-primary { background: var(--accent); color: var(--on-accent); border-color: var(--accent); font-weight: 600; }
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.btn-ghost { background: transparent; border-color: transparent; }
.btn-ghost:hover { background: var(--panel-raised); border-color: var(--line); }
.btn-danger { border-color: var(--fault); color: var(--fault); }
.btn-danger:hover { background: var(--fault); color: #fff; }
.btn-sm { height: 30px; padding: 0 11px; font-size: 12px; }
.btn-icon { width: 38px; padding: 0; }
.btn-icon.btn-sm { width: 30px; }

/* ---------- Inputs ---------- */
.field { display: flex; flex-direction: column; gap: 6px; }
.field > label { font-size: 12px; color: var(--text-dim); font-weight: 500; }
.input, .select, textarea.input {
    height: 38px; padding: 0 12px; width: 100%;
    background: var(--panel-inset); color: var(--text);
    border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
    font-family: var(--font-body); font-size: 13px; transition: border-color .15s;
}
textarea.input { height: auto; padding: 10px 12px; resize: vertical; }
.input:focus, .select:focus, textarea.input:focus { border-color: var(--accent); outline: none; }
.input::placeholder { color: var(--text-faint); }
.select { appearance: none; cursor: pointer;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2 4l4 4 4-4' stroke='%238B97A5' stroke-width='1.4' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat; background-position: right 12px center; padding-right: 32px; }
.input-mono { font-family: var(--font-mono); }

/* Toggle */
.toggle { position: relative; display: inline-block; width: 40px; height: 22px; flex: none; }
.toggle input { opacity: 0; width: 0; height: 0; }
.toggle .track { position: absolute; inset: 0; background: var(--line-strong); border-radius: 22px; transition: background .15s; cursor: pointer; }
.toggle .track::before { content: ''; position: absolute; width: 16px; height: 16px; left: 3px; top: 3px; background: var(--text); border-radius: 50%; transition: transform .15s; }
.toggle input:checked + .track { background: var(--accent); }
.toggle input:checked + .track::before { transform: translateX(18px); background: var(--on-accent); }

/* Checkbox */
.check { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; user-select: none; font-size: 13px; }
.check input { width: 16px; height: 16px; accent-color: var(--accent); }

/* ---------- Chips / Badges ---------- */
.chip { display: inline-flex; align-items: center; gap: 6px; height: 24px; padding: 0 10px; border-radius: 20px; background: var(--panel-raised); border: 1px solid var(--line); font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.04em; color: var(--text-dim); white-space: nowrap; flex: none; }
.chip.accent { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
button.chip { cursor: pointer; }

/* ---------- Tabs ---------- */
.tabs { display: inline-flex; gap: 2px; padding: 3px; background: var(--panel-inset); border: 1px solid var(--line); border-radius: var(--radius-sm); }
.tabs button { height: 30px; padding: 0 14px; border: none; background: transparent; color: var(--text-dim); font-family: var(--font-ui); font-size: 12px; font-weight: 500; border-radius: 4px; cursor: pointer; transition: background .12s, color .12s; }
.tabs button.active { background: var(--panel-raised); color: var(--text); box-shadow: inset 0 0 0 1px var(--line-strong); }
.tabs button:hover:not(.active) { color: var(--text); }

/* ---------- Table ---------- */
.table-wrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel); }
table.data { width: 100%; border-collapse: collapse; font-size: 13px; }
table.data th { text-align: left; font-family: var(--font-mono); font-size: 10.5px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-dim); font-weight: 500; padding: 11px 14px; border-bottom: 1px solid var(--line-strong); background: var(--panel-raised); position: sticky; top: 0; white-space: nowrap; }
table.data th.sortable { cursor: pointer; }
table.data th.sortable:hover { color: var(--text); }
table.data td { padding: 10px 14px; border-bottom: 1px solid var(--line); }
table.data tbody tr:hover { background: var(--panel-raised); }
table.data tbody tr:last-child td { border-bottom: none; }
table.data .num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; text-align: right; }

/* ---------- App-Shell ---------- */
/* dvh: auf Mobile-Browsern zählt die ein-/ausfahrende URL-Leiste nicht zur Höhe. */
/* Die Breite der Navigation steht als Variable und nicht als Zahl in sechs Regeln:
   js/shell.js liest sie, um zu pruefen, ob ein Dialog die Rail beruehrt - und zwar
   die AUSGEKLAPPTE Breite, auch wenn die Rail gerade eingeklappt ist. Mit der
   gemessenen Breite waere das eine Schaukel: eingeklappt beruehrt der Dialog nichts
   mehr, also klappte die Rail auf, also beruehrte er sie wieder. */
.app-shell {
    --rail-w: 232px;
    --rail-w-min: 64px;
    display: grid; grid-template-columns: var(--rail-w) 1fr;
    min-height: 100vh; min-height: 100dvh;
}
.nav-rail { position: sticky; top: 0; height: 100vh; height: 100dvh; display: flex; flex-direction: column; background: var(--panel); border-right: 1px solid var(--line); z-index: 20; }
/* Scrim und Menü-Button existieren nur unterhalb des Drawer-Breakpoints (≤900px).
   Ohne dieses display:none bliebe nach einem Resize auf Desktop-Breite ein
   unsichtbarer Klick-Blocker über der Seite liegen. */
.nav-scrim { display: none; position: fixed; inset: 0; z-index: 19; background: rgba(4,7,11,0.55); backdrop-filter: blur(2px); animation: fade .15s ease-out; }
.nav-toggle { display: none; }

/* Der Gegenknopf zum Schubladenknopf: Er macht die Leiste schmal, statt sie zu
   oeffnen, und steht deshalb genau dort, wo auf dem Telefon der andere steht. Unter
   900px faellt er weg - dort IST die Leiste eine Schublade. */
.nav-narrow { display: inline-flex; }
/* Feste dunkelblaue CI-Fläche, damit das weiße Logo in ALLEN Themes lesbar bleibt */
.brand {
    position: relative; overflow: hidden;
    display: flex; flex-direction: column; gap: 7px; padding: 18px 14px; border-bottom: 1px solid var(--line);
    background: var(--brand-grad);
}
/* Feiner Rasterschimmer über der CI-Fläche – gibt dem Kopf Tiefe, ohne Bild. */
.brand::before {
    content: ''; position: absolute; inset: 0; pointer-events: none;
    background:
        radial-gradient(120% 90% at 15% -10%, rgba(122,176,255,0.30), transparent 62%),
        repeating-linear-gradient(0deg, rgba(255,255,255,0.03) 0 1px, transparent 1px 4px);
    opacity: 0.5;
}
.brand > * { position: relative; }
.brand .brand-logo { height: 26px; width: auto; display: block; }

/* Terminalzeile: Prompt, getippter Name, blinkender Cursor – alles in einer Zeile.
   Die Rail ist 232px breit, abzüglich Polsterung bleiben 196px: darin müssen Prompt,
   „R.DiagX – Remote Diagnostics" und der Cursor Platz finden, ohne umzubrechen. */
.brand-term { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.brand-prompt { font-family: var(--font-mono); font-size: 11.5px; color: var(--brand-400); opacity: 0.9; flex: none; }
/* Der Text wird beim Laden aufgebaut – steps() macht daraus ein Tippen, kein Wischen. */
.brand-type {
    display: flex; align-items: baseline; gap: 5px; min-width: 0;
    overflow: hidden; white-space: nowrap; animation: brand-type 1.4s steps(27, end) both;
}
.brand-name {
    font-family: var(--font-ui); font-weight: 700; font-size: 14px; letter-spacing: -0.01em;
    color: #F1F6FC; flex: none;
}
.brand-caret {
    width: 7px; height: 14px; flex: none; align-self: center; border-radius: 1px;
    background: var(--brand-400); box-shadow: 0 0 10px -1px rgba(74,131,208,0.9);
    animation: brand-blink 1.05s steps(1, end) infinite;
}
/* Sperrung knapp gehalten: mit 0.06em passt die Zeile nicht mehr in die 232px-Rail. */
.brand-sub {
    font-family: var(--font-mono); font-size: 8.5px; letter-spacing: 0.04em;
    color: #9DB4D6; text-transform: uppercase; white-space: nowrap;
    overflow: hidden; text-overflow: ellipsis; min-width: 0;
}
@keyframes brand-type { from { max-width: 0; } to { max-width: 230px; } }
@keyframes brand-blink { 0%, 50% { opacity: 1; } 50.01%, 100% { opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
    .brand-type { animation: none; }
    .brand-caret { animation: none; opacity: 0.85; }
}

.nav-list { display: flex; flex-direction: column; gap: 2px; padding: 12px; flex: 1; overflow-y: auto; }
/* Gruppentitel: ordnet die Einträge, ohne selbst anklickbar zu wirken. */
.nav-group {
    font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.16em; text-transform: uppercase;
    color: var(--text-faint); padding: 14px 12px 5px; user-select: none;
}
.nav-group:first-child { padding-top: 2px; }
/* Kennzeichen am Gruppentitel, etwa „DEV“ für einen Bereich in Entwicklung. */
.nav-badge {
    display: inline-block; margin-left: 6px; padding: 0 5px; border-radius: 3px; vertical-align: 1px;
    border: 1px solid color-mix(in srgb, var(--warn) 60%, transparent);
    color: var(--warn); font-size: 8.5px; letter-spacing: .08em; line-height: 14px;
}
.nav-item { display: flex; align-items: center; gap: 12px; padding: 10px 12px; border-radius: var(--radius-sm); color: var(--text-dim); font-family: var(--font-ui); font-size: 13.5px; font-weight: 500; cursor: pointer; position: relative; transition: background .12s, color .12s; }
.nav-item svg { width: 18px; height: 18px; flex: none; }
.nav-item:hover { background: var(--panel-raised); color: var(--text); }
.nav-item.active { background: var(--accent-soft); color: var(--accent); }
.nav-item.active::before { content: ''; position: absolute; left: -12px; top: 8px; bottom: 8px; width: 3px; background: var(--accent); border-radius: 0 3px 3px 0; }
.nav-foot { padding: 12px; border-top: 1px solid var(--line); font-family: var(--font-mono); font-size: 10px; color: var(--text-faint); letter-spacing: 0.06em; }

/* Ein-/Ausklappen der Navigation */
.nav-collapse {
    display: flex; align-items: center; gap: 10px; margin: 0 12px 10px; padding: 8px 12px;
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--panel-raised); color: var(--text-dim); cursor: pointer;
    font-family: var(--font-ui); font-size: 12.5px; font-weight: 500;
    transition: color .12s, border-color .12s;
}
.nav-collapse:hover { color: var(--accent); border-color: var(--accent); }
.nav-collapse svg { width: 16px; height: 16px; flex: none; transition: transform .16s ease; }

/* Eingeklappt: Icon-Rail. Nur auf dem Desktop – ab 900px abwärts ist die Rail ein
   Drawer und wird über .nav-rail.open gesteuert. */
@media (min-width: 901px) {
    .app-shell { transition: grid-template-columns .16s ease; }
    .app-shell.nav-collapsed { grid-template-columns: var(--rail-w-min) 1fr; }

    .nav-rail.collapsed .brand { padding: 16px 8px; align-items: center; }
    /* object-fit statt max-width: sonst klemmt die Breite, während height fix bleibt,
       und das Logo wird gequetscht statt verkleinert. */
    .nav-rail.collapsed .brand-logo { height: 22px; width: 100%; object-fit: contain; }
    .nav-rail.collapsed .brand-term { display: none; }

    .nav-rail.collapsed .nav-list { padding: 12px 8px; }
    /* Eingeklappt bleibt von der Gruppe nur ein Trennstrich – der Titel hätte keinen Platz. */
    .nav-rail.collapsed .nav-group {
        font-size: 0; padding: 9px 0 8px; margin: 0 6px;
        border-top: 1px solid var(--line);
    }
    .nav-rail.collapsed .nav-group:first-child { border-top: 0; padding-top: 0; }
    .nav-rail.collapsed .nav-badge { display: none; }
    .nav-rail.collapsed .nav-item { justify-content: center; padding: 10px 0; }
    .nav-rail.collapsed .nav-item > span { display: none; }
    .nav-rail.collapsed .nav-item.active::before { left: -8px; }

    .nav-rail.collapsed .nav-collapse { margin: 0 8px 10px; padding: 8px 0; justify-content: center; }
    .nav-rail.collapsed .nav-collapse > span { display: none; }
    .nav-rail.collapsed .nav-collapse svg { transform: rotate(180deg); }
    .nav-rail.collapsed .nav-foot { display: none; }
}
@media (prefers-reduced-motion: reduce) {
    .app-shell, .nav-rail, .nav-collapse svg { transition: none; }
}

.main-col { display: flex; flex-direction: column; min-width: 0; }
.topbar { position: sticky; top: 0; z-index: 15; display: flex; align-items: center; justify-content: space-between; gap: 16px; height: 58px; padding: 0 22px; background: color-mix(in srgb, var(--bg) 82%, transparent); backdrop-filter: blur(10px); border-bottom: 1px solid var(--line); }
.topbar .left { display: flex; align-items: center; gap: 16px; min-width: 0; }
.topbar .right { display: flex; align-items: center; gap: 12px; }
.page-title { font-family: var(--font-ui); font-size: 15px; font-weight: 600; }
.content { padding: 22px; flex: 1; }

/* ---------- Schmale Fenster: die Rail wird zum Drawer ----------
   Unter 900px bleibt neben 232px Rail zu wenig für Tabellen und Diagramme. Die Rail
   verlässt deshalb das Raster (position: fixed) und fährt nur auf Anforderung über
   den Inhalt; ausgelöst wird das vom Menü-Knopf in der Topbar, geschlossen durch
   Klick auf den Scrim oder durch den Seitenwechsel (siehe MainLayout).
   Das gilt für die Fensterbreite, nicht für das Gerät: ein schmal gezogenes Fenster
   auf dem Desktop bekommt genau dieselbe Darstellung. */
@media (max-width: 900px) {
    /* Einspaltig – auch wenn der Benutzer die Rail auf dem Desktop eingeklappt hat. */
    .app-shell,
    .app-shell.nav-collapsed { grid-template-columns: 1fr; }

    .nav-rail,
    .nav-rail.collapsed {
        position: fixed; top: 0; left: 0; width: min(280px, 84vw);
        transform: translateX(-100%); transition: transform .18s ease;
        box-shadow: var(--shadow);
    }
    .nav-rail.open { transform: none; }

    .nav-scrim { display: block; }
    .nav-toggle { display: inline-flex; }
    .nav-narrow { display: none; }

    /* Was auf schmalen Fenstern nur Platz kostet: der Name neben dem Avatar, der
       Theme-Knopf (steht auch im Benutzermenü) und das Einklappen der Rail – im
       Drawer gibt es nichts einzuklappen. */
    .hide-sm { display: none; }

    .topbar { padding: 0 14px; gap: 10px; }
    .topbar .left { gap: 10px; }
    .topbar .right { gap: 8px; }
    .content { padding: 14px; }
}

/* Sehr schmal: der Seitentitel weicht, damit Statusanzeige und Bereichswahl bleiben. */
@media (max-width: 520px) {
    .page-title { display: none; }
    .content { padding: 12px 10px; }
}

/* User menu */
.usermenu { position: relative; }
.user-btn { display: flex; align-items: center; gap: 10px; padding: 5px 6px 5px 12px; border-radius: 20px; background: var(--panel-raised); border: 1px solid var(--line); cursor: pointer; color: var(--text); }
.avatar { width: 28px; height: 28px; border-radius: 50%; background: var(--accent); color: var(--on-accent); display: grid; place-items: center; font-family: var(--font-ui); font-weight: 700; font-size: 12px; flex: none; overflow: hidden; }
/* Das Bild füllt den Kreis und wird dabei nicht verzerrt: der Zuschnitt auf ein Quadrat
   passiert zwar schon beim Hochladen, ältere Einträge oder ein anderer Weg dorthin
   dürfen die Anzeige aber nicht in die Länge ziehen. */
.avatar-img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Auf der Profilseite steht der Kreis groß neben dem Namen und ist zugleich die Vorschau
   des gewählten Bildes – ein feiner Rand trennt ihn dort vom Hintergrund. */
.avatar-lg { border: 1px solid var(--line-strong); }
.menu-pop { position: absolute; right: 0; top: calc(100% + 8px); min-width: 220px; background: var(--panel); border: 1px solid var(--line-strong); border-radius: var(--radius); box-shadow: var(--shadow); padding: 8px; z-index: 40; }
.menu-pop .mi { display: flex; align-items: center; gap: 10px; padding: 9px 10px; border-radius: var(--radius-sm); cursor: pointer; font-size: 13px; color: var(--text); }
.menu-pop .mi:hover { background: var(--panel-raised); }
/* Die Zeichen im Benutzermenü sprechen dieselbe Sprache wie die der Navigation:
   Lucide-Striche in der Textfarbe, 16px. Vorher standen hier Emoji – bunte Bildchen
   in einer Oberfläche, die sonst durchweg einfarbige Striche trägt, und jedes von
   ihnen mit der Schriftart des Systems gezeichnet: unter Windows anders als auf dem
   Telefon. Gedämpft, weil im Menü der Text die Aussage ist und das Zeichen nur die
   Zeile findet; beim Überfahren zieht es mit. */
.menu-pop .mi svg { width: 16px; height: 16px; flex: none; color: var(--text-dim); }
.menu-pop .mi:hover svg { color: var(--text); }
.menu-pop .sep { height: 1px; background: var(--line); margin: 6px 2px; }
/* Überschrift einer Gruppe im Benutzermenü – dieselbe Schrift wie die Gruppentitel in
   der Navigation links, damit „Verwaltung" nach dem Umzug wiedererkennbar bleibt. */
.menu-pop .menu-group {
    font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.16em; text-transform: uppercase;
    color: var(--text-faint); padding: 4px 10px 4px; user-select: none;
}

/* Dept switcher */
.dept-switch { position: relative; }

/* ---------- Grid helpers ---------- */
.grid { display: grid; gap: 16px; }
.stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px; }

/* ---------- Modal ---------- */
.overlay { position: fixed; inset: 0; background: rgba(4,7,11,0.62); backdrop-filter: blur(3px); z-index: 100; display: grid; place-items: center; padding: 20px; animation: fade .15s ease-out; }
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
.modal { width: 100%; max-width: 560px; max-height: 88vh; overflow: auto; background: var(--panel); border: 1px solid var(--line-strong); border-radius: var(--radius); box-shadow: var(--shadow); }
.modal.lg { max-width: 760px; }
/* Dialog mit Reitern: gleiche Höhe für jeden Reiter, damit Fußzeile und Laschen beim
   Wechseln nicht springen. Der Rumpf füllt den Platz, der Reiterrahmen läuft bis unten;
   was länger ist, rollt wie bisher im Dialog. */
.modal.fixed-h { height: min(760px, 88vh); display: flex; flex-direction: column; }
.modal.fixed-h > .modal-body { flex: 1 0 auto; display: flex; flex-direction: column; }
.modal.fixed-h > .modal-body > .cfg-tabpanel { flex: 1 0 auto; }
.modal-head { display: flex; align-items: center; justify-content: space-between; padding: 16px 20px; border-bottom: 1px solid var(--line); position: sticky; top: 0; background: var(--panel); }
.modal-body { padding: 20px; }
.modal-foot { display: flex; justify-content: flex-end; gap: 10px; padding: 16px 20px; border-top: 1px solid var(--line); position: sticky; bottom: 0; background: var(--panel); }

/* ---------- Slide-over (Widget-Katalog) ---------- */
.slideover { position: fixed; top: 0; right: 0; height: 100vh; height: 100dvh; width: 380px; max-width: 92vw; background: var(--panel); border-left: 1px solid var(--line-strong); box-shadow: -20px 0 50px -30px rgba(0,0,0,0.8); z-index: 90; display: flex; flex-direction: column; animation: slidein .2s ease-out; }
@keyframes slidein { from { transform: translateX(30px); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
.slideover .so-head { padding: 18px 20px; border-bottom: 1px solid var(--line); display: flex; justify-content: space-between; align-items: center; }
.slideover .so-body { padding: 16px; overflow: auto; display: flex; flex-direction: column; gap: 10px; }

/* Catalog item */
.cat-item { display: flex; gap: 12px; padding: 13px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel-raised); cursor: pointer; transition: border-color .12s, transform .05s; }
.cat-item:hover { border-color: var(--accent); }
.cat-item:active { transform: scale(0.99); }
.cat-item .ci-icon { width: 40px; height: 40px; border-radius: var(--radius-sm); background: var(--panel-inset); border: 1px solid var(--line); display: grid; place-items: center; color: var(--accent); flex: none; }
.cat-item .ci-icon svg { width: 20px; height: 20px; }
.cat-item h4 { font-size: 13.5px; margin-bottom: 2px; }
.cat-item p { font-size: 11.5px; color: var(--text-dim); margin: 0; }
.cat-group-label { margin: 8px 2px 2px; }

/* ---------- Toasts ---------- */
.toast-stack { position: fixed; bottom: 22px; right: 22px; display: flex; flex-direction: column; gap: 10px; z-index: 200; }
.toast { display: flex; align-items: center; gap: 12px; min-width: 260px; max-width: 380px; padding: 13px 16px; background: var(--panel); border: 1px solid var(--line-strong); border-left-width: 3px; border-radius: var(--radius-sm); box-shadow: var(--shadow); animation: slidein .2s ease-out; }
.toast.info    { border-left-color: var(--info); }
.toast.success { border-left-color: var(--ok); }
.toast.warning { border-left-color: var(--warn); }
.toast.error   { border-left-color: var(--fault); }
.toast .t-text { font-size: 13px; }

/* ---------- Dashboard: Kopfleiste ---------- */
/* Ersetzt die frühere .section-head des Dashboards. Die Knöpfe bleiben im kleinen
   Maßstab; neu ist allein, dass die Leiste den Bearbeiten-Modus benennt – vorher
   fehlte jeder Hinweis, dass man sich darin befindet. */
/* Die Leiste steht nur noch im Bearbeiten-Modus (siehe Dashboard.razor): Im
   Ruhezustand wäre sie ein leerer Streifen über dem Board, und die Höhe, die er
   kostet, fehlt dem Board unten. */
.dash-bar {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    margin-bottom: 14px; min-height: 38px;
    padding: 4px 8px; border-radius: var(--radius);
    background: color-mix(in srgb, var(--accent) 9%, var(--panel));
    border: 1px solid color-mix(in srgb, var(--accent) 42%, var(--line));
}
.dash-bar-badge {
    display: inline-flex; align-items: center; height: 24px; padding: 0 10px; flex: none;
    border-radius: 999px; background: var(--accent); color: var(--on-accent);
    font-family: var(--font-ui); font-size: 11px; font-weight: 700;
    letter-spacing: .06em; text-transform: uppercase;
}
.dash-bar-hint {
    font-size: 12px; color: var(--text-dim);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
@media (max-width: 900px) { .dash-bar-hint { display: none; } }

/* Der Einstieg ins Bearbeiten in der Kopfzeile. Als Geisterknopf und nicht als
   gefüllter: Er steht neben Bereichswahl und Benutzermenü und soll dort nicht lauter
   sein als die – gesucht wird er erst, wenn man etwas am Board ändern will. */
.dash-edit-btn { gap: 6px; flex: none; }

/* ---------- Dashboard Grid ---------- */
.dash-grid { display: grid; grid-template-columns: repeat(12, 1fr); grid-auto-rows: 92px; gap: 14px; }
/* Im Bearbeiten-Modus wird das Raster selbst sichtbar – ohne es ist nicht erkennbar,
   in welchen Einheiten Breite und Höhe überhaupt springen. */
.dash-grid.is-editing {
    background-image:
        repeating-linear-gradient(to right,
            color-mix(in srgb, var(--accent) 12%, transparent) 0 1px,
            transparent 1px calc((100% - 11 * 14px) / 12 + 14px));
    background-position: 0 0;
    border-radius: var(--radius);
}
/* Der Rasterplatz einer Kachel. Er kommt als Variablen aus dem Markup (siehe
   Dashboard.razor) und wird hier zu grid-column/grid-row zusammengesetzt – nicht
   umgekehrt im style-Attribut fertig hingeschrieben. Nur so kann die schmale
   Darstellung weiter unten die Platzierung überschreiben, ohne mit !important gegen
   ein style-Attribut anzutreten. */
.dash-grid > .widget {
    grid-column: var(--gx) / span var(--gw);
    grid-row: var(--gy) / span var(--gh);
}
/* Benannter Container: die Aktionsknöpfe im Kopf richten sich nach der Breite des
   Widgets, nicht nach der des Fensters. */
.widget { position: relative; container: widget / inline-size; }
/* Das Panel füllt seine Grid-Zelle und teilt sie selbst auf: der Kopf so hoch, wie sein
   Inhalt es verlangt, der Body bekommt den Rest. Vorher stand im Body ein
   height:calc(100% - 46px). 46px ist aber die Höhe eines Kopfes OHNE Kanal-Mikrolabel
   (21px Titelzeile + 24px Polsterung + 1px Trennlinie). Board- und Dashboard-Panels
   setzen Channel immer – das sind 15px Zeile plus 3px Abstand mehr, macht 64px. Der
   Body war also 18px höher als seine Zelle und ragte über den Panelrand hinweg in das
   Panel darunter: genau die beobachtete Überlappung. Ein umbrechender Kopf oder die
   Aktionsknöpfe des Bearbeiten-Modus vergrößerten den Überstand zusätzlich. */
.widget > .panel { height: 100%; display: flex; flex-direction: column; min-height: 0; }
.widget > .panel > .panel-head { flex: none; }
.widget > .panel > .panel-body { flex: 1; min-height: 0; overflow: auto; }

/* Der Kanal nennt Gerät und Signal. Mit den Namen des Lese-Payloads – etwa
   "vehicle.acceleratorPositionPercent" – ist er breiter als ein 3-Spalten-Panel.
   Umbrechen darf er trotzdem nicht: jede zusätzliche Kopfzeile nimmt der Anzeige
   Platz weg und rückt bei kleinen Panels den Messwert aus dem Bild. */
.widget > .panel > .panel-head .eyebrow,
.widget > .panel > .panel-head h3 {
    max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.widget.editing {
    outline: 1px dashed var(--line-strong); outline-offset: 2px; border-radius: var(--radius);
    cursor: grab;
    transition: outline-color .15s, opacity .15s, transform .15s, box-shadow .15s;
}
.widget.editing:hover { outline-color: color-mix(in srgb, var(--accent) 55%, var(--line-strong)); }
.widget.editing:active { cursor: grabbing; }

/* Das gegriffene Widget bleibt sichtbar, tritt aber zurück: der Blick soll bei der
   Ablagefläche sein, nicht bei der Quelle. */
.widget.dragging { opacity: .38; transform: scale(.985); outline-style: solid; }

/* Zielbereich. Der Rahmen liegt als eigene Ebene über dem Panel (::after), damit er
   das Layout nicht anfasst; die Kante zeigt an, auf welcher Seite eingefügt wird. */
.widget.drop-target::after {
    content: ''; position: absolute; inset: -3px; pointer-events: none; z-index: 6;
    border-radius: var(--radius);
    border: 2px dashed var(--accent);
    background: color-mix(in srgb, var(--accent) 10%, transparent);
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 14%, transparent),
                inset 0 0 22px color-mix(in srgb, var(--accent) 12%, transparent);
    animation: drop-pulse 1.1s ease-in-out infinite;
}
/* Einfügemarke: fette Kante an der Seite, an der das Widget landet. */
.widget.drop-target::before {
    content: ''; position: absolute; top: -3px; bottom: -3px; width: 4px; z-index: 7;
    pointer-events: none; border-radius: 3px;
    background: var(--accent);
    box-shadow: 0 0 10px color-mix(in srgb, var(--accent) 70%, transparent);
}
.widget.drop-target.drop-before::before { left: -7px; }
.widget.drop-target.drop-after::before { right: -7px; }
@keyframes drop-pulse {
    0%, 100% { border-color: var(--accent); opacity: .95; }
    50% { border-color: color-mix(in srgb, var(--accent) 60%, transparent); opacity: .75; }
}
@media (prefers-reduced-motion: reduce) { .widget.drop-target::after { animation: none; } }

/* Während eines Ziehvorgangs treten die unbeteiligten Widgets leicht zurück, damit
   Quelle und Ziel die einzigen kräftigen Elemente im Raster sind. */
.dash-grid.is-dragging .widget:not(.dragging):not(.drop-target) { opacity: .72; }

/* Ablagefläche hinter dem letzten Widget (nur während des Ziehens im DOM). */
.dash-endzone {
    grid-column: span 12; min-height: 62px;
    display: grid; place-items: center;
    border: 2px dashed color-mix(in srgb, var(--accent) 45%, var(--line-strong));
    border-radius: var(--radius);
    background: color-mix(in srgb, var(--accent) 4%, transparent);
    color: var(--text-faint); font-size: 12px;
    transition: background .15s, border-color .15s, color .15s;
}
.dash-endzone.over {
    border-color: var(--accent); color: var(--accent);
    background: color-mix(in srgb, var(--accent) 14%, transparent);
    box-shadow: inset 0 0 26px color-mix(in srgb, var(--accent) 12%, transparent);
}

/* Aktionsknöpfe im Widget-Kopf (Konfigurieren / Entfernen).
   Vorher: .btn-ghost .btn-icon .btn-sm – randlos, transparent, 30px, mit einem
   Schrift-Glyph als Symbol. Im Panelkopf neben Kanal und Titel war davon kaum etwas
   zu sehen, obwohl Konfigurieren die zentrale Aktion des Bearbeiten-Modus ist.
   Jetzt 34px mit eigenem Grund und Rahmen; das Zahnrad trägt zusätzlich Akzentfarbe
   und blendet auf breiten Widgets seine Beschriftung ein. */
.widget .w-act {
    display: inline-flex; align-items: center; justify-content: center; gap: 7px;
    height: 34px; min-width: 34px; padding: 0 8px; flex: none;
    border-radius: var(--radius-sm);
    border: 1px solid var(--line-strong); background: var(--panel-raised);
    color: var(--text); cursor: pointer;
    font-family: var(--font-ui); font-size: 12px; font-weight: 600;
    transition: background .14s, border-color .14s, color .14s, box-shadow .14s, transform .05s;
}
.widget .w-act svg { width: 17px; height: 17px; flex: none; }
.widget .w-act:active { transform: translateY(1px); }
.widget .w-act:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Konfigurieren: die eigentliche Aktion, deshalb in Akzentfarbe angelegt. */
.widget .w-act-cfg {
    color: var(--accent);
    border-color: color-mix(in srgb, var(--accent) 55%, var(--line-strong));
    background: color-mix(in srgb, var(--accent) 14%, var(--panel-raised));
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 12%, transparent);
}
.widget .w-act-cfg:hover {
    color: var(--on-accent); background: var(--accent); border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 26%, transparent);
}

/* Entfernen bleibt zurückhaltend und färbt sich erst beim Zeigen – ein Klick darauf
   ist nicht rückgängig zu machen, er soll nicht der auffälligste Knopf im Kopf sein. */
.widget .w-act-del { color: var(--text-dim); }
.widget .w-act-del:hover { color: #fff; background: var(--fault); border-color: var(--fault); }

/* Die Beschriftung erscheint nur, wenn das Widget breit genug ist; auf schmalen
   Kacheln würde sie den Titel aus dem Kopf drängen. */
.widget .w-act-label { display: none; }
@container widget (min-width: 380px) { .widget .w-act-label { display: inline; } }

/* Rastermaß im Kopf – Rückmeldung beim Ziehen der Kanten. */
.widget .w-size {
    font-family: var(--font-mono); font-size: 11px; color: var(--text-faint);
    padding: 0 4px; white-space: nowrap;
}
.widget.resizing .w-size { color: var(--accent); }

/* Griffe zum Ziehen der Größe. Sie liegen über dem Panelrand, sind erst im
   Bearbeiten-Modus im DOM und schlucken die Zeigergeste (touch-action), sonst würde
   der Browser daraus ein Scrollen oder ein natives Drag des Widgets machen. */
.widget .w-resize {
    position: absolute; z-index: 8; touch-action: none;
    background: transparent; transition: background .12s;
}
/* Die Griffe liegen zu zwei Dritteln außerhalb des Panels: innen würden sie im
   Bearbeiten-Modus die Bildlaufleiste des Panelinhalts verdecken. */
.widget .w-resize-e { top: 10px; bottom: 16px; right: -6px; width: 9px; cursor: ew-resize; }
.widget .w-resize-s { left: 10px; right: 16px; bottom: -6px; height: 9px; cursor: ns-resize; }
.widget .w-resize-se {
    right: -6px; bottom: -6px; width: 18px; height: 18px; cursor: nwse-resize;
    display: grid; place-items: center; color: var(--line-strong);
    border-radius: 4px;
}
.widget .w-resize-se svg { width: 10px; height: 10px; }
.widget.editing:hover .w-resize-e,
.widget.editing:hover .w-resize-s { background: color-mix(in srgb, var(--accent) 55%, transparent); border-radius: 4px; }
.widget.editing:hover .w-resize-se { color: var(--accent); background: color-mix(in srgb, var(--accent) 18%, transparent); }
.widget .w-resize:hover { background: var(--accent) !important; }
.widget .w-resize-se:hover { color: var(--on-accent) !important; }

/* Während des Ziehens: Widget hervorheben und die Karten-/Chart-Inhalte nicht mit
   Zeigerereignissen belasten. */
.widget.resizing { outline: 2px solid var(--accent); outline-offset: 2px; z-index: 9; }
.widget.resizing .panel { box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 16%, transparent); }
body.dash-resizing { cursor: nwse-resize; user-select: none; }
body.dash-resizing .widget .panel-body { pointer-events: none; }

.widget .w-drag { position: absolute; top: 8px; right: 8px; display: flex; gap: 4px; opacity: 0; transition: opacity .12s; z-index: 5; }
.widget.editing .w-drag { opacity: 1; }
.w-empty { display: grid; place-items: center; height: 100%; color: var(--text-faint); text-align: center; font-size: 12px; }

/* Der Hinweis, dass die Kacheln gestapelt stehen – sichtbar nur dort, wo das gilt
   (die Regel dazu steht in der Abfrage darunter). Vor der Abfrage und nicht hinter ihr:
   Bei gleicher Spezifität gewinnt die spätere Regel, und ein display:none dahinter
   hätte den Hinweis auch im schmalen Fenster verschluckt. */
.dash-bar-note { display: none; font-size: 11.5px; line-height: 1.4; color: var(--text-dim); flex-basis: 100%; }

/* ---------- Dashboard im schmalen Fenster ----------
   Unter 700px stehen die Kacheln untereinander, jede über die ganze Breite.

   Der Grund ist das Raster selbst: Zwölf Spalten auf 360 Pixel sind Spalten von
   25 Pixeln, und eine Kachel, die am Bildschirm drei davon belegt, ist auf dem Telefon
   einen Daumen breit – mit einem Diagramm darin, das nichts mehr zeigt. Die gespeicherte
   Anordnung bleibt dabei unangetastet: Sie gehört zu dem Bildschirm, an dem sie gelegt
   wurde, und kommt dort unverändert wieder. Was die Kachel behalten darf, ist ihre Höhe
   (--gh) – sie sagt, wie viel eine Kachel zu zeigen hat, und das ändert sich mit der
   Fensterbreite nicht.

   700px und nicht 900px (wo die Navigation zum Drawer wird): Zwischen beiden Maßen ist
   das Raster noch benutzbar – 60 Pixel je Spalte –, und wer sein Board dort kennt, soll
   es nicht schon beim halben Bildschirm neu sortiert finden. */
@media (max-width: 700px) {
    /* Die Marke, an der js/dashgrid.js erkennt, dass die Kacheln gestapelt sind: Dort
       würde eine Ziehgeste in einem 12-Spalten-Raster rechnen, das gerade nicht gilt –
       und das Ergebnis in die gespeicherte Anordnung schreiben. */
    .dash-grid { --stacked: 1; grid-template-columns: 1fr; }

    /* Das Rasterbild im Bearbeiten-Modus zeigt zwölf Spalten. Hier gibt es eine, also
       zeigt es etwas, das nicht da ist. Mit .is-editing und nicht nur .dash-grid: Die
       Regel darüber trägt zwei Klassen und gewinnt sonst auch innerhalb dieser
       Abfrage – Spezifität schlägt Reihenfolge. */
    .dash-grid.is-editing { background-image: none; }

    .dash-grid > .widget {
        grid-column: 1 / -1;
        grid-row: auto / span var(--gh);
    }

    /* Verschieben und Größe ziehen sind hier abgeschaltet – also auch keine Griffe und
       kein Greifen-Zeiger. Hinzufügen, Einstellen und Entfernen bleiben. */
    .dash-grid .w-resize { display: none; }
    .widget.editing { cursor: default; }

    /* Die Leiste im Bearbeiten-Modus trägt vier Knöpfe; in einer Zeile würden sie
       zusammengequetscht. */
    .dash-bar { flex-wrap: wrap; }
    .dash-bar > .row { flex-wrap: wrap; }
    .dash-bar-note { display: block; }
}


/* Release-Notes-Widget: Versionen untereinander, je Eintrag ein Etikett für die Art
   der Änderung. Die Liste scrollt im Panel – sie wird mit jeder Version länger. */
.rn-list { display: flex; flex-direction: column; gap: 14px; height: 100%; overflow: auto; }
.rn-release { display: flex; flex-direction: column; gap: 5px; }
.rn-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.rn-version {
    font-family: var(--font-mono); font-size: 12px; font-weight: 700;
    color: var(--on-accent); background: var(--accent);
    padding: 1px 7px; border-radius: 20px; flex: none;
}
.rn-title { font-size: 12.5px; font-weight: 600; }
.rn-date { font-family: var(--font-mono); font-size: 10.5px; color: var(--text-faint); margin-left: auto; }
.rn-entry { display: flex; align-items: flex-start; gap: 8px; }
.rn-kind {
    flex: none; width: 62px; text-align: center;
    font-family: var(--font-mono); font-size: 9px; font-weight: 700;
    letter-spacing: 0.06em; text-transform: uppercase;
    padding: 2px 0; border-radius: 3px; margin-top: 1px;
}
.rn-kind-neu { background: color-mix(in srgb, var(--ok) 20%, transparent); color: var(--ok); }
.rn-kind-fix { background: color-mix(in srgb, var(--warn) 20%, transparent); color: var(--warn); }
.rn-kind-aenderung { background: var(--panel-inset); color: var(--text-dim); }
.rn-text { font-size: 12px; color: var(--text-dim); line-height: 1.45; }

/* ---------- Release Notes: kompakt und ausführlich ----------
   Die Hülle teilt die Höhe zwischen der Werkzeugzeile und der Liste auf. Ohne sie
   nähme die Liste (height:100%) die volle Höhe und schöbe den Umschalter aus der
   Kachel – im Dashboard-Widget ist das eine Fläche von vier Rasterzeilen. */
.rn-wrap { display: flex; flex-direction: column; gap: 8px; height: 100%; min-height: 0; }
.rn-wrap > .rn-list { flex: 1; min-height: 0; }

.rn-tools { display: flex; align-items: center; gap: 8px; flex: none; }
.rn-scope { font-size: 11px; color: var(--text-faint); font-family: var(--font-mono); }
.rn-tools .btn { margin-left: auto; }

/* Die Bilanz einer Version: die Zeile zwischen Kopf und Einträgen. Eingerückt auf die
   Fluchtlinie der Texte, damit die Etiketten daneben die Kante behalten. */
.rn-sum {
    font-size: 11px; color: var(--text-faint);
    font-family: var(--font-mono); margin: -1px 0 3px 70px;
}

/* Kompakt heißt: eine Zeile je Eintrag, abgeschnitten statt umgebrochen. Der volle
   Text steht im Tooltip – ein Eintrag, der zwei Zeilen bekommt, weil er länger ist,
   macht die Liste wieder zu dem Textblock, den die Kurzfassung ersetzen soll. */
.rn-list.compact .rn-release { gap: 3px; }
.rn-entry-short .rn-text {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}

/* Der Weg in die Tiefe steht dort, wo die Kürzung sichtbar wird: unter den Einträgen
   der Version. Als Text und nicht als Knopf – drei davon untereinander wären sonst
   die auffälligsten Elemente der Liste. */
.rn-more {
    align-self: flex-start; margin-top: 1px;
    background: none; border: none; padding: 0; cursor: pointer;
    font-family: var(--font-ui); font-size: 11px; color: var(--accent);
}
.rn-more:hover { text-decoration: underline; }
.rn-more-all { margin-top: 4px; font-size: 11.5px; }

/* Live value widget */
.lv { display: flex; flex-direction: column; justify-content: space-between; height: 100%; }
.lv .lv-val { display: flex; align-items: baseline; }
.lv .lv-meta { display: flex; align-items: center; justify-content: space-between; }

/* Gauge */
.gauge-wrap { display: grid; place-items: center; height: 100%; min-height: 0; overflow: hidden; }
/* Das SVG stand auf width:100%;height:auto – seine Höhe folgte damit allein der
   Breite und lief in einem flachen Panel unten heraus. max-height bindet es zusätzlich
   an die verfügbare Höhe; der Bogen skaliert über die viewBox mit. */
.gauge-wrap > svg { max-height: 100%; }

/* Status grid widget */
.veh-list { display: flex; flex-direction: column; gap: 6px; max-height: 100%; overflow: auto; }
.veh-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 10px; border-radius: var(--radius-sm); background: var(--panel-raised); }
.veh-row .vn { font-family: var(--font-mono); font-size: 12px; }

/* Bundle-Zeile (Diagnoseergebnis): links Fahrzeug + Bundle-Key, rechts Zeit + Dateizahl. */
.bundle-row {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    padding: 7px 10px; border-radius: var(--radius-sm); background: var(--panel-raised);
    border-left: 2px solid var(--accent-soft);
}
.bundle-row .vn { font-family: var(--font-ui); font-size: 12.5px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ---------- Login ---------- */
.login-wrap { min-height: 100vh; min-height: 100dvh; display: grid; grid-template-columns: 1.1fr 1fr; }
.login-brandside {
    position: relative; overflow: hidden;
    background-color: #081527;
    background-image: url("img/Background/bg_login_rweb3_clean.svg");
    background-size: cover; background-position: center;
    border-right: 1px solid var(--line);
    display: flex; flex-direction: column; justify-content: flex-start; padding: 48px;
}

/* Logo oben, Aussage in der Mitte, unten nichts. Die Kennzahlenzeile unten links
   (ISO 14229, Latenz, Dongles) ist entfallen – drei Zahlen ohne Zusammenhang beantworten
   keine Frage, die sich vor dem Anmelden stellt. */
.login-brandside .lb-hero { margin-top: auto; margin-bottom: auto; }
/* Lesbarkeits-Verlauf über dem Hintergrundbild */
.login-brandside::before {
    content: ''; position: absolute; inset: 0; pointer-events: none;
    background: linear-gradient(160deg, rgba(6,15,30,0.35) 0%, rgba(6,15,30,0.72) 70%, rgba(6,15,30,0.9) 100%);
}
.login-brandside > * { position: relative; z-index: 2; }
.login-brandside .lb-top { display: flex; align-items: center; gap: 14px; }
.login-brandside .lb-logo { height: 34px; width: auto; display: block; }

/* Der Trennstrich zwischen Herkunft und Produkt. Ohne ihn stehen zwei Marken
   nebeneinander und man weiß nicht, welche die Firma ist. */
.login-brandside .lb-div {
    width: 1px; height: 28px; flex: none;
    background: rgba(140,175,220,0.34);
}

/* ---------- Der Produktname ----------
   Das R in der Hausfarbe, der Rest in Weiß – so steht die Marke überall im Haus, und so
   soll sie auch hier stehen. Als eigene Klasse und nicht als Sonderfall der Anmeldeseite:
   Der nächste Ort, an dem der Name groß steht, soll ihn nicht neu erfinden. */
.wordmark {
    font-family: var(--font-ui); font-weight: 700; font-size: 20px;
    letter-spacing: -0.01em; color: #F1F6FC; line-height: 1;
}

.wordmark .wm-r { color: var(--brand-400); }
.login-brandside .lb-hero { max-width: 460px; }
.login-brandside .lb-hero h1 { font-size: clamp(28px, 3.4vw, 46px); line-height: 1.05; letter-spacing: -0.02em; color: #F1F6FC; }
.login-brandside .lb-hero h1 em { color: var(--brand-400); font-style: normal; }
.login-brandside .lb-hero p { color: #A9BAD0; margin-top: 16px; font-size: 15px; max-width: 420px; }
.login-formside { display: grid; place-items: center; padding: 40px; }
.login-card { width: 100%; max-width: 380px; }
.login-card .lc-head { margin-bottom: 26px; }
.login-card h2 { font-size: 22px; }
.login-card .lc-head p { color: var(--text-dim); margin-top: 6px; }
.login-err { display: flex; align-items: center; gap: 10px; padding: 11px 13px; border: 1px solid var(--fault); background: rgba(240,72,62,0.08); border-radius: var(--radius-sm); color: #ffb4ae; font-size: 13px; margin-top: 4px; }
/* Dieselbe Zeile in Gelb – für Hinweise, die kein Fehler sind (ein vermerkter
   Passwortwechsel, ein fehlender Benutzerdatensatz). In Rot liest sich ein Hinweis wie
   eine abgewiesene Eingabe, und wer die Farbe zweimal für Verschiedenes benutzt,
   verliert sie für den echten Fehler daneben. */
.login-err--warn { border-color: var(--warn); background: color-mix(in srgb, var(--warn) 10%, transparent); color: var(--warn); }

/* ---------- Der lebende Hintergrund der Markenseite ----------
   Zwei langsam driftende Lichter und eine Messkurve, die durchläuft. Alles CSS und SVG:
   keine Bibliothek, kein Canvas, keine Schleife in JavaScript. Auf einer Anmeldeseite
   soll nichts rechnen, was noch nicht angemeldet ist.

   Warum eine Kurve und kein Partikelfeld: Sie ist das, was dieses Produkt tut. Ein
   Partikelfeld wäre Dekoration von der Stange.

   Die Deckkraft ist bewusst niedrig. Wer sich anmeldet, soll das Eingabefeld finden und
   nicht das Bild ansehen – eine Anmeldeseite, die man erst betrachtet, hat ihre Aufgabe
   verfehlt. */
.login-brandside .lb-live {
    position: absolute; inset: 0; z-index: 1;
    pointer-events: none; overflow: hidden;
}

.lb-orb {
    position: absolute; border-radius: 50%;
    filter: blur(70px);
}

.lb-orb-a {
    width: 46vmin; height: 46vmin; left: -12%; top: 6%;
    background: radial-gradient(circle, rgba(74,131,208,0.50), transparent 68%);
    animation: lb-drift-a 28s ease-in-out infinite;
}

.lb-orb-b {
    width: 38vmin; height: 38vmin; right: -10%; bottom: 2%;
    background: radial-gradient(circle, rgba(46,170,200,0.34), transparent 68%);
    animation: lb-drift-b 37s ease-in-out infinite;
}

/* Unterschiedliche Dauern und kein gemeinsamer Takt: Zwei Lichter, die im Gleichschritt
   wandern, lesen sich als Mechanik. */
@keyframes lb-drift-a {
    0%, 100% { transform: translate(0, 0) scale(1); }
    50%      { transform: translate(7%, 9%) scale(1.12); }
}

@keyframes lb-drift-b {
    0%, 100% { transform: translate(0, 0) scale(1); }
    50%      { transform: translate(-9%, -7%) scale(1.08); }
}

/* ---------- Der Hintergrund: eine Leiterplatte, auf der Strom läuft ----------
   Hier standen nacheinander ein Messdiagramm und ein nachgebautes Live-Board. Beide
   wollten gesehen werden, und ein Hintergrund, den man ansieht, ist keiner: Die
   Überschrift lag darauf, und damit war beides halb zu erkennen.

   Was ein Hintergrund kann, ist Bewegung ohne Inhalt. Deshalb jetzt Leiterbahnen, über
   die in großen Abständen ein heller Abschnitt läuft. Man liest ihn nicht, man bemerkt
   ihn – und die Messtechnik am Fahrzeug ist tatsächlich ein kleiner Rechner, das Bild
   behauptet also nichts Falsches.

   Alles CSS und SVG. Auf einer Anmeldeseite soll nichts rechnen, was noch nicht
   angemeldet ist. */
.cx {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    display: block;
}

/* Die ruhenden Bahnen. Sie sind dunkel und stehen nur da, damit der helle Abschnitt
   etwas hat, worauf er laufen kann – ein Licht ohne Leitung wäre eine Sternschnuppe. */
.cx-traces {
    fill: none; stroke: #1B3E6B; stroke-width: 1.6;
    stroke-linecap: round; stroke-linejoin: round;
    opacity: 0.55;
}

/* Anschlussfelder, Durchkontaktierungen, Bauteile. Der dunkle Kern im Anschlussfeld ist
   das Loch: Ohne ihn wären es Punkte, mit ihm sind es Pads. */
.cx-pad  { fill: #0A182B; stroke: #26547F; stroke-width: 1.4; opacity: 0.75; }
.cx-via  { fill: #26547F; opacity: 0.5; }
.cx-chip { fill: rgba(13,30,53,0.72); stroke: #244E7C; stroke-width: 1.4; opacity: 0.75; }
.cx-pin  { stroke: #2C5C8C; stroke-width: 2; stroke-linecap: round; opacity: 0.7; }
.cx-key  { fill: #2C5C8C; opacity: 0.55; }

/* ---------- Der Strom ----------
   Ein kurzes Stück Licht, das über die Bahn wandert: stroke-dasharray setzt einen
   Strich von 54 und eine Lücke von 1400, stroke-dashoffset schiebt das Muster über den
   Zug. Die Lücke ist länger als jede Bahn – deshalb läuft je Durchgang genau ein
   Abschnitt hindurch, und danach ist es kurz still. Liefe ununterbrochen Licht, wäre es
   ein Förderband und kein Stromstoß.

   Zwei Lagen mit denselben Zeiten: eine breite, fast durchsichtige als Schein und eine
   schmale, helle darüber. Ein Weichzeichner täte dasselbe, kostet aber bei fünfzehn
   bewegten Zügen spürbar mehr. */
.cx-glow use, .cx-pulse use {
    fill: none;
    stroke-linecap: round; stroke-linejoin: round;
    stroke-dasharray: 54 1400;
    animation-name: cx-run;
    animation-timing-function: linear;
    animation-iteration-count: infinite;
}

.cx-glow use  { stroke: #4E9BE8; stroke-width: 8;   opacity: 0.16; }
.cx-pulse use { stroke: #A6D8FF; stroke-width: 2.2; opacity: 0.85; }

/* Die Bahnen in der Bildmitte gibt es nur dort, wo keine Überschrift darüber steht –
   siehe die Regel für schmale Fenster weiter unten. */
.cx-inner { display: none; }

@keyframes cx-run {
    from { stroke-dashoffset: 1454; }
    to   { stroke-dashoffset: 0; }
}

/* Je Bahn eine eigene Dauer und ein eigener Versatz. Der Versatz ist negativ: Damit
   beginnt der Lauf nicht bei null, sondern mittendrin – sonst starteten beim Aufbau der
   Seite fünfzehn Lichter gleichzeitig am selben Punkt ihrer Bahn. */
.cx-t1 { animation-duration: 10.7s; animation-delay: -2.9s; }
.cx-t2 { animation-duration: 12.4s; animation-delay: -5.8s; }
.cx-t3 { animation-duration: 14.1s; animation-delay: -8.7s; }
.cx-t4 { animation-duration: 9.3s; animation-delay: -2.3s; }
.cx-t5 { animation-duration: 11.0s; animation-delay: -3.5s; }
.cx-t6 { animation-duration: 12.7s; animation-delay: -4.7s; }
.cx-t7 { animation-duration: 14.4s; animation-delay: -5.9s; }
.cx-t8 { animation-duration: 9.6s; animation-delay: -4.0s; }
.cx-t9 { animation-duration: 11.3s; animation-delay: -3.5s; }
.cx-t10 { animation-duration: 13.0s; animation-delay: -3.0s; }
.cx-t11 { animation-duration: 14.7s; animation-delay: -2.5s; }
.cx-t12 { animation-duration: 9.9s; animation-delay: -5.1s; }
.cx-t13 { animation-duration: 11.6s; animation-delay: -2.9s; }
.cx-t14 { animation-duration: 13.3s; animation-delay: -0.7s; }
.cx-t15 { animation-duration: 15.0s; animation-delay: -13.5s; }
.cx-t16 { animation-duration: 10.2s; animation-delay: -5.6s; }
.cx-t17 { animation-duration: 11.9s; animation-delay: -1.7s; }
.cx-t18 { animation-duration: 13.6s; animation-delay: -11.4s; }
.cx-t19 { animation-duration: 15.3s; animation-delay: -9.2s; }

/* ---------- Leiterplatte, ruhige Fassung (Hauptseite) ---------- */
/* Dieselbe Zeichnung wie auf der Anmeldeseite (CircuitBackdrop), aber nicht dasselbe Bild.
   Drei Unterschiede, und jeder hat einen Grund:

     1  DIE FARBEN kommen aus dem Theme und nicht aus der Zeichnung. Auf der Anmeldeseite
        liegt der Hintergrund auf einer immer dunklen Flaeche und darf seine eigenen
        Blautoene haben. In der Anwendung liegt er unter acht Themes, von denen vier hell
        sind - dieselben Blautoene waeren dort ein dunkler Fleck auf hellem Grund. Also
        --line-strong fuer die ruhenden Bahnen und --accent fuer den Strom: Damit ist er in
        jedem Theme richtig, ohne dass ihn jemand achtmal einstellt.

     2  ER LIEGT FEST AM FENSTER und nicht im Textfluss. Ein Hintergrund, der mitscrollt,
        wird zum Inhalt: Man verfolgt ihn, statt ihn zu bemerken.

     3  ER IST DEUTLICH LEISER UND LANGSAMER. Auf der Anmeldeseite hat man Zeit; hier liest
        man Zahlen, die sich im Sekundentakt aendern. Was daneben blinkt, konkurriert mit
        ihnen - und verliert nicht der Hintergrund, sondern die Zahl. */
.cx-field { position: absolute; inset: 0; pointer-events: none; overflow: hidden; }

.cx-quiet {
    /* fixed und z-index 0: hinter jedem Panel (die haben eigene Hintergruende), aber ueber
       dem Raster des Koerpers. Die Rail (20) und die Kopfzeile liegen ohnehin darueber. */
    position: fixed; inset: 0; z-index: 0;
}

/* Die ruhenden Bahnen: nur noch angedeutet. Auf hellem Grund traegt --line-strong; auf
   dunklem ist es dieselbe Linie, die auch die Panelraender zeichnet - der Hintergrund
   gehoert damit sichtbar zur Oberflaeche und nicht auf sie. */
.cx-quiet .cx-traces { stroke: var(--line-strong); opacity: 0.4; stroke-width: 1.4; }

.cx-quiet .cx-pad  { fill: var(--bg); stroke: var(--line-strong); opacity: 0.45; }
.cx-quiet .cx-via  { fill: var(--line-strong); opacity: 0.35; }
.cx-quiet .cx-chip { fill: var(--panel-inset); stroke: var(--line-strong); opacity: 0.38; }
.cx-quiet .cx-pin  { stroke: var(--line-strong); opacity: 0.4; }
.cx-quiet .cx-key  { fill: var(--line-strong); opacity: 0.3; }

/* Der Strom in der Akzentfarbe des Themes. Schmaler und blasser als auf der Anmeldeseite,
   der Schein ebenso: Er soll am Rand des Blickfelds auffallen und nicht in seiner Mitte. */
.cx-quiet .cx-glow use  { stroke: var(--accent); stroke-width: 6;   opacity: 0.07; }
.cx-quiet .cx-pulse use { stroke: var(--accent); stroke-width: 1.6; opacity: 0.30; }

/* Laenger als jede Bahn: Nach jedem Durchgang ist es lange still. Auf der Anmeldeseite
   laufen fuenfzehn Lichter in zehn bis fuenfzehn Sekunden durch, hier braucht dasselbe
   Licht das Zweieinhalbfache - aus Betrieb wird damit Atmen. */
.cx-quiet .cx-glow use, .cx-quiet .cx-pulse use { animation-duration: 34s; }

/* Je Bahn trotzdem verschieden: Fuenfzehn Lichter mit derselben Dauer laufen nach dem
   ersten Durchgang im Gleichschritt, und Gleichschritt liest sich als Mechanik. Die
   Faktoren sind Primzahlverhaeltnisse - so treffen sich die Bahnen erst nach Minuten
   wieder. */
.cx-quiet .cx-t1,  .cx-quiet .cx-t6,  .cx-quiet .cx-t11 { animation-duration: 29s; }
.cx-quiet .cx-t2,  .cx-quiet .cx-t7,  .cx-quiet .cx-t12 { animation-duration: 37s; }
.cx-quiet .cx-t3,  .cx-quiet .cx-t8,  .cx-quiet .cx-t13 { animation-duration: 43s; }
.cx-quiet .cx-t4,  .cx-quiet .cx-t9,  .cx-quiet .cx-t14 { animation-duration: 31s; }
.cx-quiet .cx-t5,  .cx-quiet .cx-t10, .cx-quiet .cx-t15 { animation-duration: 41s; }

/* Der Inhalt liegt ueber dem Hintergrund. Als Stapelkontext an der Spalte und nicht als
   Regel je Kind: Vorher stand hier ".content > *:not(.cx-field)", solange der Hintergrund
   in der Dashboard-Seite lag. Seit er in der Huelle haengt (MainLayout), gilt es fuer jede
   Seite - und eine Regel an der Spalte trifft auch Inhalte, die kein direktes Kind von
   .content sind. */
.main-col { position: relative; z-index: 1; }

/* Vollbild (Diagramm .ts-fs, Live-Board .lm-fs, Antwortzeiten .ct-fs): Die Auflagen liegen
   in dieser Spalte und kommen mit ihrem z-index 60 nicht über deren Stapelkontext hinaus –
   die Navigation (20) malte darüber und verdeckte links Titel, Legende und Achse. Solange
   eine offen ist, steht die Spalte deshalb über der Navigation. Unter den Dialogen der
   Hülle (100) bleibt sie trotzdem. */
.main-col:has(.ts-fs, .lm-fs, .ct-fs) { z-index: 25; }

/* Ro.Ki im Vollbild. Schwebend liegt er ohnehin darüber (z-index 90). Angedockt ist er
   eine Spalte des Rasters mit z-index 1 – das Vollbild bedeckte mit inset:0 auch diese
   Spalte, und das geöffnete Gespräch lag unsichtbar dahinter. Das Vollbild endet deshalb
   vor der Andockspalte, in voller oder eingeklappter Breite; der Assistent bleibt neben
   dem Diagramm, über das man ihn gerade fragt. */
@media (min-width: 901px) {
    .app-shell.assist-docked :is(.ts-chart-host.ts-fs, .lm-stage.lm-fs, .ct-plot-host.ct-fs) {
        right: var(--asx-dock-w, 380px);
    }
    .app-shell.assist-docked.assist-min :is(.ts-chart-host.ts-fs, .lm-stage.lm-fs, .ct-plot-host.ct-fs) {
        right: min(var(--asx-dock-w, 380px), 252px);
    }
}
/* Schmal liegt das angedockte Fenster als Blatt über der Seite (siehe „Schmale Fenster“
   bei Ro.Ki) – dort muss es über das Vollbild, nicht daneben. */
.app-shell:has(.ts-fs, .lm-fs, .ct-fs) .asx-panel.is-docked { z-index: 90; }

/* Auf hellem Grund ist der Strom sonst zu kraeftig: Eine Akzentfarbe auf Weiss traegt
   weiter als auf Dunkelblau, und "subtil" ist der ganze Zweck. */
:root[data-theme="rosi-light"] .cx-quiet .cx-pulse use,
:root[data-theme="paper"] .cx-quiet .cx-pulse use,
:root[data-theme="nordic"] .cx-quiet .cx-pulse use,
:root[data-theme="solar"] .cx-quiet .cx-pulse use { opacity: 0.20; }

:root[data-theme="rosi-light"] .cx-quiet .cx-traces,
:root[data-theme="paper"] .cx-quiet .cx-traces,
:root[data-theme="nordic"] .cx-quiet .cx-traces,
:root[data-theme="solar"] .cx-quiet .cx-traces { opacity: 0.5; }

/* Auf schmalen Fenstern ganz weg. Dort ist der Inhalt einspaltig und nimmt die ganze
   Breite - der Hintergrund liegt dann nicht daneben, sondern darunter, und ein Telefon
   zahlt fuenfzehn laufende Animationen mit seiner Laufzeit. */
@media (max-width: 900px) {
    .cx-quiet { display: none; }
}
.cx-t20 { animation-duration: 10.5s; animation-delay: -5.5s; }
.cx-t21 { animation-duration: 12.2s; animation-delay: -12.1s; }
.cx-t22 { animation-duration: 13.9s; animation-delay: -8.2s; }

/* ---------- Die Kernaussage ---------- */
.login-brandside .lb-claim {
    color: #CFE0F4; font-size: 13.5px; margin-top: 10px; max-width: 420px;
    border-left: 2px solid var(--brand-400); padding-left: 11px;
}

.login-brandside .lb-for { margin-top: 24px; max-width: 470px; }

.login-brandside .lb-for-head {
    display: block; font-family: var(--font-mono);
    font-size: 9px; letter-spacing: 0.16em; color: #8A9BB4; margin-bottom: 9px;
}

.login-brandside .lb-for-list { display: flex; flex-wrap: wrap; gap: 7px; }

.login-brandside .lb-for-list span {
    padding: 4px 10px; border-radius: 999px;
    border: 1px solid rgba(122,164,220,0.32);
    background: rgba(24,52,92,0.42);
    color: #BFD4EC; font-size: 11.5px; white-space: nowrap;
}

/* ---------- Passwort vergessen ---------- */
.login-forgot { margin-top: 18px; }

.login-link {
    background: none; border: 0; padding: 0;
    color: var(--text-dim); font-size: 12.5px; cursor: pointer;
    text-decoration: underline; text-underline-offset: 3px;
}

.login-link:hover:not(:disabled) { color: var(--accent); }
.login-link:disabled { opacity: .5; cursor: default; }

.login-forgot-box {
    display: flex; flex-direction: column; gap: 12px;
    padding: 15px; border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--panel-raised);
}

.login-forgot-head { font-size: 13.5px; font-weight: 600; }
.login-forgot-sub { font-size: 11.5px; line-height: 1.55; color: var(--text-dim); }

.login-ok {
    display: flex; align-items: center; gap: 10px;
    padding: 10px 12px; border-radius: var(--radius-sm);
    border: 1px solid var(--ok);
    background: color-mix(in srgb, var(--ok) 10%, transparent);
    color: var(--ok); font-size: 12.5px;
}

/* ---------- Der rechtliche Fuß ----------
   Er steht unter der Karte und nicht am Seitenrand: Auf einem schmalen Fenster ist die
   Karte die ganze Seite, und ein Fuß am Seitenrand stünde dort unerreichbar weit unten. */
.login-legal {
    display: flex; align-items: center; justify-content: center; flex-wrap: wrap;
    gap: 10px; margin-top: 26px;
}

.login-legal a {
    color: var(--text-dim); font-size: 11.5px; text-decoration: none;
}

.login-legal a:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; }

.login-legal-dot {
    width: 3px; height: 3px; border-radius: 50%;
    background: var(--text-faint); flex: none;
}

.login-copy {
    text-align: center; margin-top: 8px;
    font-size: 10.5px; color: var(--text-faint);
}


/* Schmale Fenster: aus den zwei Spalten wird ein Markenstreifen oben und darunter
   das mittig stehende Formular. Der Werbetext der Markenseite entfällt dabei – er
   erklärt das Produkt, wer hier anmeldet kennt es bereits, und nebeneinander bliebe
   für die Eingabefelder zu wenig Breite. */
@media (max-width: 900px) {
    /* Aus dem Nebeneinander wird ein Übereinander: Die Markenseite ist keine Spalte mehr,
       sondern die Unterlage der ganzen Seite. Vorher stand hier ein Streifen mit dem Logo
       und darunter eine weiße Fläche mit Feldern – auf dem Telefon war vom Produkt nichts
       mehr zu sehen, und genau das Telefon zeigt die Seite am häufigsten. */
    .login-wrap { display: block; position: relative; }

    /* inset:0 misst die volle Höhe des Elternteils, nicht die des Fensters: Wird das
       Formular länger als der Schirm, wächst die Unterlage mit und die Platine hört
       nicht auf halber Strecke auf. */
    .login-brandside {
        position: absolute; inset: 0; z-index: 0;
        display: flex; flex-direction: column;
        align-items: center; justify-content: flex-start;
        padding: 26px 18px 0;
        border-right: 0; border-bottom: 0;
    }

    .login-brandside .lb-live { display: block; }
    .cx-inner { display: inline; }

    .login-brandside .lb-top { justify-content: center; }
    .login-brandside .lb-logo { height: 28px; }

    /* Von der Aussage bleibt der erste Satz. Das Logo allein sagt, von wem die Seite
       ist, aber nicht, wofür sie da ist – und auf dem Telefon ist dieser Kopf alles,
       was das Produkt zeigt. Der Rest des Textes bliebe ohnehin unter der Karte.

       Die Ränder werden im Ganzen gesetzt: Auf dem Großbild stehen dort zwei auto,
       die den Text mittig halten; in einer Spalte, die oben beginnt, schöben sie ihn
       nach unten. */
    .login-brandside .lb-hero {
        display: block; margin: 13px 0 0; max-width: 330px; text-align: center;
    }

    .login-brandside .lb-hero h1 {
        font-size: 17px; line-height: 1.32; letter-spacing: 0;
    }

    /* Der Umbruch gehört zur großen Überschrift, hier läuft der Satz in einer Zeile
       weiter und bricht dort, wo die Breite es verlangt. */
    .login-brandside .lb-hero h1 br { display: none; }

    .login-brandside .lb-hero p,
    .login-brandside .lb-claim,
    .login-brandside .lb-for { display: none; }

    .login-formside {
        position: relative; z-index: 2;
        min-height: 100vh; min-height: 100dvh;
        padding: 152px 18px 36px;
    }

    /* Das Formular bekommt eine eigene Fläche. Ohne sie stünden die Felder unmittelbar
       auf den Leiterbahnen: Der Strom liefe mitten durch die Eingabe, und lesbar wäre
       weder das eine noch das andere. Die Farben kommen aus dem Thema, nicht aus dieser
       Regel – sonst wäre die Karte in einem hellen Thema dunkel und in einem dunklen
       hell. */
    .login-card {
        background: var(--panel);
        border: 1px solid var(--line);
        border-radius: var(--radius);
        padding: 24px 20px 20px;
        box-shadow: 0 26px 64px rgba(3,10,22,0.55);
    }
}

/* Auf sehr schmalen Fenstern trägt der Kopf nur noch das Logo. */
@media (max-width: 480px) {
    .login-brandside { padding: 20px 14px 0; }
    .login-brandside .lb-logo { height: 24px; }
    .login-brandside .lb-hero h1 { font-size: 15.5px; }
    .login-formside { padding: 136px 12px 28px; }
    .login-card { padding: 20px 16px 16px; }
}

/* ---------- Toolbar / filters ---------- */
.toolbar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 16px; }
.toolbar .grow { flex: 1; }
.section-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 14px; }

/* ---------- Misc ---------- */
.route-loading { display: grid; place-items: center; min-height: 60vh; }
.spinner { width: 26px; height: 26px; border: 2.5px solid var(--line-strong); border-top-color: var(--accent); border-radius: 50%; animation: spin .7s linear infinite; display: inline-block; }
@keyframes spin { to { transform: rotate(360deg); } }
.empty-state { display: grid; place-items: center; gap: 10px; padding: 60px 20px; text-align: center; color: var(--text-dim); }
.empty-state .es-icon { color: var(--text-faint); }
.text-dim { color: var(--text-dim); }
.mono { font-family: var(--font-mono); }
.divider { height: 1px; background: var(--line); margin: 16px 0; }
.row { display: flex; align-items: center; gap: 10px; }
.col { display: flex; flex-direction: column; gap: 10px; }
.spread { display: flex; align-items: center; justify-content: space-between; gap: 12px; }

/* ---------- Formular-Raster (Modals) ---------- */
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }

/* Erklärung unter einem Eingabefeld – die Regel steht dort, wo getippt wird, und nicht
   erst in der Fehlermeldung danach. */
.field-hint { font-size: 11px; color: var(--text-faint); line-height: 1.45; }
.field-hint--warn { color: var(--warn); }

/* Aktionszeile am Fuß eines Panels. Das Panel selbst kennt keinen Fuß (sein Kopf trägt
   die Aktionen) – bei einem Formular gehören Speichern und Verwerfen aber unter die
   Felder, in Leserichtung hinter das, was sie betrifft. */
.panel-actions {
    display: flex; justify-content: flex-end; gap: 8px; flex-wrap: wrap;
    margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line);
}

/* ---------- Eigenes Profil ---------- */
/* Die Seite trägt die Höhe des Fensters: Kopf, Registerleiste und ein etwaiger Vermerk
   stehen fest, gerollt wird nur der Inhalt der offenen Karte. Vorher lagen alle
   Abschnitte in einer Bahn untereinander – auf 1366×768 gut die doppelte Fensterhöhe,
   und der Passwortwechsel begann mit Scrollen.
   Höhe = Viewport − Topbar(58) − .content-Padding oben+unten(2×22) = 102px. Gemessen,
   nicht geschätzt: ein zu kleiner Abzug erzeugt genau den Seiten-Scroll, den dieses
   Layout vermeidet. min-height niedrig – bei 1366×768 bleiben ~666px. */
.prof-layout {
    display: flex; flex-direction: column; gap: 12px;
    height: calc(100vh - 102px); min-height: 420px;
}
/* Der Kopf bringt seinen Abstand selbst mit; hier setzt ihn die Flex-Lücke. */
.prof-head { flex: none; margin-bottom: 0; }
.prof-head .prof-ident { gap: 14px; min-width: 0; }
.prof-head h1 { font-size: 20px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.prof-head .prof-badges { gap: 6px; flex: none; }

/* Registerleiste mit dem Hinweis daneben, was die offene Karte kann – derselbe Aufbau
   wie im ODX-Arbeitsbereich. */
.prof-bar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; flex: none; }
.prof-bar .tabs button { display: inline-flex; align-items: center; gap: 7px; }
.prof-hint { font-size: 11.5px; }
/* Punkt an der Karte „Sicherheit", solange ein Passwortwechsel vermerkt ist. */
.prof-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--warn); flex: none; }
/* Der Vermerk steht über den Karten und bleibt beim Blättern stehen; sein Knopf rechts. */
.prof-flag { flex: none; margin: 0; }
.prof-flag > button { margin-left: auto; flex: none; }

/* Nur dieser Bereich rollt. Rechts 4px Luft, sonst liegt die Rollleiste auf dem
   Panelrahmen. */
.prof-body { flex: 1; min-height: 0; overflow: auto; padding-right: 4px; }

/* Zwei Karten je Register, solange Platz ist – die Felder eines Abschnitts bleiben so
   zusammen sichtbar. Die Breite ist begrenzt: ein Formular über die ganze Breite eines
   Ultrawide hat Beschriftung und Eingabe eine Armlänge auseinander. */
.prof-grid {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 16px; align-items: start; max-width: 1180px;
}
@media (max-width: 1100px) {
    /* Untereinander passt der Inhalt nicht mehr ins Fenster. Die feste Höhe fällt
       deshalb weg: ein Rollbereich im Rollbereich ist schlechter als ein Seiten-Scroll. */
    .prof-layout { height: auto; min-height: 0; }
    .prof-body { overflow: visible; padding-right: 0; }
    .prof-grid { grid-template-columns: 1fr; }
}
/* Auf schmalen Fenstern stehen auch Vor- und Nachname untereinander. */
@media (max-width: 640px) {
    .prof-grid .form-grid { grid-template-columns: 1fr; }
}

/* Beschriftung mit Merkzeichen: „fest" an einem ausgegrauten Feld sagt auf einen Blick,
   was der Absatz darunter im Satz erklärt. Ohne das sieht ein gesperrtes Eingabefeld
   nach einem Fehler aus. */
.prof-grid .field > label { display: flex; align-items: center; gap: 8px; }
.prof-fixed {
    font-family: var(--font-mono); font-size: 9.5px; letter-spacing: 0.14em;
    text-transform: uppercase; color: var(--text-faint);
    border: 1px solid var(--line); border-radius: 3px; padding: 1px 5px;
}

/* Nachschlagewerte als Zeilenliste: Beschriftung links, Wert rechts, Haarlinie
   dazwischen. Als .col mit Lücke stünde dieselbe Angabe da, nur ohne die Führung, an
   der das Auge die Zeile bis zum Wert entlangläuft. */
.prof-rows { display: flex; flex-direction: column; }
.prof-rows > .spread { min-height: 34px; padding: 9px 0; border-top: 1px solid var(--line); }
.prof-rows > .spread:first-child { padding-top: 0; border-top: 0; }
.prof-rows > .spread:last-child { padding-bottom: 0; }
.prof-rows .k { font-size: 12.5px; color: var(--text-dim); }
.prof-empty { padding: 4px 0; color: var(--text-faint); font-size: 12.5px; }
/* Erklärender Absatz am Fuß einer Karte. */
.prof-note { margin: 12px 0 0; max-width: 70ch; }

/* Bild links, Erklärung und Knöpfe rechts – unter 480px untereinander, dort reicht die
   Breite neben einem 96er-Kreis für keinen Satz mehr. */
.avatar-edit { display: flex; align-items: flex-start; gap: 18px; flex-wrap: wrap; }
@media (max-width: 480px) {
    .avatar-edit { flex-direction: column; }
}
/* Das Dateifeld wird vom Knopf daneben ausgelöst und darf nicht selbst zu sehen sein.
   Nicht display:none: ein ausgeblendetes Feld nimmt in manchen Browsern keine
   Programmklicks an, und der Dialog ginge nicht auf. */
.avatar-file {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ---------- Recherche-Seiten: Filter fest oben, Tabelle scrollt intern ---------- */
/* Genutzt von Resultate und Messaufgaben (Klassenpräfix res- historisch von den
   Resultaten). Bei 50 Zeilen je Seite wäre ein Seiten-Scroll sonst die Regel und der
   Filter ständig aus dem Bild. Höhe = Viewport − Topbar(58) − .content-Padding(2×22).
   min-height niedrig: bei 1366×768 bleiben ~666px. */
.res-layout {
    display: flex; flex-direction: column; gap: 12px;
    height: calc(100vh - 102px); min-height: 400px;
}
.res-layout > .res-panel { flex: 1; min-height: 0; }
/* Tabelle randlos im Panel – der Panel-Rahmen fasst sie ein, sonst zwei Rahmen. */
.res-panel > .panel-body { padding: 0; }
/* Kein unterer Radius mehr: unter der Tabelle sitzt jetzt das Blättern, und das trägt
   die abgerundete Kante des Panels. */
.res-panel .table-wrap { flex: 1; min-height: 0; overflow: auto; border: 0; border-radius: 0; }

/* Kompaktes Filterpanel: jede gesparte Zeile hier ist eine Tabellenzeile mehr. */
.res-filters > .panel-body { padding: 10px 12px; }
.res-filters .toolbar { gap: 8px; margin: 0; }
.res-filters .field { gap: 2px; }
.res-filters .field > label { font-size: 10.5px; letter-spacing: 0.02em; }
.res-filters .input, .res-filters .select { height: 30px; font-size: 12.5px; padding-left: 9px; }
.res-filters .select { background-position: right 9px center; padding-right: 26px; }
.res-actions { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; }

/* Der Aufklappknopf trägt einen Rahmen. Als rahmenloser grauer Pfeil war er kaum als
   Knopf zu erkennen – man sah der Zeile nicht an, dass sich etwas darin öffnen lässt.
   Offen trägt er die Akzentfarbe: in einer langen Liste ist damit auf einen Blick zu
   sehen, welche Zeile die aufgeklappte ist. */
.res-expand {
    width: 22px; height: 22px; padding: 0; flex: none;
    display: inline-flex; align-items: center; justify-content: center;
    border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
    background: var(--panel-raised); color: var(--text-dim);
    font-size: 11px; line-height: 1; cursor: pointer;
    transition: border-color .12s, background .12s, color .12s;
}
.res-expand:hover { border-color: var(--accent); color: var(--text); }
.res-expand.on {
    border-color: var(--accent); background: var(--accent-soft); color: var(--accent);
}

/* Das Blättern sitzt unter der Tabelle, wo es bei einer Tabelle hingehört. Es rollt nicht
   mit: der Streifen liegt außerhalb von .table-wrap und bleibt am unteren Rand stehen. */
.res-pager {
    display: flex; align-items: center; gap: 7px; flex: none; flex-wrap: wrap;
    padding: 8px 14px; border-top: 1px solid var(--line); font-size: 12px;
    border-radius: 0 0 var(--radius) var(--radius);
}
.res-check { width: 15px; height: 15px; accent-color: var(--accent); cursor: pointer; vertical-align: middle; }

/* Schmale Fenster: feste Höhe aufgeben, die Seite darf scrollen. */
@media (max-width: 1100px) {
    .res-layout { height: auto; min-height: 0; }
    .res-layout > .res-panel { flex: none; }
    .res-panel .table-wrap { max-height: 60vh; }
}

/* ---------- Tabellen mit aufklappbarer Unterzeile (Resultate, Messaufgaben) ---------- */
/* Zeilen flacher als in den übrigen Tabellen – hier zählt die Menge. */
.res-table th { padding: 8px 14px; }
.res-table > tbody > tr > td { padding: 7px 14px; }
/* Auffällige Zeilen bekommen eine Kante statt einer Flächenfarbe – eingefärbte Zeilen
   kämpfen sonst mit dem :hover der Tabelle und mit jedem zweiten Theme. */
.res-table tbody tr.res-faulty td:first-child { box-shadow: inset 2px 0 0 var(--fault); }
.res-table tbody tr.res-warn td:first-child { box-shadow: inset 2px 0 0 var(--warn); }
.res-table tbody tr.res-files-row:hover { background: transparent; }
.res-files-row > td { padding: 0 14px 12px 48px; background: var(--panel-raised); }
table.data.res-files { font-size: 12px; border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
table.data.res-files th { padding: 7px 12px; background: transparent; position: static; }
table.data.res-files td { padding: 7px 12px; }
.res-sub-head { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-dim); margin: 10px 0 6px; display: block; }
.res-pre {
    font-family: var(--font-mono); font-size: 11.5px; line-height: 1.55;
    background: var(--panel-raised); border: 1px solid var(--line); border-radius: var(--radius);
    padding: 12px; margin: 0; max-height: 52vh; overflow: auto; white-space: pre-wrap; word-break: break-word;
}

/* ---------- Skript-Editor ---------- */
/* Bildschirmfüllend wie die Recherche-Seiten: Kopfleiste fest, darunter links die
   Sektionen des Skripts, rechts Editor/Flow und die Prüfbefunde. */
.ed-layout {
    display: flex; flex-direction: column; gap: 10px;
    height: calc(100vh - 102px); min-height: 460px;
}
.ed-bar > .panel-body { padding: 8px 12px; }
.ed-bar-row { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.ed-bar .field > label { font-size: 10.5px; }
.ed-bar .select { height: 30px; font-size: 12.5px; }

.ed-body { flex: 1; min-height: 0; display: grid; grid-template-columns: 330px minmax(0, 1fr); gap: 10px; }

/* Linke Spalte: Sektionen, jede einzeln auf- und zuklappbar, insgesamt scrollend. */
.ed-side {
    display: flex; flex-direction: column; gap: 8px; min-height: 0; overflow-y: auto;
    padding-right: 2px;
}
.ed-section { border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel); }
.ed-section-grow { display: flex; flex-direction: column; min-height: 0; }
.ed-section-head {
    width: 100%; display: flex; align-items: center; gap: 8px; justify-content: space-between;
    padding: 9px 11px; background: var(--panel-raised); border: 0; cursor: pointer;
    border-radius: var(--radius) var(--radius) 0 0; color: var(--text);
    font-family: var(--font-ui); font-size: 12.5px; font-weight: 600; text-align: left;
}
.ed-section-head:hover { color: var(--accent); }
.ed-section-body { padding: 10px 11px; border-top: 1px solid var(--line); }
.ed-section-scroll { max-height: 40vh; overflow-y: auto; }
.ed-lock { font-size: 11px; opacity: 0.75; }

/* ECU-Karten */
.ecu-card { border: 1px solid var(--line-strong); border-radius: var(--radius-sm); background: var(--panel-inset); }
.ecu-card-head {
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
    padding: 7px 9px; border-bottom: 1px solid var(--line);
}
.ecu-name { font-size: 12.5px; font-weight: 600; color: var(--accent); }
.ecu-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; padding: 9px; }
.ecu-grid .field > label, .ecu-sub .field > label { font-size: 10px; }
.ecu-grid .input, .ecu-grid .select, .ecu-sub .input, .ecu-sub .select { height: 28px; font-size: 12px; padding-left: 8px; }
.ecu-sub { display: flex; flex-direction: column; gap: 6px; padding: 0 9px 9px; }

/* Servicekatalog: eine Liste, die auch bei 60 Einträgen je ECU bedienbar bleibt. */
.svc-list { display: flex; flex-direction: column; gap: 2px; }
.svc-item {
    display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
    padding: 5px 8px; border: 1px solid transparent; border-radius: var(--radius-sm);
    background: transparent; cursor: pointer; text-align: left; color: var(--text);
}
.svc-item:hover { background: var(--panel-raised); border-color: var(--line); }
.svc-name { font-size: 11.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.svc-meta { font-size: 10.5px; color: var(--accent); flex: none; }

/* Variantenerkennung */
.vd-item { border: 1px solid var(--line); border-radius: var(--radius-sm); overflow: hidden; }
.vd-head {
    width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 8px;
    padding: 6px 9px; background: var(--panel-inset); border: 0; cursor: pointer;
    color: var(--text); font-size: 11.5px; text-align: left;
}
.vd-head:hover { color: var(--accent); }
.vd-body { padding: 7px 9px; display: flex; flex-direction: column; gap: 6px; }
.vd-or { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.1em; color: var(--text-faint); text-align: center; }
.vd-value { color: var(--accent); }

/* Bausteine */
.tpl-grid { display: flex; flex-wrap: wrap; gap: 5px; }
.tpl-item {
    padding: 5px 9px; border: 1px solid var(--line); border-radius: 20px;
    background: var(--panel-raised); color: var(--text-dim); cursor: pointer;
    font-family: var(--font-ui); font-size: 11.5px; white-space: nowrap;
}
.tpl-item:hover { color: var(--accent); border-color: var(--accent); }

/* Rechte Spalte */
.ed-main { display: flex; flex-direction: column; gap: 10px; min-width: 0; min-height: 0; }
.ed-panes { flex: 1; min-height: 0; display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; }
.ed-panes-split { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
.ed-pane {
    display: flex; flex-direction: column; min-height: 0; min-width: 0;
    border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel); overflow: hidden;
}
.ed-pane-head {
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
    padding: 7px 11px; background: var(--panel-raised); border-bottom: 1px solid var(--line);
}
.monaco-host { width: 100%; height: 100%; }
.ed-monaco { flex: 1; min-height: 0; }
/* Kurzes Aufblitzen der Zielzeile beim Sprung aus Flow oder Befundliste. */
.diag-line-flash { background: rgba(38, 90, 166, 0.35); transition: background .4s; }

/* Prüfbefunde */
.ed-issues {
    border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel);
    display: flex; flex-direction: column; max-height: 34%; min-height: 0;
}
.ed-issues-collapsed { max-height: none; }
.ed-issues-head {
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
    padding: 8px 11px; background: var(--panel-raised); border: 0; cursor: pointer;
    color: var(--text); font-family: var(--font-ui); font-size: 12.5px; font-weight: 600;
    border-radius: var(--radius) var(--radius) 0 0;
}
.ed-issues-body { overflow-y: auto; padding: 6px; display: flex; flex-direction: column; gap: 3px; border-top: 1px solid var(--line); }
.ed-issue {
    display: flex; align-items: baseline; gap: 8px; padding: 5px 8px; text-align: left;
    border: 1px solid transparent; border-radius: var(--radius-sm); background: transparent;
    color: var(--text); cursor: pointer; font-size: 12px;
}
.ed-issue:hover { background: var(--panel-raised); border-color: var(--line); }
.ed-issue-sev { font-family: var(--font-mono); font-size: 9.5px; letter-spacing: 0.08em; text-transform: uppercase; flex: none; }
.ed-issue-error .ed-issue-sev { color: var(--fault); }
.ed-issue-warning .ed-issue-sev { color: var(--warn); }
.ed-issue-info .ed-issue-sev { color: var(--text-dim); }
.ed-issue-line { font-size: 10.5px; color: var(--text-faint); flex: none; }
.ed-issue-msg { min-width: 0; }

/* ---------- OnEvent-Editor ---------- */
/* Links die Triggerliste, rechts der gewählte Trigger in drei Schritten. Der
   Referenz-Editor führte jeden Schritt in einem eigenen Dialog; hier bleibt alles
   sichtbar und die Schritte sind frei anwählbar. */
.oe-body { grid-template-columns: 320px minmax(0, 1fr); }

.oe-list {
    display: flex; flex-direction: column; min-height: 0;
    border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel);
}
.oe-list-head {
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
    padding: 8px 10px; background: var(--panel-raised); border-bottom: 1px solid var(--line);
    border-radius: var(--radius) var(--radius) 0 0;
    font-family: var(--font-ui); font-size: 12.5px; font-weight: 600;
}
.oe-list-body { flex: 1; min-height: 0; overflow-y: auto; padding: 7px; display: flex; flex-direction: column; gap: 5px; }

.oe-item {
    border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel-inset);
    padding: 7px 8px; cursor: pointer; display: flex; flex-direction: column; gap: 6px;
    transition: border-color .12s, background .12s;
}
.oe-item:hover { border-color: var(--accent); }
.oe-item.active { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; background: var(--panel-raised); }
.oe-item-main { display: flex; align-items: center; gap: 8px; min-width: 0; }
.oe-item-icon { font-size: 15px; flex: none; width: 20px; text-align: center; }
.oe-item-name { font-family: var(--font-ui); font-size: 12.5px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.oe-item-note { font-size: 10.5px; color: var(--text-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.oe-item-foot { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.oe-dots { display: inline-flex; gap: 3px; }
/* Drei Punkte = drei Schritte. Auf einen Blick, welcher Trigger noch nicht fertig ist. */
.oe-dot {
    width: 17px; height: 17px; border-radius: 50%; display: grid; place-items: center;
    font-family: var(--font-mono); font-size: 9px; border: 1px solid var(--line-strong); color: var(--text-faint);
}
.oe-dot.ok { border-color: var(--ok); color: var(--ok); }
.oe-dot.miss { border-color: var(--warn); color: var(--warn); }

.oe-pane {
    flex: 1; min-height: 0; display: flex; flex-direction: column;
    border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel); overflow: hidden;
}
.oe-head { display: flex; gap: 10px; padding: 10px 12px; border-bottom: 1px solid var(--line); flex-wrap: wrap; }
.oe-head .field > label { font-size: 10.5px; }
.oe-head .input { height: 30px; font-size: 12.5px; }

/* ---------- Ablaufkette (Aktivitätsdiagramm) ----------
   Start ▸ Ereignis ▸ Messaufgabe ▸ Ausführung ▸ Fertig. Jeder Schritt ist ein eigenes
   Panel, dazwischen liegen Kanten. Die Kanten bis zum aktuellen Schritt sind gefüllt;
   die Kante, die in den aktuellen Schritt führt, trägt einen laufenden Punkt. */
/* Der Ablauf bekommt ein Drittel der Fläche, die Bearbeitung darunter zwei Drittel. */
.oe-flow {
    flex: 1 1 0; min-height: 132px;
    display: flex; align-items: center; gap: 12px; justify-content: space-between;
    padding: 12px 12px 13px; border-bottom: 1px solid var(--line);
    background: linear-gradient(180deg, var(--panel-raised), transparent);
}
/* Die Kette nimmt die volle Breite ein: der Platz geht zuerst an die Kanten,
   die Panels wachsen nur maßvoll mit – ein Ablauf liest sich über lange Linien. */
.oe-flow-chain {
    flex: 1; display: flex; align-items: stretch; min-width: 0; overflow-x: auto; padding: 2px 2px 4px;
    height: 100%; max-height: 210px;
}

/* Start-/Endknoten: runde Marken wie im Aktivitätsdiagramm. */
.oe-cap {
    display: flex; flex-direction: column; align-items: center; gap: 5px; flex: none;
    align-self: center; color: var(--text-faint);
}
.oe-cap-dot {
    width: 15px; height: 15px; border-radius: 50%;
    border: 1px solid var(--line-strong); background: var(--panel-inset);
    transition: border-color .3s, background .3s, box-shadow .3s;
}
.oe-cap-end .oe-cap-dot { box-shadow: inset 0 0 0 2px var(--panel); }
.oe-cap.lit { color: var(--ok); }
.oe-cap.lit .oe-cap-dot { border-color: var(--ok); background: var(--ok); }
.oe-cap-end.lit .oe-cap-dot { box-shadow: inset 0 0 0 2px var(--panel), 0 0 10px -1px var(--ok); }
.oe-cap-label { font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.08em; text-transform: uppercase; }

/* Kante: Grundlinie, darüber die gefüllte Linie, dazu Pfeilspitze und laufender Punkt. */
.oe-link { position: relative; flex: 3 1 40px; min-width: 34px; align-self: center; height: 14px; }
.oe-link::before,
.oe-link::after {
    content: ''; position: absolute; left: 0; right: 0; top: 50%; height: 1.5px; margin-top: -0.75px;
    border-radius: 2px;
}
.oe-link::before { background: var(--line-strong); }
.oe-link::after {
    background: var(--accent); transform: scaleX(0); transform-origin: left center;
    transition: transform .45s cubic-bezier(.4, 0, .2, 1);
}
.oe-link.on::after { transform: scaleX(1); }
.oe-link-arrow {
    position: absolute; right: -1px; top: 50%; width: 0; height: 0; margin-top: -4px;
    border-left: 5px solid var(--line-strong);
    border-top: 4px solid transparent; border-bottom: 4px solid transparent;
    transition: border-left-color .45s .2s;
}
.oe-link.on .oe-link-arrow { border-left-color: var(--accent); }
/* Der Punkt läuft nur auf der Kante, die in den aktuellen Schritt führt. */
.oe-link-pulse { display: none; }
.oe-link.flow .oe-link-pulse {
    display: block; position: absolute; top: 50%; left: 0; width: 6px; height: 6px; margin-top: -3px;
    border-radius: 50%; background: var(--accent); box-shadow: 0 0 8px 1px var(--accent-glow);
    animation: oe-run 1.5s cubic-bezier(.55, 0, .45, 1) infinite;
}
@keyframes oe-run {
    0%   { left: 0; opacity: 0; transform: scale(.4); }
    18%  { opacity: 1; transform: scale(1); }
    72%  { opacity: 1; transform: scale(1); }
    100% { left: calc(100% - 6px); opacity: 0; transform: scale(.4); }
}

/* Schritt-Panel in der Kette */
.oe-node {
    flex: 1 1 168px; min-width: 150px; max-width: 300px;
    display: flex; flex-direction: column; gap: 3px; align-items: flex-start; justify-content: center;
    padding: 10px 13px; cursor: pointer; text-align: left;
    border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
    background: var(--panel-inset); color: var(--text-dim);
    transition: border-color .18s, background .18s, color .18s, box-shadow .18s, transform .18s;
}
.oe-node:hover { border-color: var(--accent); color: var(--text); transform: translateY(-1px); }
.oe-node.active {
    border-color: var(--accent); color: var(--text); background: var(--accent-soft);
    box-shadow: 0 0 0 1px var(--accent) inset, 0 0 18px -6px var(--accent-glow);
}
.oe-node.done { border-color: color-mix(in srgb, var(--ok) 45%, var(--line-strong)); }
.oe-node.done.active { border-color: var(--accent); }
.oe-node-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; width: 100%; }
.oe-node-no {
    width: 20px; height: 20px; border-radius: 50%; display: grid; place-items: center; flex: none;
    background: var(--panel-raised); border: 1px solid var(--line-strong);
    font-family: var(--font-mono); font-size: 10px;
}
.oe-node.active .oe-node-no { border-color: var(--accent); color: var(--accent); }
.oe-node.done .oe-node-no { border-color: var(--ok); color: var(--ok); }
.oe-node-state { font-size: 11px; flex: none; color: var(--warn); }
.oe-node.done .oe-node-state { color: var(--ok); }
.oe-node-name { font-family: var(--font-ui); font-size: 12.5px; font-weight: 600; }
.oe-node-note { font-size: 10px; color: var(--text-faint); }
.oe-node-val {
    max-width: 100%; margin-top: 3px; padding: 1px 7px; border-radius: 20px;
    border: 1px solid var(--line); background: var(--panel);
    font-family: var(--font-mono); font-size: 10px; color: var(--text-dim);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.oe-node.active .oe-node-val { border-color: var(--accent); color: var(--accent); }

.oe-flow-all {
    flex: none; align-self: center; padding: 6px 11px; cursor: pointer;
    border: 1px solid var(--line-strong); border-radius: 20px;
    background: var(--panel-inset); color: var(--text-dim);
    font-family: var(--font-ui); font-size: 11.5px; white-space: nowrap;
}
.oe-flow-all:hover { border-color: var(--accent); color: var(--text); }
.oe-flow-all.active { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }

.oe-step-body { flex: 2 1 0; min-height: 0; overflow-y: auto; padding: 12px; display: flex; flex-direction: column; gap: 10px; }
.oe-step-body.oe-stack { gap: 0; }

/* Das Panel zum aktuellen Schritt – es fährt bei jedem Wechsel neu ein. */
.oe-stage {
    display: flex; flex-direction: column; min-height: 0;
    border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel-inset);
}
.oe-stage.solo { flex: 1; }
.oe-stage.solo.oe-stage-1 { animation: oe-enter-1 .28s ease-out; }
.oe-stage.solo.oe-stage-2 { animation: oe-enter-2 .28s ease-out; }
.oe-stage.solo.oe-stage-3 { animation: oe-enter-3 .28s ease-out; }
@keyframes oe-enter-1 { from { opacity: 0; transform: translateX(10px); } to { opacity: 1; transform: none; } }
@keyframes oe-enter-2 { from { opacity: 0; transform: translateX(10px); } to { opacity: 1; transform: none; } }
@keyframes oe-enter-3 { from { opacity: 0; transform: translateX(10px); } to { opacity: 1; transform: none; } }
.oe-stage-head {
    display: flex; align-items: center; gap: 8px; padding: 8px 11px;
    background: var(--panel-raised); border-bottom: 1px solid var(--line);
    border-radius: var(--radius) var(--radius) 0 0;
}
.oe-stage-no {
    width: 20px; height: 20px; border-radius: 50%; display: grid; place-items: center; flex: none;
    border: 1px solid var(--accent); color: var(--accent);
    font-family: var(--font-mono); font-size: 10px;
}
.oe-stage-title { font-family: var(--font-ui); font-size: 12.5px; font-weight: 600; }
.oe-stage-sub { font-size: 10.5px; color: var(--text-faint); flex: 1; min-width: 0; }
.oe-stage-flag {
    flex: none; font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.08em;
    text-transform: uppercase; padding: 2px 7px; border-radius: 20px; border: 1px solid currentColor;
}
.oe-stage-flag.ok { color: var(--ok); }
.oe-stage-flag.miss { color: var(--warn); }
.oe-stage-body { padding: 11px; overflow: auto; }

/* Übersicht: die drei Panels untereinander, senkrecht verbunden. */
.oe-vlink { position: relative; height: 26px; flex: none; }
/* Senkrecht unter der Nummer im Kopf des Panels – die Kette bleibt in einer Flucht. */
.oe-vlink::before,
.oe-vlink::after {
    content: ''; position: absolute; top: 0; bottom: 0; left: 21px; width: 1.5px; border-radius: 2px;
}
.oe-vlink::before { background: var(--line-strong); }
.oe-vlink::after {
    background: var(--accent); transform: scaleY(0); transform-origin: top center;
    transition: transform .45s cubic-bezier(.4, 0, .2, 1);
}
.oe-vlink.on::after { transform: scaleY(1); }
.oe-vlink-dot {
    position: absolute; left: 17px; bottom: 3px; width: 0; height: 0;
    border-top: 6px solid var(--line-strong);
    border-left: 5px solid transparent; border-right: 5px solid transparent;
    transition: border-top-color .45s .2s;
}
.oe-vlink.on .oe-vlink-dot { border-top-color: var(--accent); }

@media (prefers-reduced-motion: reduce) {
    .oe-link.flow .oe-link-pulse { animation: none; }
    .oe-stage.solo { animation: none; }
}
.oe-foot {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    padding: 9px 12px; border-top: 1px solid var(--line); background: var(--panel-raised);
}

/* Auswahlkarten für Trigger- und Aufgabenart */
.trig-kind {
    display: flex; align-items: center; gap: 9px; padding: 8px 11px; cursor: pointer;
    border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
    background: var(--panel-inset); color: var(--text-dim); text-align: left; min-width: 190px;
    transition: border-color .12s, color .12s, background .12s;
}
.trig-kind:hover { border-color: var(--accent); color: var(--text); }
.trig-kind.active { border-color: var(--accent); color: var(--text); background: var(--accent-soft); }
.trig-kind-icon { font-size: 16px; flex: none; width: 22px; text-align: center; }
.trig-kind-name { font-family: var(--font-ui); font-size: 12.5px; font-weight: 600; }
.trig-kind-note { font-size: 10.5px; color: var(--text-faint); }

/* Dongle-Taste: das Bild zeigt, welche Taste gemeint ist – links oder rechts am Gehäuse. */
.gpio-grid { display: flex; gap: 12px; flex-wrap: wrap; }
.gpio-card {
    display: flex; flex-direction: column; gap: 7px; padding: 9px; cursor: pointer; text-align: left;
    border: 1px solid var(--line-strong); border-radius: var(--radius); background: var(--panel-inset);
    color: var(--text-dim); width: 230px;
    transition: border-color .15s, color .15s, background .15s, box-shadow .15s, transform .15s;
}
.gpio-card:hover { border-color: var(--accent); color: var(--text); transform: translateY(-1px); }
.gpio-card.active {
    border-color: var(--accent); color: var(--text); background: var(--accent-soft);
    box-shadow: 0 0 0 1px var(--accent) inset;
}
/* Die Zeichnung hat einen weißen Grund; die helle Fläche bleibt in jedem Theme gleich. */
.gpio-img {
    width: 100%; height: 138px; object-fit: contain; background: #FFFFFF;
    border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 4px;
}
.gpio-foot { display: flex; align-items: center; gap: 7px; }
.gpio-name { font-family: var(--font-ui); font-size: 12.5px; font-weight: 600; flex: 1; }
.gpio-note { font-size: 10.5px; color: var(--text-faint); }
.gpio-check { font-size: 12px; color: var(--accent); flex: none; }

/* GPS: Karte neben den Feldern, Klick setzt die Position */
.geo-map { flex: 1; min-width: 280px; height: 300px; border: 1px solid var(--line-strong); border-radius: var(--radius-sm); overflow: hidden; }

/* CAN-Signale */
.sig-card { border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel-inset); }
.sig-head { display: flex; align-items: center; gap: 8px; padding: 6px 9px; border-bottom: 1px solid var(--line); }
.sig-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 8px; padding: 9px; }
.sig-grid .field > label { font-size: 10px; }
.sig-grid .input, .sig-grid .select { height: 28px; font-size: 12px; padding-left: 8px; }

/* Ausführungsarten */
.exec-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 8px; }
.exec-card {
    display: flex; flex-direction: column; align-items: flex-start; gap: 3px;
    padding: 11px 13px; cursor: pointer; text-align: left;
    border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
    background: var(--panel-inset); color: var(--text-dim);
    transition: border-color .12s, color .12s, background .12s;
}
.exec-card:hover { border-color: var(--accent); color: var(--text); }
.exec-card.active { border-color: var(--accent); color: var(--text); background: var(--accent-soft); }
.exec-icon { font-family: var(--font-mono); font-size: 15px; color: var(--accent); }
.exec-name { font-family: var(--font-ui); font-size: 13px; font-weight: 600; }
.exec-note { font-size: 10.5px; color: var(--text-faint); }

@media (max-width: 1200px) {
    .oe-body { grid-template-columns: 1fr; }
    .oe-list { max-height: 320px; }
    .oe-pane { height: auto; }
    .geo-map { width: 100%; }
    /* Die Kette bleibt waagerecht und scrollt lieber, als umzubrechen –
       ein umgebrochener Ablauf liest sich nicht mehr als Ablauf. */
    .oe-flow { flex-direction: column; align-items: stretch; gap: 9px; }
    .oe-flow-all { align-self: flex-end; }
}

/* ---------- Flow-Ansicht (OTX-/UML-Anlehnung) ---------- */
.flow-wrap { flex: 1; min-height: 0; overflow: auto; padding: 14px 16px; }
.flow-chain { display: flex; flex-direction: column; gap: 0; }
.flow-step { display: flex; flex-direction: column; }
/* Verbindungslinie zwischen den Schritten – der Ablauf liest sich von oben nach unten. */
.flow-step + .flow-step > .flow-node::before,
.flow-step > .flow-children > .flow-step + .flow-step > .flow-node::before {
    content: ''; position: absolute; left: 50%; top: -9px; width: 1px; height: 9px;
    background: var(--line-strong);
}
.flow-node {
    position: relative; display: flex; align-items: center; gap: 9px;
    margin: 9px 0 0; padding: 7px 11px; width: 100%; text-align: left; cursor: pointer;
    border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
    background: var(--panel-raised); color: var(--text); font-family: var(--font-ui); font-size: 12.5px;
    transition: border-color .12s, background .12s;
}
.flow-node:hover { border-color: var(--accent); background: var(--panel-inset); }
.flow-active { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }
.flow-badge {
    font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase;
    padding: 2px 7px; border-radius: 20px; border: 1px solid currentColor; flex: none; opacity: 0.9;
}
.flow-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: var(--font-mono); font-size: 12px; }
.flow-note { font-size: 10.5px; color: var(--text-dim); flex: none; }
.flow-line { font-size: 10px; color: var(--text-faint); flex: none; }
/* Verschachtelung: Kinder eingerückt mit Klammerlinie, wie im Aktivitätsdiagramm. */
.flow-children {
    margin: 0 0 0 22px; padding-left: 14px; border-left: 1px dashed var(--line-strong);
}
.flow-loopback { font-size: 10.5px; color: var(--text-faint); padding: 6px 0 2px; font-family: var(--font-mono); }

.flow-start .flow-badge, .flow-end .flow-badge { color: var(--ok); }
.flow-start, .flow-end { border-radius: 20px; }
.flow-ecu .flow-badge { color: var(--warn); }
.flow-ecu { border-left: 2px solid var(--warn); }
.flow-request .flow-badge { color: var(--accent); }
.flow-request { border-left: 2px solid var(--accent); }
.flow-block { background: var(--panel-inset); font-weight: 600; }
.flow-block .flow-badge { color: var(--text-dim); }
.flow-loop, .flow-condition { background: var(--panel-inset); }
.flow-loop .flow-badge, .flow-condition .flow-badge { color: #C792EA; }
.flow-try .flow-badge { color: var(--fault); }
.flow-log .flow-badge, .flow-progress .flow-badge, .flow-statement .flow-badge { color: var(--text-faint); }
.flow-comment { opacity: 0.75; font-style: italic; }
.flow-progress .flow-title { color: var(--text-dim); }

/* Schmale Fenster: Sektionen über den Editor legen, Seite darf scrollen. */
@media (max-width: 1200px) {
    .ed-layout { height: auto; min-height: 0; }
    .ed-body { grid-template-columns: 1fr; }
    .ed-side { overflow: visible; }
    .ed-panes, .ed-panes-split { grid-template-columns: 1fr; }
    .ed-pane { height: 60vh; }
    .ed-issues { max-height: none; }
}

/* ================= Zeitreihen =================
   Zwei Spalten: links die Auswahl (Messungen, Signale), rechts die Ansicht. Alle
   Bedienelemente sind hier eine Nummer kleiner als im Rest des Portals – auf dieser Seite
   stehen viele davon gleichzeitig auf dem Schirm, und der Platz gehört dem Diagramm.

   Die Seite füllt das Fenster und scrollt nicht: 58px Topbar + 2×22px Padding von .content
   gehen ab, den Rest teilen Kopfzeile, Leiste und Inhalt unter sich auf. Gescrollt wird nur
   innerhalb der Panels – wer zum Diagramm scrollen muss, verliert die Auswahl aus dem Blick.
   Die Höhe hängt an dvh statt vh, damit die Adressleiste mobiler Browser nichts abschneidet. */
.ts-page { display: flex; flex-direction: column; height: calc(100dvh - 58px - 44px); min-height: 460px; }

/* Die mittlere Spalte ist der Steg für die Breite der Auswahl (js/splitpane.js setzt
   --ts-side-w). Sie ersetzt den Abstand zwischen den Spalten; Auswahl und Ansicht stehen
   fest in ihren Spalten, damit die Ansicht nicht in die Stegspalte rutscht, wenn es den
   Steg gerade nicht gibt (Leiste). */
.ts-layout {
    display: grid; grid-template-columns: var(--ts-side-w, 340px) 12px minmax(0, 1fr); gap: 0;
    flex: 1; min-height: 0;
}
.ts-side { grid-column: 1; display: flex; flex-direction: column; gap: 12px; min-width: 0; min-height: 0; }
.ts-layout > .ts-main { grid-column: 3; }

/* Trennstege: schmal im Ruhezustand, deutlich unter dem Zeiger – wie im ODX-Editor. Die
   Fläche zum Greifen ist der ganze Zwischenraum, gezeichnet wird nur eine Linie. */
.ts-split { position: relative; flex: none; touch-action: none; }
.ts-split::before {
    content: ''; position: absolute; border-radius: 1px;
    background: transparent; transition: background .12s;
}
.ts-split:hover::before, .ts-split.dragging::before { background: var(--accent); }
.ts-split-x { grid-column: 2; cursor: col-resize; }
.ts-split-x::before { top: 0; bottom: 0; left: 5px; width: 2px; }
.ts-split-y { height: 12px; cursor: row-resize; }
.ts-split-y::before { left: 0; right: 0; top: 5px; height: 2px; }

/* Mit Steg teilen sich die Panels die Höhe nicht mehr zu gleichen Teilen: die Messungen
   bekommen, was gezogen wurde (Vorgabe: die Hälfte), die Signale den Rest. Schrumpfen
   dürfen die Messungen trotzdem – ein gemerktes Maß aus einem größeren Fenster soll die
   Signale nicht unter ihre Mindesthöhe drücken. Der Steg selbst ist der Abstand. */
.ts-side.ts-side-split { gap: 0; }
.ts-side-split > .ts-p-sessions { flex: 0 1 var(--ts-sessions-h, 50%); }
/* Laschen oben, Ansicht darunter: die Spalte ist seit den Reitern eine Säule aus zwei
   Teilen. Die Ansicht nimmt, was die Laschen übrig lassen – mit height:100% wäre sie so
   hoch wie die ganze Spalte und stünde um die Laschenhöhe über. */
.ts-main { display: flex; flex-direction: column; min-width: 0; min-height: 0; }
.ts-main > .panel,
.ts-main > .ts-chart-host { flex: 1; min-height: 0; height: auto; }

/* Messungen und Signale teilen sich die Spalte zu gleichen Teilen – zwei gleich hohe
   Panels lesen sich als ein Paar, ungleiche wie ein Versehen. Beide behalten dieselbe
   Mindesthöhe, damit keines bei vielen Einträgen dem anderen den Platz nimmt. Auf dem
   Kommunikations-Reiter fehlt das Signal-Panel, und die Messungen nehmen die ganze
   Spalte. */
.ts-p-sessions { flex: 1 1 0; min-height: 168px; }
/* Die Auswahlspalte ist schmal: Titel und Knopfreihe teilen sich den Kopf nicht mehr auf
   einer Zeile, sobald eine Aktion dazukommt. Umbrechen ist die ehrlichere Lösung als ein
   Titel, der zu „M“ zusammenfällt. */
.ts-side .panel-head { flex-wrap: wrap; row-gap: 6px; }
.ts-side .panel-head > .row { margin-left: auto; }
.ts-side .panel-title { min-width: 0; }
.ts-side .panel-title h3 { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ts-p-signals { flex: 1 1 0; min-height: 168px; }

/* Eingeklappt: nur der Kopf bleibt, das andere Panel bekommt die Spalte. Der Körper wird
   versteckt und nicht weggelassen – Rollposition und Filter der Liste überleben so das
   Einklappen. Mit .panel-fill im Selektor, weil dessen height:100% sonst gewönne. */
.panel-fill.ts-folded { flex: 0 0 auto; height: auto; min-height: 0; }
.panel-fill.ts-folded > .panel-body { display: none; }
.ts-folded > .panel-head { border-bottom-color: transparent; }
.ts-fold svg { transition: transform .15s; }
.ts-folded .ts-fold svg { transform: rotate(-90deg); }

/* Beides eingeklappt: die Spalte wird zur Leiste, die Ansicht bekommt die Breite. Die
   Panels bleiben im Dokument (siehe oben), nur unsichtbar; an ihrer Stelle stehen zwei
   Laschen mit senkrechter Schrift und der Zahl der gewählten Einträge. */
.ts-layout.ts-rail-mode { grid-template-columns: 38px 12px minmax(0, 1fr); }
.ts-rail-mode .ts-side { gap: 8px; }
.ts-rail-mode .ts-side > .panel { display: none; }
.ts-rail-tab {
    display: flex; flex-direction: column; align-items: center; gap: 8px;
    padding: 10px 0; width: 100%; cursor: pointer;
    background: var(--panel); color: var(--text-dim);
    border: 1px solid var(--line); border-radius: var(--radius);
    font: inherit; transition: border-color .12s, color .12s;
}
.ts-rail-tab:hover, .ts-rail-tab:focus-visible { border-color: var(--line-strong); color: var(--text); }
.ts-rail-ico { font-size: 11px; line-height: 1; }
.ts-rail-label {
    writing-mode: vertical-rl; transform: rotate(180deg);
    font-family: var(--font-mono); font-size: 10.5px; letter-spacing: 0.12em;
}
.ts-rail-count {
    min-width: 20px; padding: 1px 4px; border-radius: 9px; font-size: 10.5px; text-align: center;
    background: var(--accent-soft); color: var(--accent);
}

/* Die Auswahlleiste steht fest, der Rest ist der Inhalt. */
.ts-bar { margin-bottom: 10px; flex: none; }

/* Die Ansicht selbst: Diagramm, Karte und Tabelle füllen ihr Panel und scrollen intern. */
.ts-chart-area { flex: 1; min-height: 180px; }
.ts-map-area { flex: 1; min-height: 180px; display: flex; }
.ts-map-area > .map-wrap { flex: 1; min-height: 0; }
.ts-table-area { flex: 1; min-height: 0; overflow: auto; }

.ts-pager { display: flex; align-items: center; gap: 7px; padding-top: 9px; margin-top: 9px;
    border-top: 1px solid var(--line); font-size: 11.5px; flex: none; }

/* Trennstrich zwischen Bedien- und Exportknöpfen in einer Panel-Kopfzeile. */
.ts-sep { width: 1px; height: 18px; background: var(--line-strong); margin: 0 3px; flex: none; }

.ts-f-dongle { min-width: 230px; flex: 1 1 230px; max-width: 460px; }
/* So hoch wie die Auswahlfelder daneben. */
.ts-f-dongle .sels-btn { height: 38px; }
.ts-f-range { width: 128px; }
/* Die Trennpause sitzt hinter einem Knopf, nicht in einem Auswahlfeld: die Auflage
   dazu hängt darunter und braucht den Knopf als Bezugspunkt. */
.ts-f-gap { position: relative; align-self: flex-end; }
.ts-gap-pop { right: auto; left: 0; min-width: 208px; }
.ts-gap-note { font-size: 11px; line-height: 1.45; color: var(--text-dim); padding: 2px 10px 4px; max-width: 208px; }

/* Der eigene Zeitraum: ein Knopf mit dem gesuchten Zeitraum, darunter die Auflage
   mit Von, Bis und „Übernehmen“ (siehe Timeseries.razor). */
.ts-f-custom { position: relative; }
.ts-custom-btn { gap: 8px; padding: 0 12px; font-size: 12.5px; }
.ts-custom-btn svg { width: 15px; height: 15px; flex: none; color: var(--text-dim); }
.ts-custom-btn.btn-toggled svg { color: var(--accent); }
.ts-custom-pop { right: auto; left: 0; width: 318px; padding: 8px 10px 10px; }
.ts-cp-row { display: grid; grid-template-columns: 30px minmax(0, 1fr) 92px; gap: 6px; align-items: center; margin-top: 7px; }
.ts-cp-row > label { font-size: 11.5px; color: var(--text-dim); }
.ts-cp-row .input { height: 32px; padding: 0 8px; font-size: 12.5px; }
.ts-cp-quick { display: flex; gap: 6px; margin-top: 9px; }
.ts-cp-quick .chip:hover { border-color: var(--accent); color: var(--accent); }
.ts-cp-err { margin-top: 8px; font-size: 11.5px; color: var(--warn); }
.ts-cp-foot { display: flex; justify-content: flex-end; gap: 6px; margin-top: 10px; padding-top: 9px; border-top: 1px solid var(--line); }
@media (max-width: 640px) {
    .ts-custom-pop { position: fixed; left: 12px; right: 12px; top: auto; width: auto; }
}
/* Knöpfe ohne Beschriftung stehen auf der Grundlinie der Eingabefelder – die Leiste
   zentriert sonst, und die Felder mit Beschriftung darüber sind höher. */
.ts-f-load, .ts-f-report { align-self: flex-end; }
.ts-f-color { margin: 0; width: 200px; }
.ts-f-search { margin: 0; width: 210px; }

/* ---------- Darstellungsschalter in der Auswahlleiste ---------- */
.ts-display { display: flex; align-items: center; gap: 5px; margin-left: auto; align-self: flex-end; flex-wrap: wrap; }
.ts-display .ts-y { width: 74px; }
.ts-display .ts-f-smooth { margin: 0; width: 132px; }

/* --- Vollbild des Diagramms ------------------------------------------------
   Derselbe Panel-Inhalt, nur als Auflage über dem Fenster: der Wirt wechselt die Klasse,
   das Diagramm-Bauteil bleibt dasselbe und behält Zoom und Marken. */
.ts-chart-host { height: 100%; min-height: 0; outline: none; }
.ts-chart-host > .panel { height: 100%; }
.ts-chart-host.ts-fs { position: fixed; inset: 0; z-index: 60; padding: 10px;
    background: var(--bg); display: flex; }
/* min-width: 0, weil die Auflage eine Zeile ist: Ohne ließe sich das Panel nicht schmaler
   machen als das Diagramm darin, und das trägt seine zuletzt gemessene Breite als feste
   Pixelzahl. Wurde das Vollbild schmaler – Ro.Ki angedockt –, blieb das Diagramm auf der
   alten Breite, ragte unter die Andockspalte, und sein ResizeObserver meldete nie etwas. */
.ts-chart-host.ts-fs > .panel { flex: 1; height: auto; min-height: 0; min-width: 0; }
/* Im Vollbild ist reichlich Platz: die Werkzeuge stehen in einer Zeile und der Kopf
   darf umbrechen, statt die Knopfreihe abzuschneiden. */
.ts-chart-host.ts-fs .panel-head { flex-wrap: wrap; gap: 8px; }
.ts-chart-host.ts-fs .ts-display { margin-left: 0; }

/* Die Warnung zur Glättung bleibt auch auf schmalen Fenstern stehen: sie sagt, dass die
   gezeichnete Kurve gerechnet ist – das darf nicht die erste Zeile sein, die wegfällt. */
.chart-hint-warn { color: var(--warn); }

/* Segmentierte Umschaltung (Linie/Fläche/Stufe): wie .tabs, nur in Knopfgröße. */
.seg { display: inline-flex; padding: 2px; gap: 2px; background: var(--panel-inset);
    border: 1px solid var(--line); border-radius: var(--radius-sm); flex: none; }
.seg button { height: 23px; padding: 0 9px; border: none; background: transparent; cursor: pointer;
    color: var(--text-dim); font-family: var(--font-ui); font-size: 11px; border-radius: 3px;
    transition: background .12s, color .12s; }
.seg button:hover:not(.active) { color: var(--text); }
.seg button.active { background: var(--panel-raised); color: var(--text); box-shadow: inset 0 0 0 1px var(--line-strong); }

/* Eingeschalteter Umschaltknopf – abgesetzt vom gewöhnlichen Knopf, aber ruhiger als
   btn-primary: es sind Anzeigeoptionen, keine Hauptaktion. */
.btn.btn-toggled { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.btn.btn-toggled:hover { background: var(--accent-soft); border-color: var(--accent-hover); }

/* ---------- Kompaktere Bedienelemente, nur auf dieser Seite ---------- */
.ts-page .toolbar { gap: 9px; }
.ts-page .field { gap: 3px; }
.ts-page .field > label { font-size: 10.5px; }
.ts-page .input, .ts-page .select { height: 29px; font-size: 12px; padding: 0 9px; }
.ts-page .select { padding-right: 26px; background-position: right 8px center; }
.ts-page .btn { height: 29px; padding: 0 11px; font-size: 12px; gap: 6px; }
.ts-page .btn-sm { height: 25px; padding: 0 8px; font-size: 11px; }
.ts-page .btn-icon { width: 29px; padding: 0; }
.ts-page .btn-sm.btn-icon { width: 25px; }
.ts-page .chip { height: 20px; padding: 0 8px; font-size: 10px; gap: 4px; }
.ts-page .tabs { padding: 2px; }
.ts-page .tabs button { height: 26px; padding: 0 11px; font-size: 11.5px; }
.ts-page .panel-head { padding: 8px 11px; }
.ts-page .panel-title h3 { font-size: 12.5px; }
.ts-page .check { font-size: 12px; gap: 7px; }
.ts-page .check input { width: 14px; height: 14px; }
.ts-page .data th, .ts-page .data td { padding: 5px 8px; font-size: 12px; }

/* ---------- Liste der einzelnen Messungen ---------- */
/* Die Liste nimmt die Höhe ihres Panels – kein fester Deckel, sonst bliebe bei hohem
   Fenster Platz ungenutzt und bei niedrigem stünde sie über.
   Gerollt wird der Panelkörper als Ganzes, nicht die Liste: unter ihr stehen die kurzen
   Aufzeichnungen, und solange nur die obere Liste rollte, drückte der aufgeklappte Block
   sie auf null Höhe und ragte selbst aus dem Panel – dessen overflow:hidden (.panel-fill)
   schnitt ihn ab, ohne Bildlaufleiste. Mit einer Rollfläche für beide ist jede Messung
   erreichbar, und in der schmalen Spalte stehen nicht zwei Leisten nebeneinander.
   Mit .panel-fill im Selektor, weil dessen Regel weiter unten steht und sonst gewönne. */
.panel-fill.ts-p-sessions > .panel-body { overflow-y: auto; overflow-x: hidden; scrollbar-width: thin; }
.ms-list { display: flex; flex-direction: column; gap: 3px; flex: none; padding: 1px; }
.ms-item {
    display: flex; align-items: flex-start; gap: 7px; cursor: pointer;
    padding: 5px 7px; border-radius: var(--radius-sm);
    border: 1px solid var(--line); background: var(--panel-raised);
    transition: border-color .12s, background .12s;
}
.ms-item:hover { border-color: var(--line-strong); }
.ms-item.sel { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, var(--panel-raised)); }
.ms-item input[type="checkbox"] { flex: none; width: 14px; height: 14px; margin-top: 1px; accent-color: var(--accent); }
/* Feste Breite: die Nummern sollen untereinander stehen, auch wenn eine Zeile keine hat. */
.ms-item .ms-no {
    flex: none; width: 15px; text-align: center; margin-top: 1px;
    font-family: var(--font-mono); font-size: 10.5px; color: var(--text-faint);
}
.ms-item.sel .ms-no { color: var(--accent); font-weight: 700; }
.ms-item .ms-main { flex: 1 1 auto; min-width: 0; }
.ms-item .ms-title { font-size: 12px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ms-item .ms-sub { font-size: 11px; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ms-item .ms-meta { font-size: 10px; color: var(--text-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Beide Zeilen reihen ihre Angaben mit einem Mittelpunkt – als Trenner im CSS, damit eine
   fehlende Angabe (keine Strecke, kein GPS) keinen doppelten Punkt hinterlässt. Die
   Zusätze in der zweiten Zeile (Grund, Gerät) bringen ihren eigenen Abstand mit. */
.ms-item .ms-sub > span + span::before,
.ms-item .ms-meta > .ms-sep::before,
.ms-item .ms-meta > .ms-gps::before { content: '·'; margin: 0 5px; color: var(--text-faint); }
.ms-item .ms-sub > span:first-child { font-weight: 600; }
.ms-item:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.ms-item .ms-live {
    margin-left: 6px; padding: 0 5px; border-radius: 8px; font-size: 9.5px;
    background: var(--accent-soft); color: var(--accent); border: 1px solid var(--accent);
}
/* Die selbst geschriebene Beschreibung darf zwei Zeilen hoch werden: ein Satz, der nach
   vier Wörtern abschneidet, hilft beim Wiedererkennen nicht. Mehr als zwei nicht – die
   Liste soll eine Liste bleiben und keine Zettelsammlung. */
.ms-item .ms-desc {
    margin-top: 1px; font-size: 10.5px; line-height: 1.35; color: var(--text-dim);
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
/* Der Stift bleibt unsichtbar, bis die Zeile unter dem Zeiger liegt – bei dreißig
   Messungen wären dreißig deutliche Knöpfe lauter als die Namen daneben. Der Platz ist
   trotzdem belegt: sonst rückte die Zeile beim Überfahren zusammen. Mit der Tastatur
   wird er beim Fokus sichtbar, und ohne Hover (Tablet) steht er dauerhaft blass da. */
.ms-item .ms-edit {
    flex: none; width: 24px; height: 24px; margin-top: -1px;
    font-size: 12px; opacity: 0; transition: opacity .12s;
}
.ms-item:hover .ms-edit, .ms-item .ms-edit:focus-visible { opacity: 1; }
@media (hover: none) {
    .ms-item .ms-edit { opacity: 0.55; }
}
/* Der Papierkorb steht nur im Bearbeiten-Modus da – dann aber voll sichtbar: Wer
   „Bearbeiten“ gedrückt hat, sucht ihn und soll nicht erst über die Zeile fahren. */
.ms-item .ms-edit.ms-del { opacity: 1; }
/* Kopfangaben im Benennen-Dialog. Abgesetzter Kasten, weil daran nichts zu ändern ist:
   er sagt, welche Messung gemeint ist, und steht damit vor den beiden Feldern. */
.ms-edit-meta {
    display: flex; flex-direction: column; gap: 6px; padding: 9px 11px;
    border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel-inset);
}

/* ---------- Signalliste ---------- */
.sig-filter { padding: 8px 10px 0; flex: none; }
.sig-table-wrap { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; padding: 6px 4px 4px; }
/* Feste Spaltenbreiten: in der schmalen Auswahlspalte bestimmt sonst der längste
   Signalname die Tabellenbreite, und die Liste rollt waagerecht – mit dem Häkchen als
   erstem, was aus dem Bild läuft. */
.sig-table { width: 100%; border-collapse: collapse; table-layout: fixed; }
.sig-table thead th {
    position: sticky; top: 0; z-index: 1; background: var(--panel);
    text-align: left; font-size: 9.5px; font-weight: 600; letter-spacing: 0.06em;
    text-transform: uppercase; color: var(--text-faint); padding: 0 6px 5px;
}
.sig-table tbody tr { cursor: pointer; border-top: 1px solid var(--line); }
.sig-table tbody tr:hover { background: var(--panel-raised); }
.sig-table tbody tr.sel { background: color-mix(in srgb, var(--accent) 8%, transparent); }
/* Ein Signal ohne Zahlenwerte lässt sich nicht zeichnen – es steht da, aber blass. */
.sig-table tbody tr.textonly { opacity: 0.45; cursor: default; }
.sig-table tbody tr.textonly:hover { background: transparent; }
/* Die GPS-Position: steht mit in der Liste, ist aber nicht wählbar – sie hängt an jeder
   Zeile und ist in Tabelle und Export immer dabei. Abgesetzt, damit niemand nach dem
   fehlenden Häkchen sucht. */
.sig-table tbody tr.sig-gps { cursor: help; background: color-mix(in srgb, var(--info) 6%, transparent); }
.sig-table tbody tr.sig-gps:hover { background: color-mix(in srgb, var(--info) 10%, transparent); }
.sig-table tbody tr.sig-gps .sig-name { color: var(--text); font-weight: 600; }
.sig-gps-ico { display: inline-block; width: 14px; text-align: center; color: var(--info); font-size: 13px; line-height: 1; }
/* Positionsspalte der Rohtabelle: zwei Koordinaten nebeneinander, nie umbrechen. */
.ts-pos { white-space: nowrap; font-size: 11px; }
.sig-table td { padding: 4px 6px; font-size: 11.5px; vertical-align: middle; }
.sig-table .c-check { width: 24px; }
.sig-table .c-check input { width: 14px; height: 14px; accent-color: var(--accent); }
.sig-table .c-name { display: flex; align-items: center; gap: 6px; min-width: 0; }
.sig-table .sig-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sig-table .c-unit { width: 58px; color: var(--text-dim); font-size: 10.5px; }
/* Aus dem Namen abgeleitet statt vom Steuergerät gemeldet – siehe SignalUnits. */
.sig-table .c-unit.derived { font-style: italic; color: var(--text-faint); }
.sig-table .c-count { width: 52px; text-align: right; color: var(--text-faint); font-size: 10.5px; }
.sig-table .sig-dot {
    flex: none; width: 10px; height: 10px; border-radius: 3px;
    border: 1px solid var(--line-strong); background: transparent;
}
.sig-table .sig-color {
    flex: none; width: 14px; height: 14px; padding: 0; border: none; background: none;
    cursor: pointer; border-radius: 3px;
}

/* ---------- Fußzeile unter dem Diagramm ---------- */
.chart-foot { display: flex; flex-direction: column; gap: 1px; margin-top: 6px; flex: none; }

/* Bedienhinweis richtet sich nach dem Eingabegerät, nicht nach der Fensterbreite. */
.chart-hint, .chart-hint-touch { font-size: 10.5px; color: var(--text-dim); }
.chart-hint-touch { display: none; }
@media (pointer: coarse) {
    .chart-hint { display: none; }
    .chart-hint-touch { display: block; }
    .chart-hint-warn { display: block; }
}

/* ---------- Markierte Werte ----------
   Der Block wächst mit der Zahl der Marken, nimmt dem Diagramm aber höchstens ein Drittel
   der Panelhöhe – die Kurve bleibt die Hauptsache. */
.mk-block { flex: none; max-height: 34%; display: flex; flex-direction: column; min-height: 0;
    margin-top: 10px; padding-top: 9px; border-top: 1px solid var(--line); }
.mk-head { flex: none; margin-bottom: 7px; }
.mk-wrap { flex: 1; min-height: 0; overflow: auto; }
.mk-table .c-no { width: 28px; color: var(--text-dim); }
.mk-table .c-del { width: 30px; text-align: right; }
/* --- Art eines Signals in der Auswahlliste ---------------------------------
   Ein Signal, das kein Zahlenwert ist, ergibt keine Kurve, sondern ein Band unter dem
   Diagramm. Ohne diese Kennzeichnung sähe es wie ein wirkungsloses Häkchen aus. Die
   Angabe steht in der Einheitenspalte: ein Zustand hat keine Einheit, dort stand also
   ohnehin nur ein Gedankenstrich. */
.sig-table .sig-kind { display: inline-block; font-family: var(--font-mono); font-size: 9px;
    line-height: 1; padding: 2px 4px; border-radius: 3px; border: 1px solid var(--line-strong);
    color: var(--text-dim); white-space: nowrap; }
.sig-table .sig-kind.sk-state { border-color: color-mix(in srgb, var(--ok) 60%, transparent); color: var(--ok); }
.sig-table .sig-kind.sk-hex { border-color: color-mix(in srgb, var(--accent) 60%, transparent); color: var(--accent); }
.sig-table .sig-kind.sk-text { border-color: var(--line-strong); color: var(--text-dim); }

/* --- Löschen einer Messung ------------------------------------------------- */
.ms-item .ms-del { color: var(--fault); }
.ms-item .ms-del:hover { background: var(--fault); color: #fff; }

.del-lead { font-size: 12.5px; color: var(--text-dim); }
.del-list { display: flex; flex-direction: column; gap: 4px; max-height: 220px; overflow: auto;
    border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 7px 9px;
    background: var(--panel-inset); }
.del-item { display: flex; gap: 10px; align-items: baseline; font-size: 12px; min-width: 0; }
.del-item .del-when { flex: none; font-size: 11px; color: var(--text); }
.del-item .del-what { display: flex; gap: 8px; min-width: 0; align-items: baseline; overflow: hidden; }
.del-item .del-what > span { font-size: 10.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.del-warn { font-size: 12px; line-height: 1.5; color: var(--fault);
    border-left: 2px solid var(--fault); padding-left: 10px; }
.del-sum { font-size: 11.5px; color: var(--text-dim); }
.del-final { font-size: 14px; font-weight: 600; }

.mk-table tr.mk-delta { background: var(--panel-inset); font-weight: 600; }
.mk-table tr.mk-delta td { border-top: 1px solid var(--line-strong); }

/* ---------- Legende unter der Karte ----------
   Eine Linie trägt nur eine Farbe. Das einfärbende Signal steht mit Skala, alle weiteren
   gewählten Signale mit ihrer Spanne – abzulesen sind sie am Punkt selbst. */
.track-legend { display: flex; align-items: center; gap: 12px; margin-top: 8px; flex-wrap: wrap; flex: none; }
.track-legend .tl-item { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; }
.track-legend .tl-item .tl-name { color: var(--text); }
.track-legend .tl-item .tl-range { color: var(--text-dim); font-size: 10.5px; }
/* Das einfärbende Signal ist hervorgehoben: es ist das einzige, das die Karte selbst zeigt. */
.track-legend .tl-color .tl-name { color: var(--accent); font-weight: 600; }
.track-legend .tl-sum { margin-left: auto; font-size: 10.5px; }

@media (max-width: 1180px) {
    /* Unter dieser Breite bleibt für Auswahl und Diagramm nebeneinander zu wenig übrig.
       Die Auswahl wandert nach oben und bekommt eine feste Höhe; die Seite darf dann
       scrollen, weil sonst weder Auswahl noch Diagramm brauchbar hoch wären. */
    .ts-page { height: auto; min-height: 0; }
    .ts-layout { grid-template-columns: 1fr; gap: 12px; }
    /* Übereinander gibt es nichts zu ziehen: die Breite ist die des Fensters, und die
       Panels stehen nebeneinander in fester Höhe. */
    .ts-split { display: none; }
    .ts-side, .ts-layout > .ts-main { grid-column: auto; }
    .ts-side.ts-side-split { gap: 12px; }
    .ts-side-split > .ts-p-sessions { flex: 1 1 280px; }
    .ts-side { flex-direction: row; flex-wrap: wrap; height: 260px; }
    .ts-side > * { flex: 1 1 280px; height: 100%; }
    .ts-main { height: 560px; }
    /* Übereinander: ein eingeklapptes Panel ist nur sein Kopf, beide eingeklappt ist die
       Leiste eine Zeile über der Ansicht – mit waagrechter Schrift. */
    .ts-side:has(> .ts-folded) { height: auto; min-height: 0; }
    .ts-side:has(> .ts-folded) > .panel:not(.ts-folded) { height: 260px; }
    .ts-side > .panel-fill.ts-folded { height: auto; align-self: flex-start; }
    .ts-layout.ts-rail-mode { grid-template-columns: 1fr; }
    .ts-rail-mode .ts-side { flex-direction: row; height: auto; }
    .ts-side > .ts-rail-tab { flex: 1 1 0; height: auto; flex-direction: row; justify-content: center; padding: 8px 12px; }
    .ts-rail-label { writing-mode: horizontal-tb; transform: none; }
    .ts-display { margin-left: 0; }
}
@media (max-width: 640px) {
    .ts-side { flex-direction: column; height: auto; }
    .ts-side > * { height: 240px; }
    .ts-side > .panel-fill.ts-folded { align-self: stretch; }
    .ts-f-dongle { min-width: 100%; }
    .ts-f-search, .ts-f-color { width: 100%; }
}
/* Bedienhinweis richtet sich nach dem Eingabegerät, nicht nach der Fensterbreite. */
.chart-hint, .chart-hint-touch { font-size: 11px; margin-top: 8px; }
.chart-hint-touch { display: none; }
@media (pointer: coarse) {
    .chart-hint { display: none; }
    .chart-hint-touch { display: block; }
    .chart-hint-warn { display: block; }
}

/* ---------- Theme-Selector ---------- */
/* ---------- Theme-Auswahl ---------- */
/* Drei Abschnitte statt eines Rasters: Bei vierzehn Themes ist "hell oder dunkel" die
   erste Entscheidung, und die trifft man an der Ueberschrift und nicht an vierzehn
   Karten. */
.ts-group { margin-top: 20px; }
.ts-group:first-of-type { margin-top: 14px; }
.ts-group-head {
    display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
    padding-bottom: 8px; margin-bottom: 10px; border-bottom: 1px solid var(--line);
}
.ts-group-hint { font-size: 11.5px; color: var(--text-dim); }

/* ---- Der Schalter fuer den Hintergrund ---- */
/* Er steht ueber den Themes, weil er fuer alle vierzehn gilt. */
.ts-switch {
    display: flex; align-items: flex-start; justify-content: space-between; gap: 16px;
    padding: 13px 15px; margin-bottom: 4px;
    background: var(--panel-inset); border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.ts-switch-text { min-width: 0; }
.ts-switch-name { font-family: var(--font-ui); font-weight: 600; font-size: 13px; }
.ts-switch-hint { font-size: 11.5px; line-height: 1.55; color: var(--text-dim); margin-top: 4px; }

/* Ein Kippschalter und kein Kontrollkaestchen: Die Wahl wirkt sofort und hat kein
   "Speichern" - ein Kaestchen verspricht einen Formularschritt, den es nicht gibt. */
.ts-toggle {
    flex: none; width: 42px; height: 24px; padding: 0; cursor: pointer;
    background: var(--panel-raised); border: 1px solid var(--line-strong); border-radius: 12px;
    position: relative; transition: background .15s, border-color .15s;
}
.ts-toggle-knob {
    position: absolute; top: 2px; left: 2px; width: 18px; height: 18px; border-radius: 50%;
    background: var(--text-faint); transition: transform .15s, background .15s;
}
.ts-toggle.on { background: var(--accent-soft); border-color: var(--accent); }
.ts-toggle.on .ts-toggle-knob { transform: translateX(18px); background: var(--accent); }

.ts-note {
    font-size: 11.5px; line-height: 1.55; color: var(--text-dim);
    padding: 9px 12px; margin-top: 8px;
    background: var(--panel-inset); border: 1px solid var(--line);
    border-left: 2px solid var(--accent); border-radius: var(--radius-sm);
}

/* ---- Die Karten ---- */
/* Breiter als vorher (150 -> 176px): Die Vorschau ist jetzt ein Miniaturbildschirm mit
   Navigation und Panel, und darunter zerfaellt sie zu Streifen. */
.theme-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(176px, 1fr)); gap: 12px; }
.theme-card {
    padding: 0; overflow: hidden; cursor: pointer; text-align: left;
    background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius-sm);
    color: inherit; font: inherit;
    transition: border-color .12s, transform .05s;
}
.theme-card:hover { border-color: var(--accent); }
.theme-card:active { transform: scale(0.99); }
.theme-card.active { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }

/* ---- Die Vorschau: ein Bildschirm im Kleinen ---- */
/* Vorher standen hier vier Farbbalken. Die zeigten die Farben, aber nicht, was sie tun:
   Man sah, dass ein Theme ein kraeftiges Blau enthaelt, nicht aber, ob das der Grund oder
   der Knopf ist. Dieselben Flaechen an ihrem Platz sagen es in einem Blick. */
.tc-shot { display: flex; height: 78px; }
.tc-rail {
    flex: none; width: 34px; padding: 7px 5px;
    display: flex; flex-direction: column; gap: 5px;
}
.tc-rail > span { display: block; height: 4px; border-radius: 2px; opacity: 0.9; }
.tc-rail > span:first-child { opacity: 1; }

.tc-body { flex: 1; min-width: 0; padding: 8px; }
.tc-panel {
    height: 100%; border-radius: 3px; padding: 7px;
    display: flex; flex-direction: column; gap: 5px;
}
.tc-line { display: block; height: 3px; border-radius: 2px; width: 100%; opacity: 0.85; }
.tc-line.short { width: 62%; opacity: 1; }
.tc-btn { display: block; height: 9px; width: 42%; border-radius: 3px; margin-top: auto; }

.tc-meta {
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
    padding: 9px 11px 0;
}
.tc-name { font-family: var(--font-ui); font-weight: 600; font-size: 12.5px; }
.tc-check { color: var(--accent); display: flex; flex: none; }
/* Die Beschreibung auf eine Zeile: Zwei Zeilen bei einer und eine bei der naechsten Karte
   machten das Raster ungleich hoch, und ein ungleiches Raster liest sich als Unordnung. */
.tc-desc {
    padding: 2px 11px 10px; font-size: 10.5px; line-height: 1.45; color: var(--text-dim);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.theme-swatches { display: inline-flex; gap: 3px; }
.theme-swatches .sw { width: 11px; height: 11px; border-radius: 50%; border: 1px solid rgba(255,255,255,0.12); }

/* ---------- Fleets ---------- */
/* Bildschirmfüllendes Layout: Flotten links über die volle Höhe, rechts Karte oben
   und Fahrzeugtabelle darunter. Jedes Panel scrollt intern, die Seite selbst nicht.
   Höhe = Viewport − Topbar(58) − .content-Padding oben+unten(2×22) − .section-head
   inkl. Abstand(61) = 163px. Gemessen, nicht geschätzt: ein zu kleiner Abzug
   erzeugt genau den Seiten-Scroll, den dieses Layout vermeiden soll.
   min-height bewusst niedrig – bei 1366×768 bleiben nur ~509px übrig. */
.fleet-layout {
    display: grid;
    grid-template-columns: 300px minmax(0, 1fr);
    grid-template-rows: minmax(240px, 42%) minmax(0, 1fr);
    grid-template-areas:
        "fleets map"
        "fleets cars";
    gap: 16px;
    height: calc(100vh - 163px);
    min-height: 420px;
}
.fleet-layout > .fleets-panel { grid-area: fleets; }
.fleet-layout > .map-panel    { grid-area: map; }
.fleet-layout > .cars-panel   { grid-area: cars; }
/* Karte ausgeblendet: Zeile schrumpft auf den Panel-Kopf, Tabelle bekommt den Rest. */
.fleet-layout.map-off { grid-template-rows: auto minmax(0, 1fr); }

/* Panel füllt seine Grid-Zelle und scrollt intern statt die Seite zu verlängern. */
.panel-fill { height: 100%; display: flex; flex-direction: column; min-height: 0; }
.panel-fill > .panel-body { flex: 1; min-height: 0; overflow: hidden; display: flex; flex-direction: column; }

/* Liste scrollt, die Aktionsknöpfe darunter bleiben sichtbar. */
.fleets-panel .fleet-list { flex: 1; min-height: 0; overflow-y: auto; margin: -2px; padding: 2px; }
/* Tabelle scrollt, die Kopfzeile bleibt dank position:sticky stehen. */
.cars-panel .table-wrap { flex: 1; min-height: 0; overflow: auto; }
/* Karte randlos: Panel-Kopf (46px) + Body-Padding (32px) fressen sonst über ein
   Drittel der Kartenzeile. Der Panel-Rahmen fasst die Karte selbst ein. */
.map-panel > .panel-body { padding: 0; }
.map-panel .map-wrap { flex: 1; min-height: 0; border: 0; border-radius: 0 0 var(--radius) var(--radius); }

/* Schmale Fenster: gestapelt, feste Höhen, Seite darf scrollen. */
@media (max-width: 1100px) {
    .fleet-layout {
        grid-template-columns: 1fr;
        grid-template-rows: auto auto auto;
        grid-template-areas: "fleets" "map" "cars";
        height: auto; min-height: 0;
    }
    .fleet-layout > .fleets-panel { max-height: 320px; }
    .fleet-layout > .map-panel { height: 340px; }
    .fleet-layout > .cars-panel { height: 460px; }
    .fleet-layout.map-off > .map-panel { height: auto; }
}

/* ---------- Rollout: Flotte links, Fahrzeuge mit ihren Aufgaben rechts ---------- */
.ro-layout {
    display: grid; grid-template-columns: 300px minmax(0, 1fr); gap: 16px;

    /* Wie .fleet-split: Kopfzeile, Reiterleiste und die Zeile mit den Schaltflaechen
       sind abgezogen. Das Rollout ist ein Reiter des Flottenbereichs und traegt
       dieselbe Umgebung ueber sich. */
    height: calc(100vh - 248px); min-height: 400px;
}
.ro-layout > .panel-fill > .panel-body { overflow: hidden; }
.ro-layout .fleet-list { flex: 1; min-height: 0; overflow-y: auto; margin: -2px; padding: 2px; }
.ro-layout .table-wrap { flex: 1; min-height: 0; overflow: auto; }

.ro-table td { vertical-align: top; }
.ro-table tr.ro-marked > td { background: var(--accent-soft); }

/* Eine Zuordnung: Typ, Name, und das ✕, das sie wieder wegnimmt. */
.ro-chips { display: flex; flex-wrap: wrap; gap: 5px; }
.ro-chip {
    display: inline-flex; align-items: center; gap: 6px; max-width: 100%;
    padding: 3px 4px 3px 8px; border: 1px solid var(--line-strong); border-radius: 20px;
    background: var(--panel-inset); font-size: 11.5px;
}
.ro-chip.oe { border-color: var(--accent); }
.ro-chip-type {
    font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.08em; text-transform: uppercase;
    color: var(--text-faint); flex: none;
}
.ro-chip.oe .ro-chip-type { color: var(--accent); }
.ro-chip-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ro-chip-x {
    flex: none; width: 18px; height: 18px; border-radius: 50%; border: 0; cursor: pointer;
    background: transparent; color: var(--text-faint); font-size: 11px; line-height: 1;
}
.ro-chip-x:hover { background: var(--fault); color: #fff; }

/* Auswahl der Messaufgabe im Dialog */
.ro-picker {
    max-height: 42vh; overflow-y: auto; display: flex; flex-direction: column; gap: 4px;
    border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 6px; background: var(--panel-inset);
}
.ro-pick {
    display: flex; align-items: center; justify-content: space-between; gap: 10px; width: 100%;
    padding: 7px 9px; cursor: pointer; text-align: left; color: var(--text);
    border: 1px solid transparent; border-radius: var(--radius-sm); background: transparent;
}
.ro-pick:hover { background: var(--panel-raised); border-color: var(--line); }
.ro-pick.active { border-color: var(--accent); background: var(--accent-soft); }
.ro-pick-name { font-family: var(--font-ui); font-size: 12.5px; font-weight: 600; }

/* ---------- Weiterleitungen ---------- */
/* Der UNC-Pfad ist lang und wird trotzdem ganz gebraucht – er darf umbrechen. */
.gw-target { font-size: 11.5px; word-break: break-all; }
.gw-detail-row > td { background: var(--panel-inset); }
.gw-depts { display: flex; flex-wrap: wrap; gap: 6px; }
.gw-dept {
    display: inline-flex; align-items: center; gap: 7px; padding: 5px 10px; cursor: pointer;
    border: 1px solid var(--line-strong); border-radius: 20px; background: var(--panel-inset);
    color: var(--text-dim); font-size: 12px;
}
.gw-dept:hover { border-color: var(--accent); color: var(--text); }
.gw-dept.active { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }

/* Abhängigkeiten eines Ziels im Löschdialog.

   Der Dialog muss zwei Dinge gleichzeitig leisten: zeigen, wie viel daran hängt, und
   zeigen, wo es hängt. Die Zahl links trägt das Gewicht, die Balken darunter die
   Verteilung – ein Bereich mit 69 Zuordnungen soll sich von einem mit 2 unterscheiden,
   bevor man die Zahlen liest. */
.gw-dep {
    display: flex; flex-direction: column; gap: 10px;
    padding: 11px 12px; border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--panel-inset);
}
.gw-dep-head { display: flex; align-items: center; gap: 12px; min-width: 0; }
.gw-dep-count {
    flex: none; min-width: 46px; padding: 0 8px; text-align: center;
    font-family: var(--font-mono); font-size: 21px; font-weight: 600; line-height: 1.15;
    color: var(--warn);
}
.gw-dep-scope { display: flex; flex-direction: column; gap: 5px; padding-left: 58px; }

/* Was außerhalb des gewählten Bereichs liegt, steht in keiner Liste der Seite – hier
   ist es der einzige Ort, an dem es sichtbar wird, also bekommt es eine Kante. */
.gw-dep-scope.foreign {
    padding-left: 52px; margin-left: 0;
    border-left: 2px solid var(--warn);
    padding-top: 2px; padding-bottom: 2px;
}
.gw-dep-bar { height: 5px; border-radius: 3px; background: var(--line); overflow: hidden; }
.gw-dep-fill { height: 100%; border-radius: 3px; background: var(--accent); }
.gw-dep-scope.foreign .gw-dep-fill { background: var(--warn); }
.gw-dep-note { font-size: 11.5px; line-height: 1.5; color: var(--text-dim); padding-left: 58px; }
.gw-dep .gw-dep-note:only-child { padding-left: 0; }

.gw-dep-none {
    display: flex; align-items: center; gap: 10px; padding: 11px 13px; font-size: 12.5px;
    border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel-inset);
    color: var(--text-dim);
}
.gw-dep-none > span:first-child { color: var(--ok); font-size: 15px; }

.gw-dep-ack { align-items: flex-start; gap: 10px; line-height: 1.5; }
.gw-dep-ack input { margin-top: 2px; flex: none; }

/* Zweite Bestätigung: nur noch die drei Zahlen, die verschwinden. */
.gw-dep-sum { display: flex; gap: 8px; flex-wrap: wrap; }
.gw-dep-sum > div {
    flex: 1 1 130px; display: flex; flex-direction: column; gap: 2px;
    padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--panel-inset);
}
.gw-dep-sum-n { font-family: var(--font-mono); font-size: 22px; font-weight: 600; color: var(--fault); }
.gw-dep-sum-l { font-size: 11px; line-height: 1.4; color: var(--text-dim); }

/* In der Tabelle: die Zuordnungen, die der gewählte Bereich nicht zählt. */
.gw-foreign-note { font-size: 10.5px; color: var(--warn); }

/* Eine Zeile, die man nur ansehen darf. Bewusst Text und keine graue Schaltflaeche: Ein
   abgeblendeter Knopf sieht aus wie ein Fehler, der sich gleich behebt – das hier ist eine
   dauerhafte Auskunft, und der Grund steht im Tooltip. */
.gw-readonly {
    font-size: 11px; color: var(--text-dim); font-style: italic; cursor: help;
    padding-right: 4px; white-space: nowrap;
}

@media (max-width: 700px) {
    .gw-dep-scope, .gw-dep-note { padding-left: 0; }
    .gw-dep-scope.foreign { padding-left: 9px; }
}

@media (max-width: 1100px) {
    .ro-layout { grid-template-columns: 1fr; height: auto; min-height: 0; }
    .ro-layout > .panel-fill:first-child { max-height: 300px; }
    .ro-layout > .panel-fill:last-child { height: 560px; }
}

.fleet-list { display: flex; flex-direction: column; gap: 8px; }
.fleet-item {
    display: flex; align-items: center; gap: 12px; padding: 12px 13px; cursor: pointer;
    border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel-raised);
    transition: border-color .12s, background .12s;
}
.fleet-item:hover { border-color: var(--line-strong); }
.fleet-item.active { border-color: var(--accent); background: var(--accent-soft); }
.fleet-item .fi-ic { width: 34px; height: 34px; border-radius: var(--radius-sm); background: var(--panel-inset); border: 1px solid var(--line); display: grid; place-items: center; color: var(--accent); flex: none; }
.fleet-item .fi-body { min-width: 0; flex: 1; }
.fleet-item .fi-name { font-family: var(--font-ui); font-weight: 600; font-size: 13.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fleet-item .fi-sub { font-size: 11.5px; color: var(--text-dim); }
.fleet-item .fi-count { font-family: var(--font-mono); font-size: 11px; color: var(--text-dim); flex: none; }

/* =====================================================================
   Karte (Leaflet / OpenStreetMap)
   Leaflet bringt eigenes CSS mit – hier nur die Einbettung ins Design-System.
   ===================================================================== */
.map-wrap {
    position: relative; width: 100%;
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    overflow: hidden; background: var(--panel-inset);
    /* Leaflet vergibt intern z-index bis 1000 (Controls) – ohne eigenen Stacking-
       Kontext konkurrieren die mit .overlay (100) und malen über Dialoge. isolate
       sperrt sie in die Karte ein, ohne Layout oder Darstellung zu verändern. */
    isolation: isolate;
}
.diagx-map { width: 100%; height: 100%; background: var(--panel-inset); }
/* Damit die Werkzeugleiste auf ihre eigene Breite reagieren kann (Beschriftung des
   Übersichtsknopfes fällt auf schmalen Karten weg) – nicht auf die des Fensters. */
.map-wrap { container-type: inline-size; }

/* Leaflet setzt einen eigenen Font/Hintergrund – an die Tokens angleichen. */
.diagx-map.leaflet-container { font-family: var(--font-body); background: var(--panel-inset); }

/* Dunkle Themes: OSM-Kacheln sind hell. Invertieren + Farbdrehung ergibt eine
   dunkle Karte, ohne einen zweiten Tile-Server zu benötigen. */
.diagx-map--dark .leaflet-tile-pane {
    filter: invert(1) hue-rotate(180deg) brightness(0.92) contrast(0.9) saturate(0.85);
}

/* Pins: DivIcon ohne eigenen Hintergrund. */
.diagx-pin { background: none; border: none; }
.diagx-pin svg { display: block; filter: drop-shadow(0 3px 4px rgba(0,0,0,0.35)); }
.diagx-pin--ok svg { animation: pin-live 2.4s ease-in-out infinite; }
@keyframes pin-live { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-2px); } }

/* Bedienelemente / Attribution in Panel-Optik. */
.diagx-map .leaflet-bar a {
    background: var(--panel); color: var(--text); border-bottom-color: var(--line);
}
.diagx-map .leaflet-bar a:hover { background: var(--panel-raised); color: var(--accent); }
.diagx-map .leaflet-control-attribution {
    background: color-mix(in srgb, var(--panel) 88%, transparent);
    color: var(--text-faint); font-size: 10px;
}
.diagx-map .leaflet-control-attribution a { color: var(--text-dim); }

/* Popup in Panel-Optik. */
.diagx-map .leaflet-popup-content-wrapper {
    background: var(--panel); color: var(--text);
    border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
    box-shadow: var(--shadow);
}
.diagx-map .leaflet-popup-tip { background: var(--panel); border: 1px solid var(--line-strong); }
.diagx-map .leaflet-popup-content { margin: 10px 12px; }
.diagx-map .leaflet-popup-close-button { color: var(--text-faint); }
.diagx-map .leaflet-popup-close-button:hover { color: var(--text); }

.diagx-pop .dp-title { font-family: var(--font-ui); font-weight: 600; font-size: 13.5px; }
.diagx-pop .dp-sub { font-family: var(--font-mono); font-size: 11px; color: var(--text-dim); margin-bottom: 6px; }
.diagx-pop .dp-row { display: flex; justify-content: space-between; gap: 14px; font-size: 11.5px; padding: 2px 0; }
.diagx-pop .dp-k { color: var(--text-faint); }
.diagx-pop .dp-v { font-family: var(--font-mono); color: var(--text); text-align: right; }
.diagx-pop .dp-act { margin-top: 9px; width: 100%; }

/* Overlay, wenn keine Koordinaten vorliegen. */
.map-empty {
    position: absolute; inset: 0; z-index: 500; pointer-events: none;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px;
    background: color-mix(in srgb, var(--panel-inset) 82%, transparent);
    color: var(--text-faint); font-size: 12.5px;
}
/* Über Leaflets Controls (800/1000) – bleiben durch isolation trotzdem in der Karte. */
.map-tools { position: absolute; right: 10px; top: 10px; z-index: 1001; display: flex; flex-direction: column; align-items: flex-end; gap: 6px; }
.map-tool { background: var(--panel); }
.map-tool svg { width: 15px; height: 15px; flex: none; }

/* Der Übersichtsknopf zoomt so weit heraus, dass alle Fahrzeuge im Bild sind. Als
   reines Icon zwischen Vollbild und Leaflets +/- war er nicht als das erkennbar;
   Akzentfarbe und Beschriftung machen ihn zur sichtbaren Standardaktion. Auf sehr
   schmalen Karten bleibt nur das Symbol übrig. */
.map-tool--fit {
    gap: 7px; padding: 0 11px;
    background: var(--accent); color: var(--on-accent); border-color: var(--accent);
    font-weight: 600;
    box-shadow: 0 2px 10px rgba(0,0,0,.28);
}
.map-tool--fit:hover { background: var(--accent-hover); border-color: var(--accent-hover); color: var(--on-accent); }
.map-tool-label { font-size: 11.5px; letter-spacing: .01em; }
@container (max-width: 300px) { .map-tool-label { display: none; } }

/* Vollbild: die Inline-Höhe der Komponente (z.B. 420px oder 100%) muss weichen,
   sonst bliebe die Karte ein Streifen auf schwarzem Grund. Getrennte Regeln, weil
   ein unbekannter Selektor in einer Liste die ganze Regel verwerfen würde. */
.map-wrap:fullscreen { height: 100% !important; width: 100%; border: 0; border-radius: 0; }
.map-wrap:-webkit-full-screen { height: 100% !important; width: 100%; border: 0; border-radius: 0; }

/* Farbskala der eingefärbten Strecke (blau = niedrig, rot = hoch – wie in map.js).
   Die Legende selbst steht oben bei den Zeitreihen, sie trägt dort mehrere Signale. */
.track-scale {
    width: 140px; height: 8px; border-radius: 4px; border: 1px solid var(--line);
    background: linear-gradient(90deg,
        hsl(220,85%,55%), hsl(165,85%,55%), hsl(110,85%,55%), hsl(55,85%,55%), hsl(0,85%,55%));
}

/* ---------- Live-Karte ---------- */
/* Gleiche Rechnung wie beim Fleets-Layout: Viewport minus Topbar(58),
   .content-Padding(2×22) und .section-head inkl. Abstand(61) = 163px. */
.livemap-layout {
    display: grid;
    grid-template-columns: 300px minmax(0, 1fr);
    gap: 16px;
    height: calc(100vh - 163px);
    min-height: 420px;
}
.livemap-layout .live-list-panel .veh-list { flex: 1; min-height: 0; overflow-y: auto; }
.livemap-layout .live-map-panel > .panel-body { padding: 0; }
.livemap-layout .live-map-panel .map-wrap {
    flex: 1; min-height: 0; border: 0; border-radius: 0 0 var(--radius) var(--radius);
}
.live-veh {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    padding: 8px 10px; border-radius: var(--radius-sm); background: var(--panel-raised);
    border: 1px solid transparent; cursor: pointer; transition: border-color .12s, background .12s;
}
.live-veh:hover { border-color: var(--line-strong); }
.live-veh.active { border-color: var(--accent); background: var(--accent-soft); }
.live-veh .vn { font-family: var(--font-ui); font-weight: 600; font-size: 12.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Der gerichtete Pfeil rotiert selbst – der Bounce des ok-Pins würde ihn verzerren. */
.diagx-pin--arrow svg { animation: none !important; }

@media (max-width: 1100px) {
    .livemap-layout {
        grid-template-columns: 1fr; grid-template-rows: auto auto;
        height: auto; min-height: 0;
    }
    .livemap-layout .live-list-panel { max-height: 280px; }
    .livemap-layout .live-map-panel { height: 460px; }
}

/* =====================================================================
   Live Measurements – frei gebaute Überwachungsflächen je Bereich
   Ein Grundsatz zieht sich durch: der Zustand steht in der Farbe, der
   Zahlenwert bleibt lesbar. Alarm rot, Warnung amber, ohne Daten grau –
   nie grün, wenn nichts ankommt.
   ===================================================================== */
/* ---------- Bearbeiten-Modus: Werkzeuge als Auflage im Panel ----------
   Die Knöpfe lagen früher im Panelkopf. Bei 200 Pixel Panelbreite – und genau so
   schmal wird ein 3-Spalten-Panel neben der Messleiste – passen sieben Knöpfe dort
   nicht hinein und schoben sich über Titel und Nachbarpanel. Jetzt liegen sie als
   eigene Leisten über dem Panel: oben die Aktionen, unten Griff und Größe. */
/* (Titel und Kanal kürzen sich in jedem Widget, siehe .widget > .panel > .panel-head.) */
/* Nur die Live-Boards arbeiten mit Auflagen. Das Dashboard hat seine Knöpfe weiterhin
   im Panelkopf – dort würden Polsterung und Fußabstand nur Platz wegnehmen. */
.live-main .widget.editing .panel-head { padding-right: 96px; }
.live-main .widget.editing .panel-body { padding-bottom: 38px; }

.pe-tools, .pe-size {
    position: absolute; z-index: 4; display: flex; align-items: center;
    border: 1px solid var(--line-strong); border-radius: 20px;
    background: color-mix(in srgb, var(--panel-raised) 92%, transparent);
    backdrop-filter: blur(6px);
}
.pe-tools { top: 7px; right: 8px; gap: 2px; padding: 3px; box-shadow: var(--shadow); }
.pe-size { left: 8px; right: 8px; bottom: 7px; gap: 8px; padding: 2px 4px 2px 9px; }

.pe-btn {
    width: 28px; height: 28px; flex: none; border: 0; border-radius: 50%; cursor: pointer;
    background: transparent; color: var(--text-dim); font-size: 12px; line-height: 1;
    display: grid; place-items: center;
    transition: background .14s, color .14s, box-shadow .14s;
}
.pe-btn svg { width: 16px; height: 16px; }
.pe-btn:hover { background: var(--panel-inset); color: var(--accent); }
.pe-btn.danger:hover { background: var(--fault); color: #fff; }

/* Konfigurieren ist die zentrale Aktion des Bearbeiten-Modus und war als graues
   Glyph in der Werkzeugauflage kaum zu finden – wie auf dem Dashboard bekommt es
   ein echtes Icon und Akzentfarbe. */
.pe-btn-cfg {
    color: var(--accent);
    background: color-mix(in srgb, var(--accent) 16%, transparent);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent);
}
.pe-btn-cfg:hover {
    color: var(--on-accent); background: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 26%, transparent);
}

.pe-grip { color: var(--text-faint); cursor: grab; font-size: 12px; line-height: 1; }
.pe-dim { font-family: var(--font-mono); font-size: 10px; color: var(--text-dim); margin-left: auto; }

/* Schmale Panels: die Größenleiste zeigt nur noch den Griff. */
@media (max-width: 1500px) {
    .widget.editing .pe-dim { display: none; }
}

/* Die Werkzeugauflagen liegen über dem Panel und würden die Ziehgriffe an der
   rechten und unteren Kante überdecken – deshalb enden sie ein Stück vor dem Rand. */
.widget.editing .pe-tools { right: 14px; }
.widget.editing .pe-size { left: 8px; right: 14px; bottom: 13px; }

/* Während eines Zieh- oder Größenvorgangs treten die Auflagen zurück: sie gehören
   zum ruhenden Zustand und lenken sonst vom Zielbereich ab. */
.widget.dragging .pe-tools, .widget.dragging .pe-size,
.widget.resizing .pe-tools, .widget.resizing .pe-size { opacity: 0; transition: opacity .12s; }

/* ---------- Resultate: Tabellenansichten ---------- */
.res-cols { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 6px; }
.res-col {
    display: flex; align-items: center; gap: 8px; padding: 6px 8px;
    border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel-inset);
}
.res-col.on { border-color: var(--accent); }
.res-col-name { font-size: 12.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Enge Zeilen: mehr Bundles je Bildschirm, gleiche Lesbarkeit. */
.res-table.res-compact td { padding-top: 3px; padding-bottom: 3px; }
.res-table.res-compact .chip { height: 18px; padding: 0 6px; font-size: 10px; }

/* ---------- Resultate: Auslöser (OnEvent) ----------
   Die Art des Auslösers steht als Plakette, der Name des Triggers daneben. Beides in
   einer Zeile: die Spalte steht zwischen Messaufgabe und Flotte und darf die Zeilenhöhe
   der Tabelle nicht bestimmen. Reicht die Breite nicht, wird der Name gekürzt – die
   Plakette bleibt, weil sie die eigentliche Auskunft ist. */
.res-onevent { display: flex; align-items: center; gap: 6px; min-width: 0; }
.res-onevent .chip { height: 20px; padding: 0 8px; font-size: 10.5px; gap: 4px; }
.res-onevent-icon { font-family: var(--font-body); font-size: 11px; line-height: 1; opacity: .85; }
/* Die Triggernamen sind frei vergeben und werden lang ("Start_diagscript_with_
   left_button_on_rdongle"). Ungekürzt schöbe ein einziger davon die halbe Tabelle nach
   rechts – der volle Name steht im Tooltip der Zelle. */
.res-onevent-name {
    font-family: var(--font-mono); font-size: 11px; color: var(--text-dim);
    min-width: 0; max-width: 22ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.res-table.res-compact .res-onevent .chip { height: 18px; font-size: 10px; }

/* Im Detaildialog sitzt dieselbe Plakette in einem Feldrahmen – dort ohne Kürzung,
   der Dialog ist breit genug. */
.res-onevent-field { display: flex; align-items: center; gap: 8px; overflow: hidden; }
.res-onevent-field .chip { flex: none; }
.res-onevent-field .res-onevent-name { max-width: none; font-size: 12px; }

/* ---------- Resultate: Position auf OpenStreetMap ----------
   Die Spalte ist rechtsbündig, der Link richtet sich deshalb nach rechts aus. Er sieht
   im Ruhezustand aus wie der Text vorher – erst beim Zeigen tritt er als Link hervor;
   in einer Tabelle voller Zahlen wäre eine durchgehend blaue Spalte nur Unruhe. */
.res-geo {
    display: inline-flex; align-items: center; justify-content: flex-end; gap: 5px;
    color: var(--text); text-decoration: none; font-size: 11px; white-space: nowrap;
}
.res-geo:hover { color: var(--accent); text-decoration: underline; }
.res-geo-pin { font-size: 12px; line-height: 1; color: var(--accent); opacity: .75; }
.res-geo:hover .res-geo-pin { opacity: 1; }

/* Im Detaildialog steht die Koordinate links im Feld und der Kartenlink rechts davon. */
.res-geo-field { display: flex; align-items: center; gap: 8px; font-size: 13px; }
.res-geo-field .res-geo { font-size: 12px; color: var(--accent); }

.live-layout { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 16px; align-items: start; }
.live-layout.runs-off { grid-template-columns: minmax(0, 1fr); }
.live-main { min-width: 0; }
.live-runs { position: sticky; top: 74px; }

/* ---------- Panel-Grundgerüst ---------- */
.lp { display: flex; flex-direction: column; justify-content: space-between; height: 100%; gap: 8px; }
.lp-value { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
/* Der Messwert war fest auf 2.1rem. In einem schmalen Panel lief er dadurch aus dem
   Rahmen – mit Einheit daneben und vierstelliger Zahl reicht die Breite nicht. cqw
   bezieht sich auf die Breite des Widgets (container: widget), die Grenzen halten ihn
   lesbar: nie kleiner als 1.15rem, nie größer als bisher. */
.lp-number {
    font-family: var(--font-mono); font-variant-numeric: tabular-nums;
    /* Alle drei Eckwerte tragen denselben Faktor: die Zahl skaliert weiter mit der
       Panelbreite, die Einstellung verschiebt nur das Fenster. Wuerde nur der Mittelwert
       skaliert, liefe die Groesse gegen die feste Obergrenze und die Einstellung haette
       ab einer bestimmten Panelbreite keine Wirkung mehr. */
    font-size: clamp(calc(1.15rem * var(--lp-num-scale, 1)),
                     calc(11cqw  * var(--lp-num-scale, 1)),
                     calc(2.1rem * var(--lp-num-scale, 1)));
    font-weight: 700;
    letter-spacing: -0.02em; line-height: 1;
    min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lp-number.sm { font-size: clamp(0.95rem, 6cqw, 1.25rem); }
.lp-unit { font-family: var(--font-mono); font-size: 0.72rem; color: var(--text-dim); margin-left: 0.3em; }

/* ---------- Lage des Messwerts ----------
   Ohne Klasse steht die Zahl links oben – so sahen die Panels aus, bevor die Lage
   wählbar war. Die vier Ecken entstehen aus zwei Angaben: waagerecht über
   justify-content im Wertblock, senkrecht über die Reihenfolge der Flex-Kinder. Die
   Fußzeile (Zustand und Alter) wandert dabei mit ans andere Ende, statt zwischen Wert
   und Panelrand zu klemmen. */
.lp-pos-tr .lp-value,
.lp-pos-br .lp-value { justify-content: flex-end; }

/* Unten: Fuß nach oben, Wert nach unten. Beim Balken-Panel bleibt der Balken dazwischen
   – der Wert steht dann unter seiner eigenen Skala. */
.lp-pos-bl .lp-foot,
.lp-pos-br .lp-foot { order: -1; }
.lp-pos-bl .lp-value,
.lp-pos-br .lp-value { order: 1; }

/* Mittig: der Wertblock nimmt den freien Platz und richtet sich darin in beiden
   Achsen mittig aus. Als Grid und nicht als Flexbox, weil place-content auch senkrecht
   zentriert; align-items: baseline hält die Einheit dabei auf der Grundlinie der Zahl. */
.lp-pos-c .lp-value {
    flex: 1; min-height: 0;
    display: grid; grid-auto-flow: column; place-content: center; align-items: baseline;
}
.lp-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-width: 0; }
.lp-badge {
    font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.08em; text-transform: uppercase;
    padding: 2px 7px; border-radius: 20px; border: 1px solid currentColor; flex: none;
}
/* Fußzeile: das Abzeichen behält seine Breite, der Text daneben wird gekürzt. Sonst
   schiebt „5 min · vor 12 s" neben „in Ordnung" die Zeile aus dem schmalen Panel. */
.lp-age, .lp-limits {
    font-size: 10.5px; color: var(--text-faint); font-family: var(--font-mono);
    min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Zustandsfarben: eine Regel, alle Paneltypen. */
.lp-ok .lp-number { color: var(--text); }
.lp-ok .lp-badge { color: var(--ok); }
.lp-warn .lp-number { color: var(--warn); }
.lp-alarm .lp-number { color: var(--fault); }
.lp-unknown .lp-number, .lp-unknown .lp-badge { color: var(--text-faint); }

/* Das Abzeichen war nur umrandet und in derselben Farbe wie die Zahl daneben – bei
   einem Alarm zwei blasse rote Elemente. Gefüllt ist es ein Etikett und keine Nuance. */
.lp-warn .lp-badge {
    background: var(--warn); border-color: var(--warn); color: #14100a; font-weight: 700;
}
.lp-alarm .lp-badge {
    background: var(--fault); border-color: var(--fault); color: #fff; font-weight: 700;
}
/* Solange der Grenzwert verletzt ist, schlägt das Etikett. Nicht der ganze Panelinhalt:
   die frühere Regel blendete ihn im Sekundentakt auf 72 % ab und machte damit
   ausgerechnet den Messwert schlechter lesbar, um den es geht. */
.lp-alarm .lp-badge { animation: lp-badge-beat 1.1s ease-in-out infinite; }
.lp-warn .lp-badge { animation: lp-badge-beat 2s ease-in-out infinite; }
@keyframes lp-badge-beat {
    0%, 100% { transform: scale(1); box-shadow: 0 0 0 0 currentColor; }
    50% { transform: scale(1.06); box-shadow: 0 0 0 4px color-mix(in srgb, currentColor 28%, transparent); }
}

/* Tabellen- und Fehlerspeicherzeilen: ein farbiger Balken links statt eines Farbtons,
   der bei zwölf Zeilen untereinander verschwimmt. */
.lp-row.lp-warn { border-left: 3px solid var(--warn); padding-left: 7px; }
.lp-row.lp-alarm { border-left: 3px solid var(--fault); padding-left: 7px; }
.lp-row.lp-alarm .lp-row-dot { animation: lp-dot-beat 1.1s ease-in-out infinite; }
@keyframes lp-dot-beat { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }

/* ---------- Ordner (Gruppen) auf dem Live-Board ----------
   Der Kopf nimmt eine eigene Rasterzeile über die volle Breite. Er ist Beschriftung,
   Bilanz und Ablagefläche in einem: ein Panel darauf fallen zu lassen steckt es in
   diese Gruppe. Ohne ihn unterscheiden sich zwölf gleich aussehende Kacheln nur
   durch die RIN in ihrer Kopfzeile. */
.bg-head {
    grid-column: 1 / -1;
    display: flex; align-items: center; gap: 10px;
    padding: 7px 12px; margin-top: 4px;
    border: 1px solid var(--line);
    border-left: 3px solid var(--accent);
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--panel-raised) 70%, transparent);
    transition: background .15s, border-color .15s, box-shadow .15s;
}
/* Der erste Kopf braucht keinen Abstand nach oben – darüber steht schon die Seite. */
.dash-grid > .bg-head:first-child { margin-top: 0; }

.bg-head.loose { border-left-color: var(--line-strong); opacity: .8; }

.bg-toggle {
    width: 22px; height: 22px; flex: none; border: 0; border-radius: 4px; cursor: pointer;
    background: transparent; color: var(--text-dim);
    display: grid; place-items: center; transition: background .12s, color .12s, transform .18s;
}
.bg-toggle svg { width: 15px; height: 15px; }
.bg-toggle:hover { background: var(--panel-inset); color: var(--accent); }
/* Zugeklappt zeigt der Pfeil nach rechts – dieselbe Sprache wie in einem Dateibaum. */
.bg-head.collapsed .bg-toggle { transform: rotate(-90deg); }

.bg-name {
    font-family: var(--font-ui); font-size: 13px; font-weight: 600;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
}
.bg-dongle {
    font-family: var(--font-mono); font-size: 10.5px; color: var(--text-dim);
    padding: 1px 7px; border-radius: 20px; background: var(--panel-inset); flex: none;
}
.bg-count { font-family: var(--font-mono); font-size: 10.5px; color: var(--text-faint); flex: none; }

/* Bilanz der Gruppe. Sie steht auch am zugeklappten Ordner – gerade dann ist sie
   die einzige Auskunft darüber, ob darin etwas nicht stimmt. */
.bg-chip {
    font-family: var(--font-mono); font-size: 9.5px; font-weight: 700;
    letter-spacing: .05em; text-transform: uppercase;
    padding: 2px 8px; border-radius: 20px; flex: none; color: #fff;
}
.bg-chip.alarm { background: var(--fault); animation: chip-beat 1.1s ease-in-out infinite; }
.bg-chip.warn { background: var(--warn); color: #14100a; }

.bg-tools { margin-left: auto; display: flex; align-items: center; gap: 2px; flex: none; }
/* Ohne Werkzeuge schiebt die Zählung selbst nach rechts, damit der Kopf ausbalanciert
   bleibt statt links zusammenzukleben. */
.bg-head:not(:has(.bg-tools)) .bg-count { margin-left: auto; }

/* Ablagefläche beim Ziehen – dieselbe Sprache wie der Zielrahmen am Panel. */
.dash-grid.is-dragging .bg-head {
    border-style: dashed; border-color: color-mix(in srgb, var(--accent) 40%, var(--line));
}
.bg-head.drop-into {
    border-color: var(--accent); border-style: solid;
    background: color-mix(in srgb, var(--accent) 16%, transparent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent);
}

/* ---------- Enge Panels ----------
   Zwei Rasterzeilen sind 198 Pixel; davon nimmt die Kopfzeile mit Kanal-Mikrolabel
   allein 64. Was dann noch für Messwert und Fußzeile bleibt, reicht nicht – der
   Inhalt wurde abgeschnitten oder bekam eine Bildlaufleiste. In engen Panels tritt
   deshalb alles Beiwerk zurück, damit der Messwert selbst vollständig bleibt. */
.widget.dense-y > .panel > .panel-head { padding: 7px 12px; }
.widget.dense-y > .panel > .panel-body { padding: 9px 12px; }
.widget.dense-y > .panel > .panel-head h3 { font-size: 12.5px; }
.widget.dense-y .lp { gap: 4px; }

/* Das Kanal-Mikrolabel kostet 18 Pixel Kopfhöhe. In einem flachen und zugleich
   schmalen Panel ist das der Unterschied zwischen passend und abgeschnitten – der
   Titel nennt die Sache ohnehin. */
.widget.dense-y.dense-x > .panel > .panel-head .eyebrow { display: none; }

/* Fußzeile: das Zustandsabzeichen bleibt, die Nebenangaben (Alter, Grenzen, Skala)
   weichen. Sie sind erklärend, das Abzeichen ist die Aussage. */
.widget.dense-y .lp-age, .widget.dense-y .lp-limits { display: none; }
.widget.dense-x .lp-limits { display: none; }

/* Verlauf und Tabelle: kleinere Zeilen, damit mehr davon sichtbar bleibt. */
.widget.dense-y .lp-row { padding: 4px 8px; font-size: 11.5px; }
.widget.dense-y .lp-chart { gap: 3px; }

/* Die Meldezeile deckt in einem flachen Panel viel ab – dort nur eine Zeile. */
.widget.dense-y .lp-alarmbar { padding: 5px 8px; gap: 7px; }
.widget.dense-y .lp-alarmbar .lab-sub { display: none; }
.widget.dense-y .lp-alarmbar .lab-ack { height: 22px; padding: 0 8px; font-size: 11px; }

/* Letzte Rettung: was trotz allem nicht passt, wird scrollbar statt abgeschnitten.
   Das gilt schon durch .widget > .panel > .panel-body – hier nur die Bildlaufleiste
   schmal halten, damit sie in einer 200-Pixel-Kachel nicht die halbe Breite frisst. */
.widget > .panel > .panel-body { scrollbar-width: thin; }

/* ---------- Alarm am Panel: Rahmen, Meldezeile, Quittierung ----------
   Der Grenzwertvergleich färbte bisher nur den Messwert im Panelinneren. Auf einem
   Board mit zwölf Kacheln fällt eine rote Zahl unter elf grauen nicht auf – der
   Alarm muss den ganzen Rahmen ergreifen, damit er aus dem Augenwinkel wirkt. */
.widget.alarmed { border-radius: var(--radius); }
.widget.alarmed::after {
    content: ''; position: absolute; inset: -3px; pointer-events: none; z-index: 6;
    border-radius: var(--radius); border: 2px solid transparent;
}
.widget.alarmed-warn::after { border-color: var(--warn); }
.widget.alarmed-alarm::after { border-color: var(--fault); }

/* Offen: der Rahmen pulsiert und wirft einen Schein nach außen. */
.widget.alarmed-live.alarmed-alarm::after { animation: wa-alarm 1.1s ease-in-out infinite; }
.widget.alarmed-live.alarmed-warn::after { animation: wa-warn 2s ease-in-out infinite; }
@keyframes wa-alarm {
    0%, 100% {
        box-shadow: 0 0 0 0 color-mix(in srgb, var(--fault) 55%, transparent),
                    inset 0 0 20px color-mix(in srgb, var(--fault) 16%, transparent);
        border-color: var(--fault);
    }
    50% {
        box-shadow: 0 0 0 7px color-mix(in srgb, var(--fault) 0%, transparent),
                    inset 0 0 34px color-mix(in srgb, var(--fault) 30%, transparent);
        border-color: color-mix(in srgb, var(--fault) 65%, transparent);
    }
}
@keyframes wa-warn {
    0%, 100% {
        box-shadow: 0 0 0 0 color-mix(in srgb, var(--warn) 45%, transparent),
                    inset 0 0 16px color-mix(in srgb, var(--warn) 12%, transparent);
    }
    50% {
        box-shadow: 0 0 0 6px color-mix(in srgb, var(--warn) 0%, transparent),
                    inset 0 0 26px color-mix(in srgb, var(--warn) 22%, transparent);
    }
}

/* Quittiert: der Rahmen bleibt und bleibt farbig, aber er hört auf zu rufen.
   Der Zustand ist weiterhin ablesbar – nur eben ohne Bewegung. */
.widget.alarmed-ack::after { opacity: .5; border-style: dashed; animation: none; }

/* Meldezeile mit der Quittierung. Sie legt sich über den Panelkopf, weil jeder
   Paneltyp sein Inneres selbst gestaltet und keine Stelle dafür freilässt. */
/* Die Meldezeile stand als Auflage ueber dem ganzen Panel (position:absolute, top:0)
   und verdeckte damit Kanalzeile und Titel - also gerade die Angabe, welches Signal
   betroffen ist, und das ausgerechnet im Alarmfall.

   Jetzt steht sie im Fluss zwischen Kopf und Inhalt (Banner-Bereich der Panel-
   Komponente). Den Platz nimmt sie dem Inhalt, und der darf rollen; verdeckt wird
   nichts mehr. flex:none, damit sie in der Spalte des Panels ihre Hoehe behaelt und
   nicht selbst zusammengedrueckt wird. */
.lp-alarmbar {
    flex: none;
    display: flex; align-items: center; gap: 9px;
    padding: 7px 10px;
    color: #fff; font-size: 12px;
    animation: lp-bar-in .22s ease-out;
}
/* Kein Hochschieben mehr: im Fluss wuerde ein translateY(-100%) die Zeile durch den
   Panelkopf wandern lassen. Ein kurzes Aufblenden reicht - dass sie ueberhaupt
   erscheint, ist auffaellig genug. */
@keyframes lp-bar-in { from { opacity: 0; } to { opacity: 1; } }
.lp-alarmbar-alarm { background: var(--fault); }
.lp-alarmbar-warn { background: var(--warn); color: #14100a; }
.lp-alarmbar.acked { opacity: .82; }

.lp-alarmbar .lab-icon { display: grid; place-items: center; flex: none; }
.lp-alarmbar .lab-icon svg { width: 17px; height: 17px; display: block; }
.lp-alarmbar.live .lab-icon { animation: lab-icon-beat 1.1s ease-in-out infinite; }
@keyframes lab-icon-beat {
    0%, 100% { transform: scale(1); opacity: 1; }
    50% { transform: scale(1.18); opacity: .6; }
}

.lp-alarmbar .lab-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
.lp-alarmbar .lab-head {
    font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.lp-alarmbar .lab-sub {
    font-family: var(--font-mono); font-size: 10px; opacity: .85;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.lp-alarmbar .lab-ack {
    flex: none; height: 26px; padding: 0 11px; cursor: pointer;
    border-radius: 20px; border: 1px solid currentColor;
    background: rgba(255,255,255,0.16); color: inherit;
    font-family: var(--font-ui); font-size: 11.5px; font-weight: 700;
    transition: background .14s, transform .05s;
}
.lp-alarmbar .lab-ack:hover { background: rgba(255,255,255,0.32); }
.lp-alarmbar .lab-ack:active { transform: translateY(1px); }
.lp-alarmbar .lab-acked { flex: none; font-size: 11px; font-weight: 700; opacity: .9; }

/* Die Meldezeile legt sich über den Panelkopf, statt ihn nach unten zu schieben. Ein
   padding-top von 44px hätte jedem Panel im Alarmfall 44 Pixel Inhaltshöhe genommen –
   bei einem Panel von einer Rasterzeile (92px) bleibt dafür nichts übrig. Verdeckt
   wird nur das Kanal-Mikrolabel; den Paneltitel trägt die Meldezeile selbst. */

/* Kopfzeilen-Chips und Sammelquittung. */
.chip.chip-alarm { border-color: var(--fault); color: var(--fault); }
.chip.chip-warn { border-color: var(--warn); color: var(--warn); }
.chip.beating { animation: chip-beat 1.1s ease-in-out infinite; }
@keyframes chip-beat {
    0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, currentColor 50%, transparent); }
    50% { box-shadow: 0 0 0 5px color-mix(in srgb, currentColor 0%, transparent); }
}
.btn-ack-all {
    background: var(--fault); border-color: var(--fault); color: #fff; font-weight: 600;
}
.btn-ack-all:hover {
    background: color-mix(in srgb, var(--fault) 84%, #000);
    border-color: color-mix(in srgb, var(--fault) 84%, #000);
}

/* Wer Bewegung abbestellt hat, bekommt Farbe und Rahmen – aber keine Animation.
   Der Alarm bleibt dadurch vollständig erkennbar. */
@media (prefers-reduced-motion: reduce) {
    .lp-alarm .lp-badge, .lp-warn .lp-badge, .lp-row.lp-alarm .lp-row-dot,
    .widget.alarmed::after, .lp-alarmbar, .lp-alarmbar.live .lab-icon, .chip.beating {
        animation: none;
    }
    .widget.alarmed-live.alarmed-alarm::after { box-shadow: 0 0 0 4px color-mix(in srgb, var(--fault) 30%, transparent); }
    .widget.alarmed-live.alarmed-warn::after { box-shadow: 0 0 0 4px color-mix(in srgb, var(--warn) 30%, transparent); }
}

/* ---------- Verlauf ---------- */
.lp-chart { gap: 6px; }
.lp-chart-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.lp-chart-body { flex: 1; min-height: 0; }
.live-trend { border-radius: var(--radius-sm); }

/* ---------- Rundinstrument ---------- */
.lp-gauge { align-items: center; }
.lp-gauge .lp-foot { width: 100%; }

/* ---------- Balken ---------- */
.lp-bar {
    position: relative; height: 14px; border-radius: 7px; overflow: hidden;
    background: var(--panel-inset); border: 1px solid var(--line);
}
.lp-bar-zone { position: absolute; top: 0; bottom: 0; }
.lp-bar-zone.warn { background: var(--warn); opacity: 0.18; }
.lp-bar-zone.alarm { background: var(--fault); opacity: 0.2; }
.lp-bar-fill {
    position: absolute; top: 0; bottom: 0; left: 0; border-radius: 7px;
    transition: width .3s ease-out, background .3s;
}

/* ---------- Ampel ---------- */
.lp-state { flex-direction: row; align-items: center; gap: 12px; justify-content: flex-start; }
.lp-lamp {
    width: 34px; height: 34px; border-radius: 50%; flex: none;
    border: 2px solid var(--line-strong); background: var(--panel-inset);
}
.lp-ok .lp-lamp { border-color: var(--ok); background: var(--ok); box-shadow: 0 0 16px -2px var(--ok); }
.lp-warn .lp-lamp { border-color: var(--warn); background: var(--warn); box-shadow: 0 0 16px -2px var(--warn); }
.lp-alarm .lp-lamp { border-color: var(--fault); background: var(--fault); box-shadow: 0 0 18px -2px var(--fault); }
.lp-state-label { font-family: var(--font-ui); font-size: 14px; font-weight: 600; }

/* ---------- Signaltabelle ---------- */
.lp-table { display: flex; flex-direction: column; gap: 3px; height: 100%; overflow-y: auto; }
/* Die Wertspalte muss mitschrumpfen dürfen: die Zeilen der negativen Antworten tragen
   dort Bedeutung, Dienst und Steuergerät. Mit einem festen "auto" wuchs die Spalte über
   die Panelbreite hinaus und schob die Zeitangabe aus der Zeile. */
.lp-row {
    display: grid; grid-template-columns: 10px minmax(0, 1fr) minmax(0, auto) auto;
    align-items: center; gap: 9px;
    padding: 5px 8px; border-radius: var(--radius-sm); background: var(--panel-inset);
}
.lp-row-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--text-faint); }
.lp-row.lp-ok .lp-row-dot { background: var(--ok); }
.lp-row.lp-warn .lp-row-dot { background: var(--warn); }
.lp-row.lp-alarm .lp-row-dot { background: var(--fault); }
.lp-row-name { font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lp-row-value {
    font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: 12.5px; font-weight: 600;
    min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lp-row.lp-warn .lp-row-value { color: var(--warn); }
.lp-row.lp-alarm .lp-row-value { color: var(--fault); }
.lp-row-limit { font-family: var(--font-mono); font-size: 10px; color: var(--text-faint); white-space: nowrap; }

.lp-note { height: 100%; overflow: auto; white-space: pre-wrap; font-size: 13px; color: var(--text-dim); }

/* ---------- Katalog und Konfiguration ---------- */
/* Kompakte Zeilen statt hoher Kacheln: das Vorschaubild ueber die volle Breite wurde
   rund 145 Pixel hoch, und schon vier Eintraege fuellten den Dialog. Bild links in
   fester Groesse, Text rechts - damit passen alle elf Paneltypen ohne Rollen ins Bild,
   und darum geht es bei einer Auswahl. */
.lp-catalog { display: grid; grid-template-columns: repeat(auto-fill, minmax(224px, 1fr)); gap: 6px; }
.lp-cat {
    display: flex; flex-direction: row; align-items: center; gap: 9px;
    padding: 7px 9px; cursor: pointer; text-align: left; min-width: 0;
    border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
    background: var(--panel-inset); color: var(--text-dim);
    transition: border-color .15s, color .15s;
}
.lp-cat:hover { border-color: var(--accent); color: var(--text); }

/* Das Vorschaubild sitzt auf der Flaeche, die ein Panel spaeter auch hat - dunkler
   Grund, gleicher Radius. Ohne diesen Rahmen schwebten die Formen im Nichts und man
   erkennt nicht, dass es eine Kachel darstellt.
   Feste Breite: waechst sie mit der Spalte, wird das Bild in einem breiten Dialog
   wieder gross und die Zeile hoch. */
.lp-cat-prev {
    flex: none; width: 68px;
    display: block; border-radius: 3px; background: var(--panel);
    border: 1px solid var(--line); padding: 3px 4px;
}
.lp-cat .pp { display: block; width: 100%; height: auto; color: var(--accent); }

.lp-cat-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.lp-cat-name {
    font-family: var(--font-ui); font-size: 12.5px; font-weight: 600; color: var(--text);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Der Hinweis auf eine Zeile begrenzt - er ist die Nebenangabe, und ungleich hohe
   Zeilen machen aus dem Raster ein Treppenmuster. Vollstaendig steht er im Tooltip. */
.lp-cat-note {
    font-size: 10px; color: var(--text-faint); line-height: 1.35;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.lp-series { border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 9px; display: flex; flex-direction: column; gap: 8px; }

/* Die RIN unter der Fahrzeugauswahl: sie wird gebraucht, wenn etwas nicht stimmt, und
   soll bis dahin nicht mit dem Fahrzeugnamen um Aufmerksamkeit streiten. Leer bleibt
   die Zeile ohne Höhe – sonst springt der Dialog beim ersten Auswählen. */
.lp-rin { font-family: var(--font-mono); font-size: 10.5px; color: var(--text-faint); }
.lp-rin:empty { display: none; }
/* Vorlagenauswahl. Zwei Spalten ab 620 Pixel: sechs Kacheln untereinander zwingen zum
   Rollen, und dann liegt die zuletzt gelesene Vorlage außerhalb des Bildes, während man
   die erste bewertet. */
/* auto-fit statt fester Umbruchbreite: die Kacheln entscheiden selbst, ob zwei
   nebeneinander passen. Eine Medienabfrage auf die Fensterbreite war hier ohnehin die
   falsche Groesse - massgeblich ist die Breite des Dialogs, nicht die des Bildschirms. */
.lp-tpls { display: grid; grid-template-columns: repeat(auto-fit, minmax(258px, 1fr)); gap: 8px; }

.lp-tpl {
    display: flex; align-items: flex-start; gap: 10px; padding: 10px 12px; cursor: pointer;
    border: 1px solid var(--line-strong); border-radius: var(--radius-sm); background: var(--panel-inset);
    transition: border-color .15s, background .15s;
    /* Ohne das schiebt die Kachel den Dialog auf. Ein Grid-Item darf von sich aus nicht
       schmaler werden als sein Min-Content - und die Signalzeile darin stand auf nowrap,
       ihre Min-Content-Breite war also der ganze Text. Beide Spalten zusammen waren
       damit breiter als der Dialog, und nichts konnte nachgeben. */
    min-width: 0;
}
.lp-tpl > input[type=radio] { flex: none; margin-top: 2px; }
.lp-tpl:hover { border-color: var(--accent); }
.lp-tpl.active { border-color: var(--accent); background: var(--accent-soft); }
.lp-tpl-icon { font-size: 17px; width: 22px; text-align: center; flex: none; color: var(--accent); line-height: 1.35; }
.lp-tpl-head { display: flex; align-items: baseline; gap: 8px; justify-content: space-between; min-width: 0; }
.lp-tpl-name {
    font-family: var(--font-ui); font-size: 13px; font-weight: 600;
    min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lp-tpl-count { font-family: var(--font-mono); font-size: 10px; color: var(--text-faint); flex: none; }
.lp-tpl-note { font-size: 11.5px; color: var(--text-dim); line-height: 1.4; }
/* Die Signalnamen umbrechen jetzt, statt abgeschnitten zu werden. Einzeilig gekuerzt war
   der laengste Fall schon nach dem zweiten Namen zu Ende und damit wertlos; zwei Zeilen
   zeigen ihn ganz, und der Deckel haelt die Kacheln trotzdem gleich hoch.
   overflow-wrap: anywhere ist noetig, weil ein einzelner Signalname laenger sein kann
   als die Spalte und sonst selbst wieder ueberstuende. */
.lp-tpl-sig {
    font-family: var(--font-mono); font-size: 10px; color: var(--text-faint);
    line-height: 1.45; overflow-wrap: anywhere;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

/* ---------- Messläufe ---------- */
.lr { display: flex; flex-direction: column; gap: 12px; }
.lr-start { display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end; }
.lr-runs { display: flex; flex-direction: column; gap: 8px; }
.lr-run {
    border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel-inset);
    padding: 9px 10px; display: flex; flex-direction: column; gap: 6px;
}
.lr-run.active { border-color: var(--accent); }
.lr-run-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.lr-run-name { font-family: var(--font-ui); font-size: 12.5px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lr-run-state {
    flex: none; font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.08em; text-transform: uppercase;
    padding: 2px 7px; border-radius: 20px; border: 1px solid currentColor;
}
.lr-run-state.ok { color: var(--accent); }
.lr-run-state.warn { color: var(--warn); }
.lr-run-state.alarm { color: var(--fault); }
.lr-run-state.done { color: var(--ok); }
.lr-run-meta, .lr-run-foot {
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
    font-size: 11px; color: var(--text-dim);
}
.lr-run-meta span { font-family: var(--font-mono); }

.lr-bar { position: relative; height: 8px; border-radius: 4px; background: var(--panel); border: 1px solid var(--line); overflow: hidden; }
.lr-bar-fill { position: absolute; top: 0; bottom: 0; left: 0; background: var(--accent); border-radius: 4px; transition: width .9s linear; }
.lr-bar.over .lr-bar-fill { background: var(--warn); }
/* Ohne erwartete Laufzeit gibt es keinen Anteil – der Streifen zeigt nur „aktiv". */
.lr-bar-fill.indeterminate { width: 35%; animation: lr-sweep 1.8s ease-in-out infinite; transition: none; }
@keyframes lr-sweep { 0% { left: -35%; } 100% { left: 100%; } }
@media (prefers-reduced-motion: reduce) { .lr-bar-fill.indeterminate { animation: none; left: 0; } }

.lr-flow { font-size: 10.5px; font-family: var(--font-mono); }
.lr-flow.on { color: var(--ok); }
.lr-flow.off { color: var(--text-faint); }
.lr-age { font-family: var(--font-mono); font-size: 10.5px; color: var(--text-faint); }

@media (max-width: 1200px) {
    .live-layout { grid-template-columns: minmax(0, 1fr); }
    .live-runs { position: static; }
}

/* ---------- Hub-Analyse: Mitschnitt ---------- */
/* Zwei Spalten: links der schmale Strom-Wähler, rechts das Paket. Die Liste bekommt ein
   festes Maß, weil sie sonst mit jedem hinzukommenden Fahrzeugnamen anders breit wäre
   und der JSON-Block daneben bei jedem Takt springt. */
.hm { display: grid; grid-template-columns: 280px minmax(0, 1fr); gap: 14px; align-items: start; }
@media (max-width: 900px) { .hm { grid-template-columns: 1fr; } }

.hm-list .panel-body { display: flex; flex-direction: column; gap: 4px; max-height: 70vh; overflow: auto; }

.hm-dongle {
    display: flex; align-items: center; gap: 9px; width: 100%; text-align: left; cursor: pointer;
    padding: 8px 10px; border: 1px solid transparent; border-radius: var(--radius-sm);
    background: transparent; color: inherit; font: inherit;
    transition: background .12s, border-color .12s;
}
.hm-dongle:hover { background: var(--panel-inset); }
.hm-dongle.active { border-color: var(--accent); background: var(--accent-soft); }

/* Der Punkt ist die eigentliche Aussage der Zeile: sendet das Gerät gerade oder nicht.
   Er pulsiert nur im laufenden Fall – ein Blinken für Stille wäre ein Alarm, und Stille
   ist hier oft der Normalzustand. */
.hm-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.hm-dot.live { background: var(--ok); animation: hm-pulse 1.6s ease-in-out infinite; }
.hm-dot.silent { background: var(--text-faint); }
@keyframes hm-pulse {
    0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--ok) 55%, transparent); }
    50% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--ok) 0%, transparent); }
}

/* Feste Spalten in der Zeile. Vorher waren Name und Zahlen zwei flex-Kinder ohne Mass:
   sobald aus "9 s" ein "12 s" wurde, wanderte die Zahlenspalte, und mit ihr der
   abgeschnittene Fahrzeugname daneben - die ganze Liste zuckte im Sekundentakt. */
.hm-dongle { display: grid; grid-template-columns: 8px minmax(0, 1fr) 56px; }
.hm-ident { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.hm-nums { display: flex; flex-direction: column; gap: 2px; align-items: flex-end; }

.hm-car { font-family: var(--font-ui); font-size: 12.5px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Die CAR-ID führt die Zeile an: Unter ihr wird das Fahrzeug in der Fahrzeugverwaltung
   geführt, und mit ihr wird ein auffälliger Strom weitergemeldet. Die Nummer steht in
   Ziffern gleicher Breite, damit die Liste beim Aktualisieren nicht atmet. */
.hm-carid {
    font-family: var(--font-mono); font-size: 13px; font-weight: 600; color: var(--text);
    font-variant-numeric: tabular-nums;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Die Beschriftung gehört zur Nummer, trägt aber nicht die Aussage - sie steht deshalb
   klein und gesperrt davor, wie die Spaltenköpfe im übrigen Portal. */
.hm-cap {
    font-size: 9.5px; letter-spacing: 0.1em; text-transform: uppercase;
    color: var(--text-faint); font-weight: 500; margin-right: 6px;
}
.hm-vin { font-family: var(--font-ui); font-size: 11px; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hm-rin { font-family: var(--font-mono); font-size: 10px; color: var(--text-faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Ziffern gleicher Breite: sonst ist "1.0/s" schmaler als "0.8/s" und die Spalte
   atmet bei jeder Aktualisierung. */
.hm-rate, .hm-age { font-family: var(--font-mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
.hm-rate { font-size: 11px; color: var(--accent); }
.hm-age { font-size: 10px; color: var(--text-faint); }

.hm-meta {
    display: flex; flex-wrap: wrap; gap: 6px 18px; margin-bottom: 12px;
    padding-bottom: 12px; border-bottom: 1px solid var(--line); font-size: 11.5px; color: var(--text-dim);
}
.hm-meta b { font-weight: 500; color: var(--text-faint); margin-right: 5px; }
.hm-meta .mono { color: var(--text); }
.hm-warn .mono, .hm-warn b { color: var(--warn); }

.hm-hint { font-size: 11.5px; color: var(--text-dim); margin-bottom: 10px; line-height: 1.45; }

/* Der Rohtext bleibt unumbrochen: JSON lebt von der Einrückung, und ein Umbruch mitten
   in einer langen Response-Hexkette macht sie unlesbar. Deshalb rollt der Block
   waagerecht statt umzubrechen. */
.hm-json {
    margin: 0; padding: 12px 14px; max-height: 58vh; overflow: auto;
    background: var(--panel-inset); border: 1px solid var(--line); border-radius: var(--radius-sm);
    font-family: var(--font-mono); font-size: 11.5px; line-height: 1.55; color: var(--text);
    white-space: pre; tab-size: 2;
}

.hm-signals { font-size: 12px; }
.hm-signals td { padding: 6px 12px; }
/* Zahlen rechtsbuendig und gleich breit: nur so kann man eine Spalte ueberfliegen,
   statt jede Zeile einzeln zu lesen. */
.hm-signals .hm-val { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* Gliederung wie im JSON. Die beiden Ebenen unterscheiden sich in Farbe und Einzug,
   nicht in der Schriftgroesse - ein Block ist keine Ueberschrift, sondern der Name
   eines Knotens, und der steht im selben Mass wie sein Inhalt. */
.hm-signals .hm-grp > td {
    padding: 9px 12px 5px; background: var(--panel-raised);
    border-bottom: 1px solid var(--line-strong);
}
.hm-signals .hm-grp-name {
    font-family: var(--font-mono); font-size: 11.5px; font-weight: 600; color: var(--accent);
}
.hm-signals .hm-grp-note { font-size: 10.5px; color: var(--text-faint); margin-left: 8px; }

.hm-signals .hm-sub > td { padding: 6px 12px 4px 26px; background: var(--panel-inset); }
.hm-signals .hm-sub-name {
    font-family: var(--font-mono); font-size: 11px; font-weight: 600; color: var(--text);
}

/* Der Einzug macht die Zugehoerigkeit sichtbar, ohne dass die Zeile eine Klammer oder
   ein Baumzeichen braucht. */
.hm-signals .hm-sig { padding-left: 26px; }
.hm-signals .hm-sig-deep { padding-left: 40px; }

/* Ein Block soll nicht mitten im Seitenumbruch der Rollflaeche haengen. */
.hm-signals tbody + tbody .hm-grp > td { border-top: 1px solid var(--line); }

.hm-frames { display: flex; flex-direction: column; gap: 2px; max-height: 52vh; overflow: auto; }
.hm-frame {
    display: grid; grid-template-columns: auto 1fr auto auto; gap: 12px; align-items: center;
    width: 100%; text-align: left; cursor: pointer; padding: 6px 10px;
    border: 1px solid transparent; border-radius: var(--radius-sm);
    background: transparent; color: var(--text-dim); font-family: var(--font-mono); font-size: 11px;
}
.hm-frame:hover { background: var(--panel-inset); color: var(--text); }
.hm-frame.active { border-color: var(--accent); background: var(--accent-soft); color: var(--text); }
.hm-frame-time { color: var(--text); }
.hm-frame-type { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hm-frame-size, .hm-frame-seq { color: var(--text-faint); }

.hm-pinned {
    display: flex; align-items: center; gap: 8px; margin-top: 10px; padding: 7px 10px;
    border: 1px solid var(--accent); border-radius: var(--radius-sm); background: var(--accent-soft);
    font-size: 11.5px; color: var(--text-dim);
}

/* ---------- Kopfzeile Live Measurements ---------- */
/* Die Werkzeugleiste soll einzeilig bleiben. Sie steht über jedem Board, und bricht sie
   mitten in den Knöpfen um, verschiebt schon ein hinzukommender Alarm-Chip das ganze
   Raster nach unten – man verliert die Kachel, auf die man gerade sieht.
   Der Platz wird deshalb in dieser Reihenfolge geholt:
     1. der Boardtitel schrumpft (Auslassungspunkte, voller Name im Tooltip),
     2. die Board-Auswahl schrumpft bis 110 Pixel,
     3. die Leiste rutscht als geschlossener Block unter den Titel,
     4. erst auf schmalen Geräten bricht sie selbst um. */
.section-head.lm-head { flex-wrap: wrap; row-gap: 10px; }

.lm-head > .lm-title { flex: 1 1 auto; min-width: 0; }
.lm-head .lm-title h1 { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Umbrechen statt abschneiden. Vorher stand hier nowrap zusammen mit min-width:0 –
   eine Kombination, bei der die Leiste beliebig schmal werden darf, ihre Kinder aber
   nicht mitschrumpfen: Was nicht mehr passte, lief nach rechts aus dem Bild und war
   weg. Der Bearbeiten-Knopf, letzter in der Reihe, traf es zuerst.

   Ohne min-width zählt die Leiste ihre eigene Mindestbreite, und .lm-head (flex-wrap)
   setzt sie eine Zeile tiefer, bevor es eng wird. Reicht auch das nicht, bricht sie
   in sich um – rechtsbündig, damit die Knöpfe an der Kante bleiben, an der man sie
   sucht. */
.lm-bar {
    display: flex; align-items: center; gap: 6px;
    flex-wrap: wrap; justify-content: flex-end; row-gap: 6px;
    margin-left: auto;
}
/* Knöpfe und Chips tragen Text – schrumpfen dürfen sie nicht, sonst werden sie
   abgeschnitten statt umzubrechen. */
.lm-bar > * { flex: none; }

/* Auswahl und Stern in einer Fassung: eine gemeinsame Kante, ein gemeinsamer Block. */
.lm-pick { display: inline-flex; align-items: center; flex: 0 1 auto; min-width: 0; }
.lm-pick.with-star .lm-boards { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.lm-pick .btn-star {
    border-top-left-radius: 0; border-bottom-left-radius: 0;
    margin-left: -1px;   /* die beiden Rahmen teilen sich einen Strich */
}
.lm-bar .lm-boards { flex: 0 1 230px; min-width: 130px; height: 30px; }

/* Der Strich zwischen Auskunft und Bedienung. Kein Abstand allein: sechs Pixel mehr
   Luft liest sich als Zufall, ein Strich als Grenze. */
.lm-sep { flex: none; width: 1px; height: 18px; background: var(--line-strong); margin: 0 3px; }

/* Ein Zeichen vor der Beschriftung, überall gleich groß und gleich weit weg – vorher
   steckte das Pluszeichen mal im übersetzten Text („＋ Ordner"), mal in einer eigenen
   Hülle, und die Knöpfe standen entsprechend unterschiedlich weit ein. */
.btn .btn-ico {
    flex: none; width: 1.15em; height: 1.15em;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 1.1em; line-height: 1;
}

/* Ein Schalter, der an ist. Nur Rahmen und ein Hauch Fläche, die Schrift bleibt
   normal: „eingeschaltet" ist ein Nebensatz. Die kräftigen Farben in dieser Leiste
   gehören dem Speichern (gefüllt), den offenen Meldungen (rot) und dem Startboard
   (gold) – ein vierter lauter Ton machte alle vier leiser. */
.lm-bar .btn.is-on {
    border-color: var(--accent);
    background: var(--accent-soft);
}

/* ---------- Startboard-Stern ---------- */
/* Zwei Zustände an einem Knopf, und beide müssen ohne Tooltip lesbar sein: nicht
   markiert ist ein leerer Stern in der gedämpften Schriftfarbe, markiert ein gefüllter
   in der Warnfarbe – in jedem der Themes der goldene Ton, und der einzige Ort in dieser
   Leiste, an dem er vorkommt.

   Die Beschriftung wechselt beim Klicken nicht mit („Als Startboard“ / „Startboard“):
   der Knopf säße sonst bei jedem Umschalten an einer anderen Stelle, und die halbe
   Leiste rutschte mit. Den Zustand tragen Füllung und Farbe.

   Kein btn-primary und kein btn-add: in dieser Leiste gehören die gefüllten Knöpfe dem
   Speichern und dem Hinzufügen. Der Stern ist eine Markierung, kein Auftrag. */
.btn.btn-star .btn-ico { color: var(--text-dim); }
.btn.btn-star:hover .btn-ico { color: var(--warn); }

.btn.btn-star.on {
    border-color: var(--warn);
    color: var(--warn);
    background: var(--panel-raised);
}
.btn.btn-star.on .btn-ico { color: var(--warn); }

/* Während geschrieben wird, bleibt der Knopf stehen und wird nur blass – ein
   verschwindender Stern sähe aus, als hätte der Klick etwas gelöscht. */
.btn.btn-star:disabled { opacity: .55; cursor: default; }

/* Auf schmalen Geräten darf auch die Auswahl auf eine eigene Zeile: nebeneinander
   blieben von Board-Name und Stern zusammen keine lesbaren 130 Pixel übrig. */
@media (max-width: 720px) { .lm-pick { flex: 1 1 100%; } .lm-pick .lm-boards { flex: 1 1 auto; } }

/* ---------- Vollbild der Boardfläche ----------
   Dieselben Panels, nur als Auflage über dem Fenster – wie beim Diagramm (.ts-fs) und
   mit demselben z-index: Dialoge (100), Slideover (90) und Meldungen (200) liegen
   darüber und bleiben bedienbar.

   Für das Telefon gebaut. Dort kosten Navigationsleiste, Topbar, Seitenkopf und
   Adressleiste zusammen rund 40 Prozent der Höhe – bei vier Kacheln auf einem 6-Zoll-
   Schirm ist das der Unterschied zwischen zwei sichtbaren Werten und allen vier.

   100dvh und nicht 100vh: die Einheit vh rechnet auf mobilen Browsern mit ausgefahrener
   Adressleiste, und das Board stünde um deren Höhe zu tief im Bild. */
.lm-stage { outline: none; }
.lm-stage.lm-fs {
    position: fixed; inset: 0; z-index: 60;
    height: 100dvh; overflow: auto;
    padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
    background: var(--bg);
    /* Auf iOS gibt es kein echtes Vollbild – die Statusleiste bleibt stehen und würde
       sonst die erste Kachelreihe anschneiden. */
    padding-top: calc(10px + env(safe-area-inset-top));
}

/* Die Kopfzeile der Auflage: schmal, aber sie trägt alles, was im Vollbild sonst
   verdeckt wäre – Board, Verbindung, offene Meldungen und der Weg zurück. Sie bleibt
   beim Rollen stehen; auf einem langen Board wäre sie sonst nach der zweiten Kachel
   fort, und mit ihr die einzige Auskunft darüber, ob die Zahlen noch aktuell sind. */
.lm-fsbar {
    position: sticky; top: 0; z-index: 2;
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    padding: 0 2px 10px;
    background: var(--bg);
}
.lm-fsname {
    font-family: var(--font-ui); font-weight: 600; font-size: 14px;
    color: var(--text); min-width: 0; max-width: 45vw;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lm-fsconn { font-size: 11.5px; color: var(--fault); }
.lm-fsgap { flex: 1 1 auto; }

/* ---------- Boardfläche auf kleinen Geräten ----------
   Das Raster hat zwölf Spalten, und ein Panel steht auf der Zelle, auf die es gelegt
   wurde. Auf 390 Pixel Gerätebreite ist eine Spalte 24 Pixel breit – diese Anordnung
   lässt sich auf einem Telefon nicht zeigen.

   Unterhalb von 820 Pixeln fällt das Raster deshalb auf zwei Spalten zusammen, und die
   Panels werden der Reihe nach gesetzt. Zwei und nicht eine: Ein Messwertpanel trägt
   eine Zahl, ein Abzeichen und sonst nichts – über die volle Telefonbreite gezogen ist
   es ein Band mit einer Handbreit Luft daneben, und auf den Schirm passen drei davon.
   Nebeneinander passen sechs, und ein Board mit zwölf Kacheln ist damit in zwei Blicken
   gelesen statt in vier.

   Welche Kachel sich die halbe Breite nimmt und welche die ganze bekommt, entscheidet
   die Seite (MobileSpanClass in LiveMeasurements.razor) und schreibt es als .m-half
   oder .m-full ans Element: Wer ein Panel auf höchstens sechs der zwölf Spalten gelegt
   hat, hat es als halbe Breite gemeint. Tabellen, Karten, Heatmap und die
   Messungssteuerung bekommen unabhängig davon die ganze Zeile – von ihren Spalten
   bliebe in 180 Pixeln nichts übrig.

   Die Rasterplätze der Panels stehen als Inline-Stil am Element – nur !important kommt
   dagegen an. Die Reihenfolge übernimmt das Dokument, und die stimmt: Layout() gibt die
   Panels eines Abschnitts nach Zeile und Spalte sortiert aus.

   Die Höhe kommt aus derselben Zahl wie am großen Schirm (--gh, die Rasterzeilen des
   Panels), nur mit kleinerem Zeilenmaß und gedeckelt: ein Panel über sechs Zeilen füllt
   sonst den ganzen Schirm, und man rollt an einem einzigen Wert entlang. Die halbe
   Kachel ist flacher als die ganze – sie zeigt weniger, also braucht sie weniger. */
@media (max-width: 820px) {
    .dash-grid.lm-grid {
        /* Die Marke, an der js/dashgrid.js erkennt, dass das 12-Spalten-Raster gerade
           nicht gilt (dasselbe Signal wie beim Dashboard). Ohne sie rechnete eine
           Zieh- oder Kantengeste hier weiter in zwölf Spalten und schriebe das Ergebnis
           in die gespeicherte Anordnung – das Board des großen Schirms wäre zerlegt,
           ohne dass jemand es dort angefasst hätte. */
        --stacked: 1;

        /* Das Zeilenmaß der beiden Kachelbreiten. Als Variable, damit die Abfrage für
           ganz schmale Geräte weiter unten nur zwei Zahlen zu ändern hat. */
        --m-row: 46px;
        --m-row-full: 56px;

        grid-template-columns: repeat(2, minmax(0, 1fr));
        grid-auto-rows: auto;
        gap: 8px;
    }

    /* Volle Zeile: der Grundfall und alles ohne Klasse. */
    .dash-grid.lm-grid > .widget {
        grid-column: 1 / -1 !important;
        grid-row: auto !important;
        min-height: clamp(110px, calc(var(--gh, 2) * var(--m-row-full)), 52dvh);
    }

    /* Die halbe Kachel – zwei davon nebeneinander. „auto“ und nicht „1 / -1“, damit sie
       sich die Spalte nimmt, die gerade frei ist; eine volle Kachel dazwischen fängt
       dadurch von selbst wieder links an. */
    .dash-grid.lm-grid > .widget.m-half {
        grid-column: auto / span 1 !important;
        min-height: clamp(94px, calc(var(--gh, 2) * var(--m-row)), 38dvh);
    }

    /* Der Ordnerkopf geht immer über beide Spalten – er ist die Überschrift für das,
       was darunter steht. */
    .dash-grid.lm-grid > .bg-head {
        grid-column: 1 / -1 !important;
        grid-row: auto !important;
        padding: 5px 9px; gap: 7px; margin-top: 2px;
    }
    .dash-grid.lm-grid > .bg-head .bg-name { font-size: 12px; }
    .dash-grid.lm-grid > .bg-head .bg-dongle,
    .dash-grid.lm-grid > .bg-head .bg-count { font-size: 9.5px; }
    .dash-grid.lm-grid > .bg-head .bg-toggle { width: 20px; height: 20px; }

    /* Die Ablagefläche des Bearbeiten-Modus ergibt gestapelt keinen Sinn – auf dem
       Telefon wird nicht per Zeiger angeordnet. */
    .dash-grid.lm-grid > .dash-pad { display: none; }

    /* Das Rasterbild des Bearbeiten-Modus zeigte zwölf Spalten, die es nicht mehr gibt. */
    .dash-grid.lm-grid.is-editing { background-image: none; }

    /* Verschieben und Kanten ziehen sind durch --stacked abgeschaltet – also weg mit
       den Griffen, die sonst etwas versprechen, was nicht passiert. Hinzufügen,
       Einstellen und Entfernen bleiben. */
    .dash-grid.lm-grid .w-resize { display: none; }
    .dash-grid.lm-grid .pe-grip { display: none; }
    .dash-grid.lm-grid .widget.editing { cursor: default; }

    /* ---- Die kompakte Kachel ----
       Alles, was am großen Schirm Luft ist, kostet hier eine Zeile Inhalt. Die Maße
       gelten für beide Breiten gleich: zwei verschieden große Messwerte nebeneinander
       liest man als Fehler, und nebeneinander stehen sie jetzt.

       Die Selektoren tragen .dash-grid mit, damit sie gegen die Regeln für enge Panels
       (.widget.dense-y) gewinnen, ohne sich auf die Reihenfolge im Stylesheet zu
       verlassen. */
    .dash-grid.lm-grid > .widget > .panel > .panel-head { padding: 6px 9px; gap: 6px; }
    .dash-grid.lm-grid > .widget > .panel > .panel-body { padding: 8px 9px; }
    .dash-grid.lm-grid > .widget > .panel > .panel-head h3 { font-size: 12px; }
    .dash-grid.lm-grid > .widget .panel-title { gap: 1px; }
    .dash-grid.lm-grid > .widget .eyebrow { font-size: 8.5px; letter-spacing: 0.1em; }
    .dash-grid.lm-grid > .widget .led.led-xs { gap: 4px; font-size: 9px; }

    /* Die Eck-Ticks sitzen 6 Pixel vom Rand. Bei 9 Pixeln Polsterung liegen sie im Text
       statt in der Ecke – auf einer Kachel dieser Größe sind sie Zierrat, der Platz
       kostet. */
    .dash-grid.lm-grid > .widget > .panel::before,
    .dash-grid.lm-grid > .widget > .panel::after { display: none; }

    /* Der Messwert. cqw bezieht sich auf die Kachelbreite (container: widget), deshalb
       schrumpft die Zahl in der halben Spalte von allein mit – nachzuziehen ist nur die
       Untergrenze: 1,15rem waren in 180 Pixeln mit vierstelliger Zahl und Einheit
       daneben zu breit. Der Faktor aus der Paneleinstellung (--lp-num-scale) bleibt an
       allen drei Eckwerten, sonst liefe die Einstellung gegen die feste Obergrenze. */
    .dash-grid.lm-grid > .widget .lp-number {
        font-size: clamp(calc(0.9rem  * var(--lp-num-scale, 1)),
                         calc(12cqw   * var(--lp-num-scale, 1)),
                         calc(1.65rem * var(--lp-num-scale, 1)));
    }
    .dash-grid.lm-grid > .widget .lp-unit { font-size: 0.64rem; }
    .dash-grid.lm-grid > .widget .lp { gap: 4px; }
    .dash-grid.lm-grid > .widget .lp-badge { font-size: 8px; padding: 1px 5px; }
    .dash-grid.lm-grid > .widget .lp-age,
    .dash-grid.lm-grid > .widget .lp-limits { font-size: 9.5px; }

    /* Auf der halben Kachel steht neben dem Zustandsabzeichen kein zweiter Text mehr:
       „12,4 … 14,8 V“ neben „in Ordnung“ ergibt in 180 Pixeln zwei abgeschnittene
       Angaben statt einer lesbaren. Das Alter bleibt – es beantwortet, ob die Zahl
       überhaupt noch gilt. */
    .dash-grid.lm-grid > .widget.m-half .lp-limits { display: none; }

    /* Zeilen und Listen: kleineres Maß, damit mehr davon ins Bild passt. */
    .dash-grid.lm-grid > .widget .lp-table { gap: 2px; }
    .dash-grid.lm-grid > .widget .lp-row { padding: 3px 7px; gap: 6px; }
    .dash-grid.lm-grid > .widget .lp-row-name { font-size: 11px; }
    .dash-grid.lm-grid > .widget .lp-row-value { font-size: 11.5px; }
    .dash-grid.lm-grid > .widget .lp-row-limit { font-size: 9px; }

    /* Ampel, Balken, Zellen und Häufigkeiten. */
    .dash-grid.lm-grid > .widget .lp-lamp { width: 24px; height: 24px; }
    .dash-grid.lm-grid > .widget .lp-state { gap: 9px; }
    .dash-grid.lm-grid > .widget .lp-state-label { font-size: 12px; }
    .dash-grid.lm-grid > .widget .lp-bar { height: 11px; }
    .dash-grid.lm-grid > .widget .lph { gap: 5px; }
    .dash-grid.lm-grid > .widget .lph-cell { font-size: 9px; min-height: 15px; }
    .dash-grid.lm-grid > .widget .lph-i { font-size: 7px; }
    .dash-grid.lm-grid > .widget .lph-stats,
    .dash-grid.lm-grid > .widget .lph-end,
    .dash-grid.lm-grid > .widget .lph-unit { font-size: 9px; }
    .dash-grid.lm-grid > .widget .vb-row {
        grid-template-columns: minmax(44px, 26%) 1fr auto auto; gap: 6px;
    }
    .dash-grid.lm-grid > .widget .vb-label { font-size: 10px; }
    .dash-grid.lm-grid > .widget .vb-track { height: 10px; }
    .dash-grid.lm-grid > .widget .vb-count { font-size: 10px; min-width: 32px; }
    .dash-grid.lm-grid > .widget .vb-share { font-size: 9px; min-width: 32px; }

    /* Ort, Fahrzeug und Notiz. */
    .dash-grid.lm-grid > .widget .lp-gps-value { font-size: 12px; }
    .dash-grid.lm-grid > .widget .lp-gps-sub { font-size: 10px; }
    /* Das Fahrzeugbild deckelt sein eingestelltes Maß. Getrennt nach Lage, weil die
       drei Aufbauten verschiedene Maße führen: oben die Höhe, daneben die Breite, und
       beim füllenden Bild gibt die Rasterzeile die Höhe vor – dort darf hier nichts
       stehen, sonst wäre aus dem füllenden Bild wieder ein festes. */
    .dash-grid.lm-grid > .widget .lp-veh:not(.lp-veh-fill):not(.lp-veh-side):not(.lp-veh-noimg) .lp-veh-shot {
        height: min(var(--veh-img, 120px), 92px);
    }
    .dash-grid.lm-grid > .widget .lp-veh.lp-veh-side .lp-veh-shot { width: min(var(--veh-img, 120px), 92px); }
    .dash-grid.lm-grid > .widget .lp-veh-led-text b { font-size: 11px; }
    .dash-grid.lm-grid > .widget .lp-note-text { font-size: 11.5px; }

    /* Die Meldezeile deckt auf einer Kachel dieser Größe viel ab – auf der halben
       bleiben Symbol, Kopfzeile und der Knopf zum Quittieren. */
    .dash-grid.lm-grid > .widget .lp-alarmbar { padding: 4px 7px; gap: 6px; }
    .dash-grid.lm-grid > .widget .lp-alarmbar .lab-head { font-size: 11px; }
    .dash-grid.lm-grid > .widget .lp-alarmbar .lab-ack { height: 22px; padding: 0 8px; font-size: 11px; }
    .dash-grid.lm-grid > .widget.m-half .lp-alarmbar .lab-sub { display: none; }

    /* Vollbild ist auf dem Telefon der Grund, warum diese Leiste angefasst wird –
       er steht deshalb vorn und nicht hinter fünf anderen Knöpfen. */
    .lm-bar .btn-fs { order: -1; }

    /* Der Seitenkopf nimmt sonst zwei Zeilen für Augenbraue und Titel. */
    .lm-head .eyebrow { display: none; }
    .lm-head .lm-title h1 { font-size: 17px !important; }

    .lm-fsbar { gap: 6px; }
    .lm-fsname { max-width: 40vw; font-size: 13px; }
}

/* Ganz schmale Geräte: die Kacheln dürfen flacher werden und rücken enger zusammen,
   sonst passt selbst im Vollbild nur eine Reihe ins Bild. */
@media (max-width: 430px) {
    .dash-grid.lm-grid { --m-row: 40px; --m-row-full: 48px; gap: 7px; }
    .lm-stage.lm-fs { padding-left: 8px; padding-right: 8px; }
}

/* ============================================================================
   Instrumente, Heatmap und Häufigkeiten
   ============================================================================ */

/* ---------- Rundinstrument und Zeigerinstrument ---------- */
/* Beide Instrumente teilen sich Schriftgrößen und Töne: sie stehen auf demselben
   Board oft nebeneinander, und zwei verschieden große Messwerte in gleich großen
   Kacheln liest man als Fehler.

   Die Fassung des Zeigers ist dieselbe wie die des Bogens (.gauge-wrap weiter oben):
   mittig, an die Panelhöhe gebunden, damit das SVG in einer flachen Kachel nicht
   unten herausläuft. */
.ndl-wrap { display: grid; place-items: center; height: 100%; min-height: 0; overflow: hidden; }
.ndl-wrap > svg { max-height: 100%; }
.ndl { width: 100%; max-width: 210px; height: auto; }

.gauge-readout, .ndl-readout {
    font-family: var(--font-ui); font-size: 19px; font-weight: 700; fill: var(--text);
}
.gauge-unit, .ndl-unit {
    font-family: var(--font-mono); font-size: 7.5px; letter-spacing: 0.12em;
    fill: var(--text-dim);
}
/* Skalenanfang und -ende am Rundinstrument, Teilstrichzahlen am Zeigerinstrument.
   Bewusst klein: sie sind Bezugsgröße, nicht Messwert – wer sie so groß setzt wie
   den Wert, hat drei gleich wichtige Zahlen im Bild und liest keine davon. */
.gauge-end, .ndl-tick {
    font-family: var(--font-mono); font-size: 6.5px; fill: var(--text-faint);
}

/* Der Zeiger wandert, statt zu springen – wie der Wertbogen (.gauge-value). Die Feder ist bewusst leicht überschwingend: ein Zeiger, der
   linear anfährt und abrupt stehenbleibt, sieht aus wie ein Standbild, das
   weitergeschaltet wurde. */
.ndl-hand { transition: transform .5s cubic-bezier(.34,1.32,.44,1); }
.ndl-hand.off { opacity: .28; }

@media (prefers-reduced-motion: reduce) { .ndl-hand { transition: none; } }

/* ---------- Heatmap ---------- */
.lp-heat { display: flex; flex-direction: column; min-height: 0; }
.lph { display: flex; flex-direction: column; gap: 7px; min-height: 0; flex: 1; }

/* Die Felder füllen die Höhe gleichmäßig aus: eine Heatmap, die oben klebt und unten
   Luft lässt, sieht nach halb geladen aus. */
.lph-grid { display: grid; gap: 3px; flex: 1; min-height: 0; align-content: stretch; }

.lph-cell {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 1px; border-radius: 3px; min-height: 18px; min-width: 0;
    font-family: var(--font-mono); font-size: 10px; line-height: 1.1;
    overflow: hidden; position: relative;
}
.lph-v { font-weight: 600; }
.lph-i { font-size: 8px; opacity: .7; }

/* Warnung und Alarm bekommen einen Rahmen und keine andere Füllung: die Füllung
   trägt schon den Messwert. Zwei Aussagen in einem Kanal wären eine zu viel – so
   sagt die Fläche „wie warm" und der Rahmen „zu warm". */
.lph-cell.warn { box-shadow: inset 0 0 0 1.5px var(--warn); }
.lph-cell.alarm { box-shadow: inset 0 0 0 2px var(--fault); }

.lph-legend { display: flex; align-items: center; gap: 6px; flex: none; }
.lph-ramp { flex: 1; height: 6px; border-radius: 3px; min-width: 40px; }
.lph-end, .lph-unit {
    font-family: var(--font-mono); font-size: 9.5px; color: var(--text-dim);
    white-space: nowrap;
}
.lph-unit { color: var(--text-faint); }

/* Derselbe Streifen als Vorschau im Konfigurationsdialog, unter der Auswahlliste. */
.lph-ramp-pick { height: 8px; margin-top: 2px; }

.lph-stats {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap; flex: none;
    font-family: var(--font-mono); font-size: 10px; color: var(--text-dim);
}
.lph-stats b { color: var(--text-faint); font-weight: 500; margin-right: 3px; }
/* Die Spanne ist bei Zellen die Kennzahl, wegen der man hinsieht. */
.lph-stats .lph-delta { color: var(--text); }
.lph-stats .lph-count { margin-left: auto; color: var(--text-faint); }

/* ---------- Häufigkeiten ---------- */
.lp-dist { display: flex; flex-direction: column; min-height: 0; }
.vb { display: flex; flex-direction: column; gap: 6px; flex: 1; min-height: 0; }
.vb-rows { display: flex; flex-direction: column; gap: 4px; overflow: auto; flex: 1; min-height: 0; }

/* Feste Spalten: Beschriftung, Balken, Zahl, Anteil. Ohne sie wandern die Zahlen mit
   der Länge der Beschriftung, und untereinander lässt sich nichts vergleichen. */
.vb-row {
    display: grid; grid-template-columns: minmax(56px, 22%) 1fr auto auto;
    align-items: center; gap: 8px;
}
.vb-label {
    font-size: 11px; color: var(--text); overflow: hidden; text-overflow: ellipsis;
    white-space: nowrap;
}
.vb-track { height: 12px; border-radius: 6px; background: var(--panel-raised); overflow: hidden; }
.vb-fill { display: block; height: 100%; border-radius: 6px; transition: width .35s ease-out; }
.vb-count {
    font-family: var(--font-mono); font-size: 11px; color: var(--text);
    min-width: 44px; text-align: right;
}
.vb-share {
    font-family: var(--font-mono); font-size: 10px; color: var(--text-faint);
    min-width: 44px; text-align: right;
}
/* Der Sammelbalken ist keine Ausprägung, sondern eine Auskunft über die Grenze. */
.vb-row.other .vb-label { color: var(--text-faint); font-style: italic; }

.vb-foot {
    display: flex; align-items: center; gap: 10px; flex: none; flex-wrap: wrap;
    font-family: var(--font-mono); font-size: 10px; color: var(--text-faint);
}
.vb-foot .vb-over { margin-left: auto; color: var(--warn); }

@media (prefers-reduced-motion: reduce) { .vb-fill { transition: none; } }

/* ---------- Verbindungslage Fahrzeug (online / offline) ---------- */
/* Die kleine Variante der Geräte-LED. In der Kanalzeile eines Panels steht schon Text
   in 10 px – eine LED in Normalgröße daneben zöge die Zeile auseinander. */
.led.led-xs { gap: 5px; font-size: 9.5px; }
.led.led-xs .dot { width: 6px; height: 6px; }

/* Die Kanalzeile trägt jetzt Lampe und Text nebeneinander. */
.eyebrow-row { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
.eyebrow-row > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Die Lampe darf nicht mitschrumpfen, wenn der Kanalname zu lang wird - sie ist der
   Teil der Zeile, der auch bei 200 Pixel Panelbreite noch lesbar bleiben muss. */
.eyebrow-row > .led, .lm-panel-link > .led { flex: none; }
.lm-panel-link { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }

.lm-link-age { color: var(--fault); text-transform: none; letter-spacing: 0; }

/* Nur der stille Zustand wird am Rahmen markiert. Wären auch die laufenden Panels
   eingefärbt, hätte man auf einem gesunden Board zwölf farbige Rahmen und sähe den
   einen stummen darunter nicht mehr.
   Gedimmt statt rot umrandet: Rot gehört auf diesem Board den Grenzwertalarmen, und ein
   Fahrzeug, das nicht sendet, ist kein Messwert außerhalb seines Bereichs. */
.panel.lm-offline { opacity: 0.62; }
.panel.lm-offline .lp-number { color: var(--text-dim); }
.panel.lm-nohub { border-color: var(--warn); }


.lr-link { display: inline-flex; align-items: center; gap: 7px; height: 30px; font-size: 11.5px; color: var(--text-dim); white-space: nowrap; }

/* Die Lampe im Gruppenkopf sitzt im vorhandenen Chip. */
.bg-dongle { display: inline-flex; align-items: center; gap: 6px; }

/* ---------- Panel-Dialog: feste Raster statt umbrechender Reihen ---------- */
/* Vorher lagen die Felder in .row mit flex-wrap. Sobald ein Feld eine Zeile mehr
   brauchte - eine Verbindungsmeldung unter der Fahrzeugauswahl, ein laengerer Hinweis -
   wuchs es, und alles dahinter rutschte in die naechste Zeile. Beim Durchprobieren der
   Fahrzeuge sprang so der halbe Dialog. Ein Raster haelt jede Spalte an ihrem Platz,
   unabhaengig davon, was in der Nachbarzelle steht. */
.lp-grid { display: grid; gap: 12px; align-items: start; }
.lp-grid > .field { min-width: 0; }

.lp-grid-2      { grid-template-columns: 1fr 1fr; }
.lp-grid-4      { grid-template-columns: repeat(4, 1fr); }
.lp-grid-5      { grid-template-columns: repeat(5, 1fr); }
.lp-grid-run    { grid-template-columns: 2fr 1fr; }
/* Signal trägt den laengsten Text, Stellen den kuerzesten. */
.lp-grid-signal { grid-template-columns: 2.4fr 0.8fr 0.7fr 1fr; }
.lp-grid-series { grid-template-columns: 2fr 1.6fr 0.8fr 0.7fr auto; }

/* Unter 640 px (schmales Fenster, geteilter Bildschirm) darf gestapelt werden - dort
   ist ein Raster mit fuenf Spalten unlesbar. */
@media (max-width: 640px) {
    .lp-grid-2, .lp-grid-run { grid-template-columns: 1fr; }
    .lp-grid-4, .lp-grid-5, .lp-grid-signal { grid-template-columns: 1fr 1fr; }
    .lp-grid-series { grid-template-columns: 1fr 1fr; }
}

/* Die Statuszeile unter einer Auswahl. Feste Hoehe und kein Umbruch: ihre Hoehe darf
   nicht davon abhaengen, ob gerade "online" oder "offline - zuletzt vor 12 min" darin
   steht. Sie wird immer gerendert, auch leer. */
.lp-link {
    display: flex; align-items: center; gap: 6px;
    height: 15px; min-width: 0; overflow: hidden;
    font-size: 10.5px; color: var(--text-faint);
}
.lp-link-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Kästchen in einer Rasterzelle: gleiche Hoehe wie ein Eingabefeld, damit die Zeile
   eine gemeinsame Grundlinie behaelt. */
.lp-check { display: inline-flex; align-items: center; gap: 8px; height: 38px; white-space: nowrap; }

/* Ein deaktiviertes Auswahlfeld muss als solches erkennbar sein - sonst haelt man die
   leere Signalliste fuer einen Fehler statt fuer "erst ein Fahrzeug waehlen". */
.select:disabled { opacity: .5; cursor: not-allowed; }

/* ---------- JSON-Einfaerbung im Mitschnitt ---------- */
/* Vier Farben reichen: Schluessel, Zeichenkette, Zahl, Literal. Mehr Kategorien
   unterscheidet man beim Ueberfliegen ohnehin nicht mehr auseinander. */
.hm-json .j-key { color: var(--accent); }
.hm-json .j-str { color: var(--ok); }
.hm-json .j-num { color: var(--warn); font-variant-numeric: tabular-nums; }
.hm-json .j-lit { color: var(--text-dim); font-style: italic; }

/* Fussnote innerhalb einer Gruppe: erklaert, was im Rohtext steht, aber absichtlich
   kein Signal ist. Zurueckgenommen, damit sie nicht wie eine Messzeile aussieht. */
.hm-signals .hm-note > td {
    padding: 6px 12px 8px 26px; font-size: 10.5px; line-height: 1.5;
    color: var(--text-faint); border-bottom: 1px solid var(--line);
}
.hm-signals .hm-note b { font-family: var(--font-mono); font-weight: 500; color: var(--text-dim); }

/* ---------- Firmenlogo, themeabhaengig (BrandLogo) ---------- */
/* Zwei Dateien, eine sichtbar. Voreingestellt die weisse Fassung: Das Standardtheme ist
   dunkel, und :root ohne data-theme ist ebenfalls dunkel - so ist der erste Bildaufbau
   richtig, noch bevor ein Theme gesetzt wurde.

   Praefix blogo- und nicht brand-logo-: Letzteres gehoert der Nav-Rail (.brand
   .brand-logo), und eine Klasse, die an zwei Stellen etwas anderes bedeutet, ist beim
   naechsten Umbau der Fehler.

   Kein filter: invert(1) auf einem einzigen Logo: Invertiert wuerde aus dem blauen
   Zeichen ein orangefarbenes, und das Zeichen ist Teil der Marke. */
.blogo { display: inline-flex; align-items: center; }
.blogo img { width: auto; display: block; }
.blogo-light { display: none; }

:root[data-theme="rosi-light"] .blogo-dark,
:root[data-theme="paper"] .blogo-dark,
:root[data-theme="nordic"] .blogo-dark,
:root[data-theme="solar"] .blogo-dark { display: none; }

:root[data-theme="rosi-light"] .blogo-light,
:root[data-theme="paper"] .blogo-light,
:root[data-theme="nordic"] .blogo-light,
:root[data-theme="solar"] .blogo-light { display: block; }

/* ---------- Panel: Fahrzeug / Pruefstand ---------- */
/* Die Kachel beantwortet die Frage vor jeder Messung: Welches Fahrzeug ist das, und lebt
   es? Der Aufbau folgt der Reihenfolge des Zweifels - Bild, Lampen, Bewegung, Kennungen.
   Wer nur ueberfliegt, hat nach dem Bild und den Lampen schon, was er braucht.

   Zwei Ebenen statt einer: aussen Bild und Rumpf, innen die drei Bloecke des Rumpfs. Nur
   so laesst sich das Bild per Einstellung daneben stellen - in einem einzigen Grid mit
   vier Zeilen muesste dafuer jede Zeile ihre Spalte neu zugewiesen bekommen.

   --veh-img ist das eingestellte Bildmass. Es steht am Element und wird hier je Lage als
   Hoehe oder als Breite gelesen; welche der beiden gemeint ist, weiss das Stylesheet und
   nicht das Bauteil. */
.lp-veh {
    display: grid;
    grid-template-rows: auto minmax(0, 1fr);
    gap: 9px;
    height: 100%;
    min-height: 0;
}

/* Vollflaechig - die Vorgabe: Das Bild nimmt die Zeile, die uebrig bleibt, die
   Statuszeilen darunter nur, was sie brauchen. Umgekehrt zu allen anderen Lagen, und
   genau das ist der Zweck: Mit festem Bildmass blieb unter drei Statuszeilen ein
   Streifen Schwarz stehen, und der sah nach einem Ladefehler aus.

   Eine Mindesthoehe fuer das Bild, weil der Rumpf wachsen kann - haekt jemand acht
   Stammdatenzeilen dazu, soll das Bild schmaler werden und nicht verschwinden. */
.lp-veh.lp-veh-fill { grid-template-rows: minmax(84px, 1fr) auto; }
.lp-veh.lp-veh-fill .lp-veh-shot { height: auto; min-height: 0; }
.lp-veh.lp-veh-fill .lp-veh-body { align-content: end; }

/* Bild daneben: aus den zwei Zeilen werden zwei Spalten. align-items:start haelt das Bild
   oben, wenn der Rumpf hoeher ist - ein mittig schwebendes Foto neben einer Liste sieht
   nach Versehen aus. */
.lp-veh.lp-veh-side {
    grid-template-rows: minmax(0, 1fr);
    grid-template-columns: auto minmax(0, 1fr);
    align-items: start;
}
/* Rechts: die Spalten tauschen, das Bild wandert per order hinter den Rumpf. Zwei
   Markup-Varianten dafuer waeren dieselbe Kachel doppelt gepflegt. */
.lp-veh.lp-veh-right { grid-template-columns: minmax(0, 1fr) auto; }
.lp-veh.lp-veh-right .lp-veh-shot { order: 2; }

/* Ohne Bild bleibt nur der Rumpf, und der nimmt die ganze Hoehe. */
.lp-veh.lp-veh-noimg { grid-template-rows: minmax(0, 1fr); }

/* overflow:auto und eine Mindesthoehe an der Liste: Ohne beides bekam die 1fr-Zeile in
   einer knapp gezogenen Kachel exakt 0 Pixel - Bild, Lampen und Bewegung standen da, und
   die Stammdaten waren spurlos weg. Kein Abschneiden, kein Rollbalken, nichts. Jetzt
   behaelt die Liste drei Zeilen Anspruch, und passt auch das nicht, rollt der ganze
   Rumpf. Der Reihe nach: erst so viel zeigen wie geht, dann erreichbar machen. */
.lp-veh-body {
    display: grid;
    grid-template-rows: auto auto minmax(0, 1fr);
    gap: 9px;
    min-height: 0;
    overflow: auto;
}
/* Ohne Stammdatenliste gibt es keine Zeile, die den Rest nehmen soll: dann stehen Lampen
   und Bewegung oben und dehnen sich nicht auf halbe Kachelhoehe. Das trifft jede Kachel,
   an der die Stammdaten abgewaehlt sind oder zu der nichts hinterlegt ist. */
.lp-veh-body:not(:has(> .lp-veh-meta)) {
    grid-template-rows: auto auto;
    align-content: start;
}

/* Das Bild bekommt ein festes Mass statt sein Seitenverhaeltnis zu behalten: In einer
   Kachel, deren Hoehe der Benutzer zieht, waere ein 3:2-Bild bei jeder Groesse anders
   hoch - und die Lampen darunter wanderten mit. object-fit in .veh-img > img schneidet
   dafuer zu, und da das Motiv mittig liegt, verliert es nichts.

   Oben ist das Mass die Hoehe (die Breite gibt die Kachel vor), daneben die Breite - dort
   haelt aspect-ratio die Hoehe im Bildformat, weil eine Spalte keine Hoehe vorgibt. */
.lp-veh-shot { height: var(--veh-img, 120px); min-height: 0; }
.lp-veh-shot .veh-img { width: 100%; height: 100%; }

.lp-veh-side .lp-veh-shot {
    width: var(--veh-img, 120px);
    height: auto;
    aspect-ratio: 3 / 2;
}

/* ---- Die Lampen ---- */
/* Nebeneinander und gleich breit: Sie sind gleichrangig. "Fahrzeug sendet" und "Fahrzeug
   weiss, wo es ist" sind zwei Aussagen - ein Fahrzeug in der Halle sendet tadellos und hat
   keinen Satelliten.

   Spalten aus dem Fluss und nicht "1fr 1fr": Ist eine der beiden Lampen abgewaehlt, fuellt
   die andere die Breite, statt neben einer Luecke zu stehen. */
.lp-veh-leds {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: minmax(0, 1fr);
    gap: 8px;
}

.lp-veh-led {
    display: flex; align-items: center; gap: 8px; min-width: 0;
    padding: 7px 9px;
    background: var(--panel-inset); border: 1px solid var(--line);
    border-radius: var(--radius-sm);
}
.lp-veh-led-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
.lp-veh-led-text b { font-size: 12px; font-weight: 600; color: var(--text); }
.lp-veh-led-text span {
    font-family: var(--font-mono); font-size: 9.5px; letter-spacing: 0.04em;
    color: var(--text-faint);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.lp-veh-dot { width: 9px; height: 9px; border-radius: 50%; flex: none; background: var(--stale); }
.lp-veh-led.ok    .lp-veh-dot { background: var(--ok); }
.lp-veh-led.warn  .lp-veh-dot { background: var(--warn); }
.lp-veh-led.fault .lp-veh-dot { background: var(--fault); }
.lp-veh-led.stale .lp-veh-dot { background: var(--text-faint); }

.lp-veh-led.ok    { border-color: color-mix(in srgb, var(--ok) 40%, var(--line)); }
.lp-veh-led.warn  { border-color: color-mix(in srgb, var(--warn) 40%, var(--line)); }
.lp-veh-led.fault { border-color: color-mix(in srgb, var(--fault) 40%, var(--line)); }

/* Der Puls nur im guten Fall, und das ist Absicht: Er heisst "es kommt gerade etwas an".
   Ein blinkender roter Punkt hiesse "hier passiert etwas" - genau das Gegenteil von
   "nichts kommt". Stillstand ist die richtige Darstellung fuer Stillstand.

   box-shadow und kein Massstab: Eine wachsende Flaeche verschiebt die Zeile daneben um
   Bruchteile eines Pixels, und bei zwei Sekunden Takt sieht man das Zittern. */
.lp-veh-led.pulse .lp-veh-dot { animation: lp-veh-pulse 2s ease-out infinite; }

@keyframes lp-veh-pulse {
    0%   { box-shadow: 0 0 0 0 currentColor; }
    70%  { box-shadow: 0 0 0 6px transparent; }
    100% { box-shadow: 0 0 0 0 transparent; }
}
.lp-veh-led.ok   .lp-veh-dot { color: color-mix(in srgb, var(--ok) 55%, transparent); }
.lp-veh-led.warn .lp-veh-dot { color: color-mix(in srgb, var(--warn) 55%, transparent); }

/* ---- Faehrt oder steht ---- */
/* Eine eigene Zeile: Ob ein Pruefling steht, entscheidet darueber, ob eine Messung
   ueberhaupt gueltig ist - bei einer Rollenmessung ebenso wie bei einer Fahrt. */
.lp-veh-move {
    display: flex; align-items: center; gap: 9px;
    padding: 6px 9px;
    border: 1px solid var(--line); border-left-width: 2px;
    border-radius: var(--radius-sm);
    background: var(--panel-inset);
    font-size: 12px;
}
.lp-veh-move.moving { border-left-color: var(--ok); }
.lp-veh-move.still  { border-left-color: var(--text-faint); }
.lp-veh-move.unknown { border-left-color: var(--line-strong); }

.lp-veh-move-icon {
    font-size: 10px; line-height: 1; color: var(--text-dim); flex: none;
    width: 14px; text-align: center;
}
.lp-veh-move.moving .lp-veh-move-icon { color: var(--ok); }
.lp-veh-move-text { font-weight: 600; color: var(--text); }
.lp-veh-move.unknown .lp-veh-move-text { font-weight: 500; color: var(--text-dim); }
.lp-veh-move-num {
    margin-left: auto; font-family: var(--font-mono); font-size: 11px;
    font-variant-numeric: tabular-nums; color: var(--text-dim); white-space: nowrap;
}
.lp-veh-move-num + .lp-veh-move-num { margin-left: 0; }

/* ---- Stammdaten ---- */
/* Bezeichnung und Wert in zwei Spalten, die Liste rollt in sich. Als Tabelle waere es
   dieselbe Information mit Rahmen und einer Kopfzeile, die nichts zu sagen haette. */
.lp-veh-meta {
    display: grid; grid-template-columns: auto minmax(0, 1fr);
    gap: 3px 10px; align-content: start;
    margin: 0; overflow: auto;
    /* Drei Zeilen Anspruch - siehe .lp-veh-body. Mit min-height:0 gab die Liste in einer
       knappen Kachel ihren ganzen Platz her, ohne es anzuzeigen. */
    min-height: 46px;
    font-size: 11.5px;
}
.lp-veh-meta dt {
    color: var(--text-faint); white-space: nowrap;
    font-size: 10.5px; padding-top: 1px;
}
.lp-veh-meta dd {
    margin: 0; color: var(--text); min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lp-veh-meta dd.mono { font-family: var(--font-mono); font-size: 11px; }


/* ---------- Rechtsseiten: Impressum und AGB ---------- */
/* Eine eigene Bahn und nicht die Huelle der Anwendung: Beide Seiten laufen unter
   LoginLayout, weil sie OHNE Anmeldung erreichbar sein muessen. Es gibt hier also keine
   Navigation, keine Kopfzeile und keinen Inhaltsrahmen - die Seite bringt ihren eigenen
   mit.

   Eine Lesebreite und keine volle Seite: Fliesstext ueber 1600 Pixel liest niemand. 760
   Pixel sind rund 90 Zeichen je Zeile, und damit endet eine Zeile dort, wo das Auge sie
   noch ohne Suchen wiederfindet. */
.legal-wrap {
    max-width: 760px; margin: 0 auto; padding: 28px 20px 60px;
    min-height: 100vh; min-height: 100dvh;
}

.legal-head { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 30px; }
.legal-back { font-size: 13px; color: var(--text-dim); text-decoration: none; }
.legal-back:hover { color: var(--accent); }
.legal-brand { display: flex; align-items: center; gap: 10px; }
.legal-brand-name {
    font-family: var(--font-mono); font-size: 12px; letter-spacing: 0.08em;
    color: var(--text-dim);
}

.legal-title { font-size: clamp(24px, 3vw, 34px); line-height: 1.15; letter-spacing: -0.01em; }
.legal-sub { color: var(--text-dim); font-size: 14px; margin-top: 8px; }

.legal-card {
    margin-top: 26px; padding: 4px 24px 8px;
    background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
}

/* Ein Abschnitt je Pflichtangabe. Die Trennlinie trennt Sachverhalte und nicht Absaetze -
   ohne sie liest sich ein Impressum als eine lange Aufzaehlung, in der man die
   Registernummer nicht wiederfindet. */
.legal-sec { padding: 20px 0; border-bottom: 1px solid var(--line); }
.legal-sec:last-child { border-bottom: none; }
.legal-sec h2 {
    font-size: 13px; font-weight: 600; color: var(--accent);
    letter-spacing: 0.01em; margin-bottom: 10px;
}
.legal-sec p { font-size: 13.5px; line-height: 1.7; color: var(--text); }
.legal-sec p + p { margin-top: 10px; }
.legal-sec a { color: var(--accent); text-decoration: none; }
.legal-sec a:hover { text-decoration: underline; }

.legal-strong { font-weight: 600; }
.legal-lines { line-height: 1.8 !important; }
.legal-sec .mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

/* Bezeichnung und Wert in zwei Spalten. Auf dem Telefon untereinander: Zwei Spalten mit
   je 40 Prozent Breite brechen sonst beide mitten im Wort. */
.legal-dl {
    display: grid; grid-template-columns: 150px minmax(0, 1fr); gap: 4px 14px;
    font-size: 13.5px; line-height: 1.7;
}
.legal-dl dt { color: var(--text-dim); }
.legal-dl dd { margin: 0; color: var(--text); }
@media (max-width: 560px) {
    .legal-dl { grid-template-columns: 1fr; gap: 0 0; }
    .legal-dl dt { margin-top: 8px; }
}

/* ---- Die Dokumentkarten der AGB-Seite ---- */
/* Nebeneinander, solange Platz ist: Die beiden Dokumente sind gleichrangig, und
   untereinander laese sich das erste als das wichtigere. */
.legal-docs {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 14px;
    margin-top: 26px;
}
.legal-doc {
    display: grid; grid-template-columns: 40px minmax(0, 1fr); gap: 14px; align-items: start;
    padding: 18px; background: var(--panel); border: 1px solid var(--line);
    border-radius: var(--radius);
}
.legal-doc-icon {
    width: 40px; height: 40px; display: grid; place-items: center; font-size: 20px;
    background: var(--panel-inset); border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.legal-doc-body h2 { font-size: 14px; font-weight: 600; color: var(--text); }
.legal-doc-body p { font-size: 12.5px; line-height: 1.6; color: var(--text-dim); margin-top: 6px; }
.legal-doc-actions { display: flex; gap: 8px; margin-top: 12px; flex-wrap: wrap; }

/* Der Betrachter im Fenster. Feste Hoehe in vh und nicht nach Inhalt: Ein Dokument von
   vierzig Seiten hat keine Hoehe, die sich aus ihm ableiten liesse - es rollt in sich. */
.legal-pdf { width: 100%; height: 70vh; border: 1px solid var(--line); border-radius: var(--radius-sm); display: block; background: var(--panel-inset); }

/* ---- Fuss ---- */
.legal-foot { margin-top: 30px; text-align: center; }
.legal-foot-links { display: flex; align-items: center; justify-content: center; gap: 12px; flex-wrap: wrap; }
.legal-foot-links a { font-size: 12px; color: var(--text-dim); text-decoration: none; }
.legal-foot-links a:hover { color: var(--accent); }
/* Die Seite, auf der man steht, ist kein Verweis mehr - sie wird als Ort markiert statt
   als Ziel angeboten. */
.legal-foot-links a.is-here { color: var(--text); cursor: default; pointer-events: none; }
.legal-dot { width: 3px; height: 3px; border-radius: 50%; background: var(--text-faint); }
.legal-copy { margin-top: 10px; font-size: 11px; color: var(--text-faint); }

/* ---------- Hub-Analyse: Auswertung ---------- */
/* Eigenes Praefix ha-, weil hm- den Mitschnitt traegt: Die beiden Reiter teilen die Seite,
   aber kein Bauteil - und eine Klasse, die an zwei Stellen etwas anderes bedeutet, ist beim
   naechsten Umbau der Fehler. */

.ha { display: flex; flex-direction: column; gap: 14px; }
.ha-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.ha-scope-line { font-size: 11.5px; color: var(--text-dim); font-variant-numeric: tabular-nums; }

/* Die Plakette: Anzahl offener Befunde in der Farbe des schwersten. Sie steht am
   Bereichsreiter der Seite und im Kopf des Befund-Panels - wer den Mitschnitt liest, soll
   von einem Ausfall erfahren, ohne dafuer nachzusehen. */
.ha-badge {
    display: inline-block; margin-left: 6px; min-width: 16px; padding: 0 5px;
    border-radius: 9px; border: 1px solid transparent;
    font-family: var(--font-mono); font-size: 9.5px; line-height: 15px; text-align: center;
    font-variant-numeric: tabular-nums;
}
.ha-badge.info   { background: var(--panel-inset); color: var(--text-dim); border-color: var(--line); }
.ha-badge.notice { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, transparent); }
.ha-badge.warn   { background: color-mix(in srgb, var(--warn) 16%, transparent);   color: var(--warn);   border-color: color-mix(in srgb, var(--warn) 55%, transparent); }
.ha-badge.fault  { background: color-mix(in srgb, var(--fault) 16%, transparent);  color: var(--fault);  border-color: color-mix(in srgb, var(--fault) 55%, transparent); }

/* ---- Kachelzeile ---- */
/* auto-fit statt fester Spaltenzahl: sechs Kacheln passen auf einen Monitor in eine Reihe,
   auf einem Laptop in zwei und auf dem Telefon untereinander - ohne eigene Umbruchregeln. */
.ha-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(168px, 1fr)); gap: 10px; }
.ha-kpi {
    display: flex; flex-direction: column; gap: 3px; min-width: 0;
    padding: 11px 13px; background: var(--panel); border: 1px solid var(--line);
    border-left: 2px solid var(--line-strong); border-radius: var(--radius-sm);
}
.ha-kpi.ok    { border-left-color: var(--ok); }
.ha-kpi.warn  { border-left-color: var(--warn); }
.ha-kpi.fault { border-left-color: var(--fault); }

.ha-kpi-cap {
    font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.14em; text-transform: uppercase;
    color: var(--text-faint);
}
/* tabular-nums und eine feste Zeilenhoehe: Die Kacheln werden im Sekundentakt neu
   geschrieben, und ohne gleich breite Ziffern atmet die ganze Zeile mit jeder Rate. */
.ha-kpi-val {
    font-family: var(--font-mono); font-size: 19px; line-height: 1.15; font-weight: 600;
    color: var(--text); font-variant-numeric: tabular-nums;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ha-kpi-of { font-size: 12px; font-weight: 400; color: var(--text-faint); margin-left: 4px; }
.ha-kpi-sub { font-size: 10.5px; line-height: 1.35; color: var(--text-dim); }

/* ---- Flotte und Befunde nebeneinander ---- */
/* Das Bild links ist der Beleg fuer die Aussagen rechts, also stehen sie in einer Zeile.
   Die Flotte bekommt mehr Platz: Eine Sekundenreihe ueber fuenf Minuten braucht Breite,
   ein Befund braucht Hoehe. Unter 1100px stapeln beide - nebeneinander waere die Reihe
   dann zu schmal, um eine Luecke von einer Sekunde noch zu zeigen. */
.ha-top { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); gap: 14px; align-items: start; }
@media (max-width: 1100px) { .ha-top { grid-template-columns: 1fr; } }

/* Die Befundliste rollt in sich. Ohne Deckel zieht sie bei zwanzig Befunden das ganze
   Dashboard auseinander, und die Gerätetabelle darunter waere nicht mehr zu sehen -
   genau das, was die Reiter vorher falsch gemacht haben. */
.ha-findings-panel .panel-body { max-height: 420px; overflow: auto; }

/* ---- Erklaerzeile ueber einer Auswertung ---- */
/* Sie traegt auf dieser Seite mehr als ueblich: Eine Zahl wie "p95 = 1,2 s" ist ohne den
   Satz daneben keine Auskunft, sondern nur eine Zahl. Deshalb nicht zurueckgenommen bis
   zur Unlesbarkeit - lesbar, aber ruhiger als der Inhalt. */
.ha-hint {
    font-size: 11.5px; line-height: 1.55; color: var(--text-dim);
    padding: 9px 12px; margin-bottom: 12px;
    background: var(--panel-inset); border: 1px solid var(--line); border-left: 2px solid var(--accent);
    border-radius: var(--radius-sm);
}
.ha-hint b { color: var(--text); font-weight: 600; }
.ha-hint .mono { font-family: var(--font-mono); font-size: 11px; }

.ha-dim { color: var(--text-dim); font-size: 11.5px; line-height: 1.45; }
.ha-warn  { color: var(--warn); }
.ha-fault { color: var(--fault); }

/* ---- Gemeinsame Ausfaelle, unter der Flottenreihe ---- */
/* Sie stehen bei der Reihe und nicht in einer eigenen Tabelle weiter unten: Die Liste ist
   die Lesehilfe zum Bild darueber - welcher der grauen Sockel welcher Vorfall war. */
.ha-outages { margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--line); }
.ha-outage {
    display: grid; grid-template-columns: 68px 52px minmax(0, 1fr); gap: 8px; align-items: baseline;
    padding: 3px 0; font-size: 11px;
}
.ha-outage .mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

/* ---- Befundliste ---- */
.ha-findings { display: flex; flex-direction: column; gap: 8px; }
.ha-finding {
    display: grid; grid-template-columns: 22px minmax(0, 1fr); gap: 10px; align-items: start;
    padding: 11px 13px; background: var(--panel-raised);
    border: 1px solid var(--line); border-left: 2px solid var(--line-strong);
    border-radius: var(--radius-sm);
}
.ha-finding.notice { border-left-color: var(--accent); }
.ha-finding.warn   { border-left-color: var(--warn); }
.ha-finding.fault  { border-left-color: var(--fault); }

/* Das Zeichen traegt die Stufe, nicht die Schriftfarbe des Textes: Eine ganze Meldung in
   Rot liest sich schlechter als eine in normaler Schrift mit rotem Zeichen davor. */
.ha-sev {
    display: grid; place-items: center; width: 20px; height: 20px; border-radius: 50%;
    font-family: var(--font-mono); font-size: 11px; font-weight: 600; line-height: 1;
    background: var(--panel-inset); color: var(--text-dim); border: 1px solid var(--line);
}
.ha-finding.notice .ha-sev { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 50%, transparent); }
.ha-finding.warn   .ha-sev { color: var(--warn);   border-color: color-mix(in srgb, var(--warn) 60%, transparent); }
.ha-finding.fault  .ha-sev { color: var(--fault);  border-color: color-mix(in srgb, var(--fault) 60%, transparent); }

.ha-finding-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.ha-finding-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ha-finding-title { font-size: 13px; font-weight: 600; color: var(--text); }
.ha-finding-detail { font-size: 12px; line-height: 1.5; color: var(--text); font-variant-numeric: tabular-nums; }
/* Die Deutung steht zurueckgenommen unter der Begruendung: Sie ist der Satz, den man beim
   zweiten Lesen braucht, nicht beim ersten Ueberfliegen. */
.ha-finding-advice { font-size: 11.5px; line-height: 1.5; color: var(--text-dim); }

/* Der Sprung vom Befund zu seinen Zahlen. Als Knopf und nicht als Link: Er waehlt das
   Geraet und rollt zur Einzelansicht, fuehrt aber nirgends hin. */
.ha-pill {
    font-family: var(--font-mono); font-size: 10.5px; padding: 2px 8px; border-radius: 10px;
    background: var(--panel-inset); border: 1px solid var(--line); color: var(--text-dim);
    cursor: pointer; transition: border-color .12s, color .12s;
}
.ha-pill:hover { border-color: var(--accent); color: var(--accent); }
.ha-pill-flat { cursor: default; }
.ha-pill-flat:hover { border-color: var(--line); color: var(--text-dim); }

/* ---- Tabellen ---- */
/* Enger als .data sonst: Sechzehn Spalten je Zeile sind der Zweck dieser Tabelle, und mit
   der ueblichen Polsterung braeuchte eine Zeile zwei Bildschirmbreiten. */
.ha-table { font-size: 12px; }
.ha-table th { padding: 8px 9px; }
.ha-table td { padding: 6px 9px; }
.ha-table td.num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap; }
/* Der Zeigefinger nur dort, wo eine Zeile wirklich etwas tut: Die Schwellentabelle ist
   Auskunft und kein Auswahlmittel. */
.ha-table.ha-pick tbody tr { cursor: pointer; }
.ha-table .ha-row-sel { background: var(--accent-soft); }
.ha-table .ha-row-sel:hover { background: var(--accent-soft); }

/* Die Gruppenzeile ueber den Spaltennamen. Sie ist bewusst leiser als die Namen darunter:
   Sie ordnet ein, sie benennt nicht - und zwei gleich laute Kopfzeilen laesen sich wie
   eine doppelte Ueberschrift. */
.ha-table .ha-th-group > th {
    border-bottom: 1px solid var(--line); background: var(--panel-inset);
    color: var(--text-faint); font-size: 9px; letter-spacing: 0.16em; text-align: center;
    padding-top: 7px; padding-bottom: 5px;
}
.ha-table .ha-th-group > th[rowspan] {
    text-align: left; letter-spacing: 0.1em; vertical-align: bottom;
    border-bottom: 1px solid var(--line-strong);
}
/* Die Gruppen voneinander abgrenzen: Bei sechzehn Zahlenspalten ist der senkrechte Strich
   die einzige Hilfe, um beim Lesen einer Zeile nicht in die Nachbargruppe zu rutschen. */
.ha-table .ha-th-group > th + th { border-left: 1px solid var(--line); }

.ha-th-mark, .ha-td-mark { width: 26px; text-align: center; padding-right: 0 !important; padding-left: 8px !important; }

/* Das Zeichen in der Tabelle: nur Glyphe und Farbe, keine Flaeche. In einer Tabelle mit
   sechzig Zeilen waeren sechzig farbige Plaketten lauter als die Zahlen daneben. */
.ha-mark { font-size: 12px; line-height: 1; color: var(--text-faint); }
.ha-mark.info   { color: var(--ok); }
.ha-mark.notice { color: var(--accent); }
.ha-mark.warn   { color: var(--warn); }
.ha-mark.fault  { color: var(--fault); }

.ha-veh { display: flex; align-items: center; gap: 8px; font-family: var(--font-mono); font-size: 12px; white-space: nowrap; }
.ha-dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.ha-dot.live { background: var(--ok); }
.ha-dot.silent { background: var(--text-faint); }

/* ---- Einzelansicht ---- */
/* Sie ist als Block abgesetzt und nicht nur eine weitere Panelreihe: Alles darin gilt fuer
   EIN Fahrzeug, alles darueber fuer die Flotte. Ohne diese Grenze liest man eine Zahl aus
   der Einzelansicht als Flottenwert. */
.ha-detail {
    display: flex; flex-direction: column; gap: 10px;
    padding: 12px; border: 1px solid var(--line-strong); border-radius: var(--radius);
    background: color-mix(in srgb, var(--accent) 4%, transparent);
    scroll-margin-top: 72px;
}
.ha-detail-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; padding: 0 2px; }
.ha-detail-name { font-size: 14px; font-weight: 600; color: var(--text); }

.ha-two { display: grid; grid-template-columns: repeat(auto-fit, minmax(330px, 1fr)); gap: 12px; align-items: start; }

/* ---- Kennzahlenliste ---- */
/* Zwei Spalten je Zeile, Bezeichnung links und Zahl rechts. Als Tabelle waere es dieselbe
   Information mit Rahmen, Kopfzeile und Zeilentrennern - und die Kopfzeile haette nichts
   zu sagen ("Groesse" / "Wert"). */
.ha-stats { display: flex; flex-direction: column; }
.ha-stat-grp {
    font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.16em; text-transform: uppercase;
    color: var(--text-faint); margin: 10px 0 3px; padding-bottom: 3px; border-bottom: 1px solid var(--line);
}
.ha-stat-grp:first-child { margin-top: 0; }
.ha-stat {
    display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
    padding: 3px 0; font-size: 11.5px;
}
.ha-stat-key { color: var(--text-dim); min-width: 0; }
.ha-stat-val { font-variant-numeric: tabular-nums; color: var(--text); white-space: nowrap; }

/* ---- Zeitverlaeufe ---- */
.ha-plot {
    width: 100%; display: block;
    background: var(--panel-inset); border: 1px solid var(--line); border-radius: var(--radius-sm);
}
/* Die Nadel ist der Abstand EINES Pakets. Alle Nadeln einer Art liegen in EINEM Pfad -
   zweihundert Rechtecke, jede Sekunde ersetzt, waren genau die Last, an der die
   Rohtextanzeige dieser Seite vorher den Browser angehalten hat. */
.ha-needle { fill: var(--accent); opacity: 0.55; }
.ha-needle.over { fill: var(--fault); opacity: 0.95; }

.ha-plot-limit  { stroke: var(--fault); stroke-width: 1; stroke-dasharray: 4 4; opacity: 0.7; }
.ha-plot-median { stroke: var(--ok); stroke-width: 1; stroke-dasharray: 2 5; opacity: 0.7; }
.ha-plot-zero   { stroke: var(--line-strong); stroke-width: 1; }
.ha-skew        { fill: none; stroke: var(--accent); stroke-width: 1.4; stroke-linejoin: round; }

/* Die Achsenbeschriftung liegt in HTML und nicht im SVG: Die Plots sind mit
   preserveAspectRatio="none" gezogen, und mitverzerrte Schrift ist unlesbar. */
.ha-axis {
    display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
    margin-top: 6px; font-size: 10.5px; color: var(--text-faint); flex-wrap: wrap;
}
.ha-axis .mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.ha-axis .ha-dim { font-size: 10.5px; text-align: center; flex: 1 1 auto; }

/* ---- Verteilung und Anteile ---- */
/* Dieselben vier Spalten fuer Abstandsklassen und Abfragearten: Beide beantworten "wie oft
   welcher Fall", und zwei verschiedene Balkenzeilen fuer dieselbe Frage waeren zwei
   Lesegewohnheiten fuer nichts. */
.ha-hist { display: flex; flex-direction: column; gap: 3px; }
.ha-hist-row {
    display: grid; grid-template-columns: 112px minmax(0, 1fr) 54px 48px; gap: 8px; align-items: center;
    font-size: 11px;
}
.ha-hist-lab { color: var(--text-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ha-hist-bar { height: 9px; background: var(--panel-inset); border-radius: 2px; overflow: hidden; }
.ha-hist-bar > span { display: block; height: 100%; background: var(--accent); border-radius: 2px; }
/* Klassen oberhalb der Aussetzerschwelle: Sie sind nicht "der rechte Rand der Verteilung",
   sondern ein anderer Sachverhalt - dort war die Strecke unterbrochen. */
.ha-hist-row.over .ha-hist-lab { color: var(--fault); }
.ha-hist-row.over .ha-hist-bar > span { background: var(--fault); }
.ha-hist-num, .ha-hist-pct { text-align: right; font-variant-numeric: tabular-nums; color: var(--text-dim); }
.ha-hist-pct { color: var(--text-faint); }

/* ---- Pakete je Sekunde (SvgSecondBars) ---- */
.sb { display: flex; flex-direction: column; gap: 3px; }
.sb-plot {
    width: 100%; display: block;
    background: var(--panel-inset); border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.sb-grid { stroke: var(--line); stroke-width: 1; stroke-dasharray: 3 5; }
/* Drei Pfade, drei Aussagen. Eine leere Sekunde ist grau, solange sie nur dieses Geraet
   betrifft, und rot, sobald die ganze Flotte gleichzeitig schwieg - genau diese
   Unterscheidung ist der Grund, warum das Bauteil die Ausfallsekunden uebergeben bekommt
   und nicht selbst herleitet. */
.sb-bar.live   { fill: var(--accent); }
.sb-bar.empty  { fill: var(--text-faint); }
.sb-bar.outage { fill: var(--fault); }

/* Das Geraeteband: Flaeche unter der Zahl gleichzeitig sendender Geraete. Eine eigene
   Farbe, weil es eine andere Groesse zaehlt als die Balken darueber. */
.sb-band {
    width: 100%; display: block; height: 14px;
    background: var(--panel-inset); border: 1px solid var(--line); border-radius: 2px;
}
.sb-cell { fill: color-mix(in srgb, var(--ok) 55%, transparent); }

@media (max-width: 700px) {
    /* Die Beschriftungsspalte der Verteilung ist auf dem Telefon der Engpass: Sie traegt
       "1,50 s - 2,00 s" und laesst dem Balken daneben sonst nichts. */
    .ha-hist-row { grid-template-columns: 92px minmax(0, 1fr) 46px 44px; gap: 6px; font-size: 10.5px; }
    .ha-kpi-val { font-size: 16px; }
    .ha-detail { padding: 8px; }
}

/* ---------- Wertwechsel: kurze Einblendung ---------- */
/* Die Zahl wechselte bisher hart. Ein Uebergang macht sichtbar, DASS sich etwas
   geaendert hat - bei einem Panel, das man nebenher im Blick hat, ist das der halbe
   Zweck.

   Die Dauer ist bewusst kurz: die Mock-Dongles senden mit 5 Hz, also alle 200 ms. Eine
   Animation von 300 ms liefe dauerhaft und das Panel flimmerte - schlimmer als der
   harte Wechsel vorher. 140 ms lassen zwischen zwei Werten einen ruhigen Moment.

   Ausgeloest wird sie ueber @key auf dem dargestellten Text: Blazor tauscht das Element
   nur bei einer echten Aenderung aus, und nur dann laeuft die Animation erneut an. Ein
   stehender Wert bleibt ruhig, auch wenn im Sekundentakt gerendert wird. */
/* Die Zahl selbst wird nicht mehr per CSS animiert - sie wird in js/app.js fortlaufend
   nachgefuehrt (.dx-num). Eine Animation JE AKTUALISIERUNG kann bei 5 Hz nicht ruhig
   wirken: 200 ms Takt gegen 140 ms Animation hiess 70 % der Zeit in Bewegung, und das
   sah aus wie Flackern. Eine fortlaufende Annaeherung hat keinen Ausloeser und damit
   nichts, was blinken koennte.

   Animiert werden hier nur noch die analogen Teile - Balken und Bogen -, denn deren
   Bewegung IST die Aussage. */
.lp-bar-fill { transition: width .22s cubic-bezier(0.33, 1, 0.68, 1), background-color .25s ease; }
.gauge-value { transition: stroke-dashoffset .28s cubic-bezier(0.33, 1, 0.68, 1), stroke .25s ease; }

/* Ziffern gleicher Breite sind Voraussetzung fuer die Nachfuehrung: ohne sie wackelt
   die Zahl bei jedem Ziffernwechsel seitlich, und genau das sollte weg. */
.dx-num { font-variant-numeric: tabular-nums; }

@media (prefers-reduced-motion: reduce) {
    .lp-bar-fill, .gauge-value { transition: none; }
}

/* Stufenwechsel weich: von "in Ordnung" auf "Warnung" springt sonst Farbe und Rahmen
   im selben Bild um. Das ist der eine Moment, den man bemerken soll - ein Uebergang
   von einer Viertelsekunde macht ihn deutlicher, nicht leiser. */
.lp-number, .lp-row-value, .lp-row-dot, .lp-lamp { transition: color .25s ease, background-color .25s ease; }
.lp-row { transition: background-color .25s ease; }
.widget > .panel { transition: border-color .25s ease, opacity .25s ease; }
@media (prefers-reduced-motion: reduce) {
    .lp-number, .lp-row-value, .lp-row-dot, .lp-lamp, .lp-row, .widget > .panel { transition: none; }
}

/* ---------- Panel: Signale tabellarisch ---------- */
.lp-sigtable { gap: 2px; }
/* Der Blockkopf ist kein Titel, sondern der Name eines Knotens - gleiche Groesse wie
   die Zeilen darunter, nur zurueckgenommen und mit Abstand darueber. */
.lp-sig-grp {
    display: flex; align-items: baseline; gap: 8px;
    padding: 7px 8px 3px; margin-top: 2px;
    font-family: var(--font-mono); font-size: 10.5px; letter-spacing: 0.06em;
    color: var(--text-dim); border-bottom: 1px solid var(--line);
}
.lp-sig-grp:first-child { margin-top: 0; padding-top: 2px; }
.lp-sig-grp-name { font-weight: 600; }
.lp-sig-grp.vehicle .lp-sig-grp-name { color: var(--accent); }
.lp-sig-grp-note { color: var(--text-faint); margin-left: auto; }

/* ---------- Panel: Position ---------- */
.lp-gps { justify-content: flex-start; gap: 10px; }
.lp-gps-grid { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 3px 10px; align-items: baseline; }
.lp-gps-label { font-family: var(--font-mono); font-size: 9.5px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-faint); }
/* Koordinaten sind zum Ablesen und Kopieren da - Ziffern gleicher Breite, damit die
   Nachkommastellen untereinander stehen und die Zeile beim Zaehlen nicht wandert. */
.lp-gps-value {
    font-family: var(--font-mono); font-variant-numeric: tabular-nums;
    font-size: clamp(0.9rem, 5.5cqw, 1.2rem); font-weight: 700; letter-spacing: -0.01em;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    user-select: all;
}
.lp-gps-sub { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; font-size: 11px; color: var(--text-dim); }
.lp-gps-sub b { font-family: var(--font-mono); font-weight: 700; color: var(--text); }
.lp-gps-move { margin-left: auto; font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.08em; text-transform: uppercase; padding: 2px 7px; border-radius: 20px; border: 1px solid currentColor; flex: none; }
.lp-gps.lp-ok .lp-gps-move { color: var(--ok); }

/* ---------- Signalauswahl im Panel-Dialog ---------- */
.lp-pick { display: flex; flex-direction: column; gap: 10px; max-height: 320px; overflow: auto; padding: 10px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel-inset); }
.lp-pick-grp { display: flex; flex-direction: column; gap: 6px; }
.lp-pick-head { display: flex; align-items: center; gap: 8px; }
.lp-pick-name { font-family: var(--font-mono); font-size: 11.5px; font-weight: 600; }
.lp-pick-name.vehicle { color: var(--accent); }
.lp-pick-note { font-size: 10.5px; color: var(--text-faint); margin-right: auto; }
/* Mehrspaltig: vierzig Signale untereinander waeren eine Rollflaeche, in der man das
   gesuchte nicht findet. */
.lp-pick-items { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 2px 12px; }
.lp-pick-item { font-size: 12px; padding: 2px 0; min-width: 0; }
.lp-pick-item .mono { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lp-pick-unit { font-family: var(--font-mono); font-size: 10px; color: var(--text-faint); flex: none; }

/* ---------- Dialog: Klick daneben schliesst nicht ---------- */
/* Der Dialog bleibt stehen und gibt stattdessen eine kurze Rueckmeldung. Ohne sie wirkt
   er eingefroren - man klickt zwei-, dreimal, weil nichts passiert, und sucht den Fehler
   bei der Anwendung statt bei der eigenen Zielgenauigkeit.

   Kleine Amplitude und kurze Dauer: das ist keine Fehlermeldung, sondern ein Hinweis.
   Ein kraeftiges Ruetteln liest sich als "das war verboten" - gemeint ist aber nur
   "hier geht es nicht raus, oben rechts oder unten schon". */
@keyframes modal-nudge {
    0%   { transform: translateX(0); }
    22%  { transform: translateX(-3px); }
    55%  { transform: translateX(3px); }
    80%  { transform: translateX(-1px); }
    100% { transform: translateX(0); }
}
.modal.nudge {
    animation: modal-nudge .24s ease-in-out;
    border-color: var(--accent);
}

/* Ohne Bewegung bleibt der Rahmen als Rueckmeldung - schon damit ist erkennbar, dass
   der Klick angekommen ist. */
@media (prefers-reduced-motion: reduce) {
    .modal.nudge { animation: none; }
}

/* Die Fusszeile steht jetzt in jedem Dialog, auch in denen ohne eigene Schaltflächen.
   Bei einem Dialog mit nur einer Schaltflaeche sass diese bisher am rechten Rand weit
   weg vom Inhalt - deshalb rueckt ein alleinstehendes "Schliessen" nach links. */
.modal-foot > .btn:only-child { margin-right: auto; }

/* ---------- Hervorgehobene Hinzufuegen-Schaltflaeche ---------- */
/* "Panel" ist im Bearbeiten-Modus die Schaltflaeche, wegen der man ueberhaupt dort ist -
   sie stand aber als eine von fuenf gleich aussehenden in der Reihe.

   Bewusst nicht btn-primary: das gehört "Speichern". Zwei gefuellte Knoepfe
   nebeneinander heben sich gegenseitig auf, und dann faellt wieder keiner auf. Diese
   hier trägt die Akzentfarbe als Rahmen und Schrift und fuellt sich erst beim
   Ueberfahren - deutlich genug, um gefunden zu werden, ohne dem Speichern die Rolle
   streitig zu machen. */
.btn.btn-add {
    border-color: var(--accent);
    color: var(--accent);
    background: var(--accent-soft);
    font-weight: 600;
}
.btn.btn-add:hover {
    background: var(--accent);
    color: var(--on-accent);
    border-color: var(--accent);
}
/* Das Plus dieses Knopfes kommt aus derselben Hülle wie jedes andere Zeichen in der
   Leiste (.btn-ico) – hier bleibt nur seine Farbe, die der Akzent des Knopfes ist. */

/* Die Meldezeile grenzt sich nach unten ab, damit sie nicht mit dem Inhalt verschwimmt -
   bei einem Panel mit dunklem Koerper sass sie sonst scheinbar direkt auf dem Messwert. */
.lp-alarmbar + .panel-body { border-top: 1px solid rgba(0,0,0,0.18); }

/* ============================================================================
   ODX-Workspace

   Die Ansichten des Workspace zeigen fast durchgehend Kennungen, Hex und Zahlen -
   also Text, den man zeichenweise liest und vergleicht. Deshalb steht hier haeufiger
   die Schreibmaschinenschrift als sonst im Portal, und die Zeilen sind enger: ein
   Parameterbaum mit 40 Zeilen soll auf einen Bildschirm passen, sonst scrollt man
   zwischen Byte und Bedeutung hin und her.
   ============================================================================ */

/* ---------- Chip-Varianten, die es bisher nur als Sonderfall gab ---------- */
.chip.ok     { border-color: var(--ok); color: var(--ok); }
.chip.warn   { border-color: var(--warn); color: var(--warn); }
.chip.danger { border-color: var(--fault); color: var(--fault); }

/* ---------- Bestand ---------- */
.odx-inv { display: flex; flex-direction: column; gap: 14px; }
.odx-inv-meta { display: flex; gap: 14px; flex-wrap: wrap; font-size: 11.5px; margin-bottom: 10px; }

/* Der erste Schritt als Zeile über dem Bestand. Eine abgeblendete Reiterleiste sieht nach
   einem Fehler aus; hier steht, dass noch etwas zu tun ist und was. */
.odx-onboard {
    display: flex; align-items: flex-start; gap: 12px;
    padding: 12px 14px; border-radius: var(--radius);
    border: 1px solid var(--accent); background: var(--accent-soft);
    font-size: 12.5px; line-height: 1.5;
}
.odx-onboard.ok { border-color: var(--line-strong); background: var(--panel-inset); }
.odx-onboard-step {
    flex: none; width: 22px; height: 22px; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--accent); color: var(--bg);
    font-family: var(--font-mono); font-size: 12px; font-weight: 600;
}
.odx-onboard.ok .odx-onboard-step { background: var(--ok); }
.odx-onboard .text-dim { font-size: 11.5px; }

/* Neue Container einspielen. Der Bereich ist eingeklappt, bis jemand ihn braucht: er ist
   ein seltener Vorgang und stünde sonst über der Liste, die man täglich benutzt. */
.odx-upload {
    display: flex; gap: 16px; flex-wrap: wrap; align-items: flex-start;
    padding: 12px; margin-bottom: 12px;
    background: var(--panel-inset); border: 1px solid var(--accent); border-radius: var(--radius-sm);
}
.odx-upload input[type="file"] { padding: 6px 8px; font-size: 12px; height: auto; cursor: pointer; }
.odx-upload .spinner { width: 16px; height: 16px; border-width: 2px; }

/* Die Bestandsliste rollt in sich: der Kopf mit Suche und Filtern bleibt stehen, während
   man die Treffer durchgeht. Ohne das wanderte er beim Blättern aus dem Bild und man
   scrollte für jede Filteränderung wieder nach oben. */
.odx-inv-scroll { max-height: 58vh; overflow: auto; }
@media (max-height: 900px) { .odx-inv-scroll { max-height: 48vh; } }

/* Das Bestandsverzeichnis ist eine Auskunft und kein Eingabefeld – es liegt fest. */
.odx-inv-path {
    display: flex; flex-direction: column; gap: 2px;
    padding: 9px 12px; margin-bottom: 12px;
    background: var(--panel-inset); border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.odx-inv-path .mono { font-size: 12px; word-break: break-all; }

.odx-scan { display: flex; align-items: center; gap: 12px; padding: 10px 0; }
.odx-bar { height: 4px; background: var(--panel-inset); border-radius: 2px; overflow: hidden; }
.odx-bar-fill { height: 100%; background: var(--accent); transition: width .3s ease; }

.odx-stats { display: flex; gap: 10px; flex-wrap: wrap; }
.odx-stat {
    display: flex; flex-direction: column; gap: 1px; min-width: 110px;
    padding: 8px 12px; background: var(--panel-inset);
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    font-size: 11px; color: var(--text-dim);
}
.odx-stat-num { font-family: var(--font-mono); font-size: 17px; color: var(--text); }
.odx-stat-dim .odx-stat-num { color: var(--text-dim); }

.odx-row-active > td { background: var(--accent-soft); }
button.chip.odx-chip-ecu { cursor: pointer; font-size: 10.5px; height: 21px; }
button.chip.odx-chip-ecu:hover { border-color: var(--accent); color: var(--accent); }

/* ---------- Zweiteilung Liste / Detail ---------- */
/* Die Liste bekommt eine feste Spalte: sie ist Navigation und soll beim Blaettern durch
   die Detailansicht nicht springen. */
.odx-split { display: grid; grid-template-columns: 320px minmax(0, 1fr); gap: 14px; align-items: start; }
@media (max-width: 1100px) { .odx-split { grid-template-columns: 1fr; } }
.odx-split-list { position: sticky; top: 12px; }
.odx-split-detail { min-width: 0; }

.odx-list { display: flex; flex-direction: column; gap: 2px; max-height: 62vh; overflow: auto; }
.odx-list-item {
    display: flex; align-items: center; gap: 8px; justify-content: space-between;
    padding: 6px 8px; border-radius: var(--radius-sm); cursor: pointer;
    border: 1px solid transparent;
}
.odx-list-item:hover { background: var(--panel-inset); }
.odx-list-item.active { background: var(--accent-soft); border-color: var(--accent); }
.odx-list-name { font-size: 12px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.odx-list-hex { font-size: 10.5px; color: var(--text-dim); flex: none; }

/* ---------- Destillat: Dienstauswahl ----------
   Eine Auswahlliste und keine Navigationsliste: die Zeile ist eine Kästchenzeile, und die
   Beschriftung gehört links an den Rand. Vorher trug sie .odx-list-item mit
   justify-content:space-between – der Name saß dann je nach Zeile mittig oder am Rand,
   weil nur manche Dienste einen Hex-Vorsatz haben.

   Kompakter als die Dienstliste des Viewers: hier zählt, wie viele Dienste gleichzeitig zu
   sehen sind – man hakt zwanzig Stück an und will dabei nicht rollen. */
.odx-pick { display: flex; flex-direction: column; gap: 1px; max-height: 66vh; overflow: auto; }
.odx-pick-row {
    display: grid; grid-template-columns: 16px minmax(0, 1fr) auto;
    align-items: center; gap: 8px;
    padding: 3px 6px; border-radius: var(--radius-sm);
    border: 1px solid transparent; cursor: pointer; text-align: left;
}
.odx-pick-row:hover { background: var(--panel-inset); }
.odx-pick-row.on { background: var(--accent-soft); border-color: var(--accent); }
.odx-pick-row input { width: 14px; height: 14px; accent-color: var(--accent); cursor: pointer; }
.odx-pick-name {
    font-family: var(--font-mono); font-size: 11.5px; line-height: 1.35;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.odx-pick-sub {
    font-size: 10px; color: var(--text-faint); line-height: 1.3;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.odx-pick-hex { font-family: var(--font-mono); font-size: 10px; color: var(--text-dim); flex: none; }

/* Die Hauptaktion des Reiters. Sie stand als btn-sm in der Kopfzeile des Panels und ging
   dort neben den Feldern unter – sie ist der Grund, warum man hier ist. */
.odx-act {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line);
}
.odx-act .btn-lg {
    height: 42px; padding: 0 24px; font-size: 13.5px; font-weight: 600; flex: none;
}
/* Der Wartekreisel ist auf 26px ausgelegt – in einem Knopf ist das ein Rad. */
.odx-act .btn .spinner { width: 14px; height: 14px; border-width: 2px; }
.odx-act-note { font-size: 11.5px; color: var(--text-dim); flex: 1; min-width: 200px; line-height: 1.45; }

/* ---------- Destillat: Befund ----------
   Der Befund ist eine Aussage und keine Kacheltafel: oben eine Zeile, die sagt, ob das
   Extrakt taugt, darunter die Zahlen und erst dann die Einzelheiten. */
.odx-verdict {
    display: flex; align-items: center; gap: 12px;
    padding: 12px 14px; border-radius: var(--radius); margin-bottom: 12px;
    border: 1px solid var(--ok); background: rgba(60,200,120,0.08);
}
.odx-verdict.bad { border-color: var(--fault); background: rgba(240,72,62,0.08); }
.odx-verdict-mark {
    flex: none; width: 26px; height: 26px; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--ok); color: var(--bg); font-size: 14px; font-weight: 700;
}
.odx-verdict.bad .odx-verdict-mark { background: var(--fault); }
.odx-verdict-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.odx-verdict-text strong { font-size: 13px; }
.odx-verdict-text span { font-size: 11.5px; color: var(--text-dim); }

/* Die beiden Wege nach dem Destillieren: laden oder festschreiben. Beide gehören nach
   oben und groß – sie sind der Abschluss des Vorgangs. */
.odx-verdict-acts { margin-left: auto; display: flex; gap: 8px; flex: none; }
.odx-verdict-acts .btn { height: 34px; padding: 0 16px; font-size: 12.5px; font-weight: 600; }

/* Vier Zahlen in einer Reihe, gleich breit – als Zeile lesbar und nicht als Kachelhaufen. */
.odx-figures { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; }
.odx-figure {
    display: flex; flex-direction: column; gap: 1px;
    padding: 8px 11px; background: var(--panel-inset);
    border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.odx-figure-num { font-family: var(--font-mono); font-size: 18px; line-height: 1.1; }
.odx-figure-cap { font-size: 10.5px; color: var(--text-dim); }
.odx-figure.warn { border-color: var(--warn); }
.odx-figure.warn .odx-figure-num { color: var(--warn); }
.odx-figure.bad { border-color: var(--fault); }
.odx-figure.bad .odx-figure-num { color: var(--fault); }
.odx-figure.dim .odx-figure-num { color: var(--text-dim); font-size: 15px; }

/* Der Mengenvergleich Quelle/Extrakt: eine Balkenzeile je Elementart. Eine Prozentzahl in
   einer Tabellenspalte muss man lesen, einen Balken sieht man. */
.odx-reduce { display: flex; flex-direction: column; gap: 2px; margin-top: 4px; }
/* Gedeckelt: ein Balken über die ganze Breite eines 27-Zoll-Schirms sagt nicht mehr als
   einer über 300 Pixel, aber die Zeile wird unlesbar weit. */
.odx-reduce-row {
    display: grid; grid-template-columns: minmax(110px, 190px) 84px minmax(60px, 300px) 46px;
    align-items: center; gap: 12px; font-size: 11.5px; padding: 2px 0;
}
.odx-reduce-kind { color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.odx-reduce-num { font-family: var(--font-mono); font-size: 11px; text-align: right; color: var(--text-dim); }
.odx-reduce-num b { color: var(--text); font-weight: 500; }
.odx-reduce-bar { height: 5px; background: var(--panel-inset); border-radius: 3px; overflow: hidden; }
.odx-reduce-bar > i { display: block; height: 100%; background: var(--accent); border-radius: 3px; }
.odx-reduce-pct { font-family: var(--font-mono); font-size: 10.5px; color: var(--text-faint); text-align: right; }

/* Die Einzelheiten stehen zugeklappt darunter: beim Übernehmen liest sie niemand, beim
   Nachsehen will man sie vollständig. */
.odx-fold { margin-top: 12px; border-top: 1px solid var(--line); padding-top: 10px; }
.odx-fold > summary {
    cursor: pointer; font-family: var(--font-mono); font-size: 10.5px;
    letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-dim);
    list-style: none;
}
.odx-fold > summary::-webkit-details-marker { display: none; }
.odx-fold > summary::before { content: "▸ "; }
.odx-fold[open] > summary::before { content: "▾ "; }
.odx-fold > summary:hover { color: var(--text); }
.odx-fold-body { padding: 8px 0 2px; }

/* ---------- Dienst-Detail ---------- */
.odx-detail { display: flex; flex-direction: column; gap: 12px; }
.odx-desc { font-size: 12.5px; display: flex; flex-direction: column; gap: 3px; }
.odx-meta-row { display: flex; gap: 6px; flex-wrap: wrap; }
.odx-built {
    display: flex; flex-direction: column; gap: 5px;
    padding: 10px 12px; background: var(--panel-inset);
    border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.odx-hex { font-size: 12.5px; letter-spacing: 0.06em; word-break: break-all; }

.odx-tmpl { border: 1px solid var(--line); border-radius: var(--radius-sm); overflow: hidden; }
.odx-tmpl-head {
    display: flex; align-items: center; gap: 8px; padding: 7px 10px;
    background: var(--panel-inset); cursor: pointer;
}
.odx-tmpl-table td, .odx-tmpl-table th { padding: 4px 8px; }

/* ---------- Parameter-Trace ---------- */
.odx-param { border-top: 1px solid var(--line); }
.odx-param:first-child { border-top: none; }
.odx-param-child { margin-left: 14px; border-left: 1px solid var(--line); border-top: none; }
.odx-param-head {
    display: flex; align-items: center; gap: 8px; padding: 5px 6px;
    cursor: pointer; font-size: 12px;
}
.odx-param-head:hover { background: var(--panel-inset); }
.odx-param-caret { display: inline-block; width: 10px; transition: transform .12s; color: var(--text-dim); flex: none; }
.odx-param-caret.open { transform: rotate(90deg); }
.odx-param-name { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.odx-param-pos { font-size: 10.5px; flex: none; }
.odx-param-raw { font-size: 11px; color: var(--accent); flex: none; }
.odx-param-phys { margin-left: auto; font-size: 11.5px; flex: none; }
.odx-param-body { padding: 4px 6px 10px 24px; }
.odx-param-collapsed { padding: 0 6px 5px 24px; font-size: 10.5px; }
.chip.odx-chip-type { height: 18px; padding: 0 7px; font-size: 9.5px; flex: none; }

.odx-trace-grid {
    display: grid; grid-template-columns: 108px minmax(0, 1fr);
    gap: 2px 10px; font-size: 11.5px;
}
.odx-trace-key { color: var(--text-faint); }
.odx-trace-val { word-break: break-word; }

.odx-issues { display: flex; flex-direction: column; gap: 4px; margin-top: 8px; }
.odx-issue {
    display: flex; gap: 7px; align-items: flex-start; font-size: 11.5px;
    padding: 5px 8px; border-radius: var(--radius-sm);
    border: 1px solid var(--line);
}
.odx-issue.err  { border-color: var(--fault); color: var(--fault); background: rgba(240,72,62,0.08); }
.odx-issue.warn { border-color: var(--warn); color: var(--warn); background: rgba(245,166,35,0.08); }
.odx-notes { margin-top: 6px; font-size: 11px; color: var(--text-faint); }
.odx-note-line { font-size: 12px; color: var(--text-dim); padding: 2px 0; }

/* ---------- Interpreter ---------- */
.odx-interp { display: flex; flex-direction: column; gap: 14px; }
/* Der passende Kandidat trägt links eine gruene Kante, ein unpassender eine gelbe -
   bei zehn Kandidaten untereinander findet man den Treffer sonst nur durch Lesen. */
.odx-cand-exact { border-left: 3px solid var(--ok); }
.odx-cand-weak  { border-left: 3px solid var(--warn); }

/* ---------- Simulation ---------- */
.odx-exchange { display: flex; flex-direction: column; gap: 4px; }
.odx-exchange-line { display: flex; gap: 10px; align-items: baseline; font-size: 12px; }
.odx-dir { font-family: var(--font-mono); font-size: 10.5px; color: var(--text-faint); flex: none; width: 62px; }
.odx-dir-out { color: var(--accent); }
.odx-dir-in  { color: var(--ok); }

/* ---------- Dokument ---------- */
.odx-doc { display: flex; flex-direction: column; gap: 14px; }
.odx-tree { max-height: 66vh; overflow: auto; font-size: 12px; }
.odx-node-head {
    display: flex; align-items: center; gap: 7px; padding: 2px 4px;
    cursor: pointer; border-radius: 4px; white-space: nowrap;
}
.odx-node-head:hover { background: var(--panel-inset); }
.odx-node-head.leaf { cursor: default; }
.odx-node-dot { width: 10px; color: var(--text-faint); flex: none; }
.odx-node-name { font-family: var(--font-mono); font-size: 11.5px; color: var(--accent); }
.odx-node-sn { font-size: 11.5px; }
.odx-node-attr { font-size: 10.5px; color: var(--text-faint); }
.odx-node-attr-val { color: var(--text-dim); }
.odx-node-text { font-size: 11.5px; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; }
.odx-node-count { font-size: 10px; }
.odx-node-children { margin-left: 14px; border-left: 1px solid var(--line); padding-left: 4px; }
.odx-node-more { height: 22px; font-size: 10.5px; margin: 2px 0 2px 14px; }

.odx-xml { max-height: 66vh; overflow: auto; background: var(--panel-inset); border-radius: var(--radius-sm); padding: 8px 0; }
.odx-xml-line { display: flex; gap: 12px; font-family: var(--font-mono); font-size: 11px; line-height: 1.55; padding: 0 10px; }
.odx-xml-line.hit { background: var(--accent-soft); }
.odx-xml-no { color: var(--text-faint); text-align: right; width: 58px; flex: none; user-select: none; }
.odx-xml-text { white-space: pre; }
.odx-edit { width: 100%; min-height: 420px; font-size: 11.5px; line-height: 1.5; resize: vertical; white-space: pre; overflow-wrap: normal; overflow-x: auto; }

/* ---------- Analyse: der Auswertungsbericht der Zeitreihen ---------- */
/* Ein Bericht und keine Kacheltafel. Oben die Reiterzeile, darunter der gewählte
   Abschnitt – und nur der rollt: Reiter, Werkzeugleiste und Datenhinweis bleiben stehen,
   auch bei einer Tabelle über mehrere Bildschirmhöhen. */
.an-body { flex: 1; min-height: 0; overflow: hidden; display: flex; flex-direction: column; gap: 12px; }
.an-sections { flex: 1; min-height: 0; overflow: auto; padding-right: 2px; }

/* Die Reiterzeile der Abschnitte, dahinter der Satz, der den ganzen Bericht einordnet.
   Der Satz darf umbrechen und schrumpfen – er ist nicht der Grund, warum die Zeile zu
   breit wird –, die Reiter dagegen bleiben immer vollständig. */
.an-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.an-head .an-note { flex: 1 1 200px; min-width: 0; }
/* Sechs Reiter mit Abzeichen passen auf einem schmalen Fenster nicht in eine Zeile. Sie
   brechen dann innerhalb ihres Kastens um, statt seitlich hinauszulaufen – ein Reiter, der
   aus dem Bild ragt, ist keiner. */
.an-tabs { flex: 0 1 auto; flex-wrap: wrap; max-width: 100%; }
.an-tabs button { display: inline-flex; align-items: center; gap: 6px; }

/* Das Abzeichen am Reiter: neutral, solange es nur eine Menge ist (Signalpaare), grün mit
   Haken, wenn geprüft und nichts gefunden, und in Warn- bzw. Fehlerfarbe mit der Anzahl,
   sobald etwas dasteht. Ohne diesen Unterschied läse sich „28“ neben „Zusammenhänge“ wie
   achtundzwanzig Probleme.

   Die Farben sind die der Seite (--ok/--warn/--fault) und die Flächen bleiben schwach:
   ein Bericht, in dem sechs Reiter leuchten, sagt nichts mehr aus. */
.an-badge {
    min-width: 17px; height: 15px; padding: 0 4px; border-radius: 7px;
    display: inline-flex; align-items: center; justify-content: center;
    font-family: var(--font-mono); font-size: 10px; line-height: 1; font-variant-numeric: tabular-nums;
    background: var(--panel-inset); color: var(--text-dim); border: 1px solid var(--line);
}
.tabs button.active .an-badge { background: var(--panel-inset); }
.an-badge.ok    { background: color-mix(in srgb, var(--ok) 12%, transparent);    color: var(--ok);    border-color: color-mix(in srgb, var(--ok) 45%, transparent); }
.an-badge.warn  { background: color-mix(in srgb, var(--warn) 14%, transparent);  color: var(--warn);  border-color: color-mix(in srgb, var(--warn) 55%, transparent); }
.an-badge.fault { background: color-mix(in srgb, var(--fault) 14%, transparent); color: var(--fault); border-color: color-mix(in srgb, var(--fault) 55%, transparent); }

/* Das Zeichen in der Tabelle: nur Glyphe und Farbe, keine Fläche. In einer Tabelle mit
   zwanzig Zeilen wären zwanzig farbige Plaketten lauter als die Zahlen daneben.
   „Nicht geprüft“ bekommt einen Strich in Grau und ausdrücklich keinen Haken. */
.an-mark { font-size: 12px; line-height: 1; color: var(--text-faint); }
.an-mark.ok    { color: var(--ok); }
.an-mark.warn  { color: var(--warn); }
.an-mark.fault { color: var(--fault); }
.an-mark.none  { color: var(--text-faint); }

.an-th-mark, .an-td-mark { width: 22px; padding-right: 0 !important; text-align: center; }

/* Die auffällige Zeile bekommt eine Kante und keine Färbung: ein eingefärbter Hintergrund
   über die ganze Breite überstrahlt die Zahlen, um die es geht. Zeilen ohne Befund bleiben
   ganz unmarkiert – sonst trüge jede Zeile etwas, und keine sagte mehr etwas. */
.an-row-warn  > td:first-child { box-shadow: inset 2px 0 0 color-mix(in srgb, var(--warn) 70%, transparent); }
.an-row-fault > td:first-child { box-shadow: inset 2px 0 0 var(--fault); }

/* Ein Messwert außerhalb der physikalischen Grenze. Nur diese eine Zahl, damit zu sehen
   ist, welcher der beiden Ränder gemeint ist. */
.an-bad { color: var(--fault); font-weight: 600; }

/* Befund-Plakette in den aufklappbaren Blöcken – dieselbe Form wie .chip.accent, aber in
   der Warnfarbe: die Akzentfarbe der Seite ist das CI-Blau und kein Warnsignal. */
.chip.warn  { color: var(--warn);  border-color: color-mix(in srgb, var(--warn) 55%, transparent);  background: color-mix(in srgb, var(--warn) 12%, transparent); }
.chip.fault { color: var(--fault); border-color: color-mix(in srgb, var(--fault) 55%, transparent); background: color-mix(in srgb, var(--fault) 12%, transparent); }

/* Die Notiz neben den Zahlen darf umbrechen und schrumpfen – sie ist der Satz, der die
   Zahlen einordnet, und nicht der Grund, warum die Zeile zu breit wird. */
.an-note { font-size: 10.5px; line-height: 1.4; }

/* Was für diesen einen Abschnitt gilt: die Schwelle, das Fenster, das Raster. Steht über
   der Tabelle, weil es beim Lesen der Zahlen darunter gebraucht wird. */
.an-sec-note { margin: 0; font-size: 10.5px; line-height: 1.4; color: var(--text-dim); }

.an-warn {
    display: flex; align-items: flex-start; gap: 8px;
    padding: 7px 10px; border-radius: var(--radius-sm);
    border: 1px solid color-mix(in srgb, var(--warn) 55%, transparent);
    background: color-mix(in srgb, var(--warn) 10%, transparent);
    color: var(--text); font-size: 11.5px; line-height: 1.5;
}
.an-warn .an-mark { margin-top: 2px; flex: none; }

/* „Nichts gefunden“ ist ein Ergebnis: der leere Abschnitt bekommt einen Haken und liest
   sich damit als geprüft und in Ordnung – nicht als Liste, die noch nicht geladen ist. */
.an-clear { display: flex; align-items: center; justify-content: center; gap: 8px; }

.an-sec { display: flex; flex-direction: column; gap: 7px; }

/* Der Satz unter einer Tabelle sagt, was ihre Zahlen bedeuten und wo sie aufhören zu
   gelten. Er steht bewusst in Fließtextbreite und nicht über die ganze Tabelle: gelesen
   wird er als Satz und nicht als Fußzeile einer Spalte. */
.an-foot { margin: 0; max-width: 96ch; font-size: 11px; line-height: 1.55; color: var(--text-dim); }

.an-table { font-size: 12px; }
.an-table th, .an-table td { padding: 5px 8px; white-space: nowrap; }
.an-table td.num, .an-table th.num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; text-align: right; }
.an-sub-table { background: var(--panel-inset); border-radius: var(--radius-sm); }
.an-sub-table th { background: transparent; position: static; }

/* Der Farbpunkt ist dieselbe Farbe wie die Kurve im Diagramm. Ohne ihn müsste man beim
   Blättern zwischen Diagramm und Bericht die Namen vergleichen. */
.an-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; margin-right: 6px; vertical-align: middle; flex: none; }

/* Zwei Klassen tief, damit die Zeilenhöhe der Tabelle gegen `.ts-page .select` besteht –
   ein 29 px hohes Auswahlfeld sprengte die Zeile, in der es steht. */
.an-body .an-unit { height: 22px; font-size: 11px; padding: 0 20px 0 6px; background-position: right 5px center; min-width: 74px; }

/* Zweite Zeile in einer Zahlenzelle: dieselbe Größe in einer anderen Einheit. */
.an-sub { display: block; font-size: 10px; color: var(--text-dim); font-family: var(--font-mono); }
.an-sub-inline { margin-left: 6px; font-size: 10.5px; }

.an-f { margin: 0; width: 152px; }

/* ---------- DiagX Assist: der Befund, den ein Sprachmodell schreibt ----------

   Dieser Abschnitt darf aussehen wie etwas anderes als der Rest des Berichts, und er
   soll es auch: Alles daneben sind gerechnete Zahlen, das hier ist ein geschriebener
   Text. Der Unterschied ist keine Zierde, sondern die Aussage – wer den Verlauf sieht,
   weiß auf einen Blick, dass er hier etwas Formuliertes liest und keine Messung.

   Die Farben kommen nicht aus der Statuspalette. Grün, Amber und Rot sind in diesem
   Portal Urteile über ein Fahrzeug; ein Assistent, der sich daraus bedient, behauptete
   mit seiner Farbe etwas über die Messung. Der Verlauf mischt stattdessen den
   CI-Akzent mit Violett und Cyan – über color-mix aus dem Akzent des jeweiligen Themes
   gebildet, damit er in allen acht Themen sitzt und in den hellen dunkel genug für
   weiße Schrift bleibt.

   Es sind zwei Verläufe und nicht einer, weil sie zwei verschiedene Aufgaben haben.
   --ai-grad liegt unter weißer Schrift und bleibt deshalb akzentnah und dunkel genug,
   auch wenn das Theme hell ist. --ai-vivid trägt keine Schrift, sondern Ränder, Punkte
   und das Funkeln – dort darf und soll man Cyan und Violett wirklich sehen. Mit nur
   einem Verlauf wäre entweder der Knopf unlesbar oder der Rand ein blauer Strich. */
:root {
    --ai-a: var(--accent);
    --ai-b: color-mix(in srgb, var(--accent) 45%, var(--s-violet));
    --ai-c: color-mix(in srgb, var(--accent) 55%, var(--s-cyan));
    --ai-b2: color-mix(in srgb, var(--accent) 14%, var(--s-violet));
    --ai-c2: color-mix(in srgb, var(--accent) 20%, var(--s-cyan));
    --ai-grad:  linear-gradient(115deg, var(--ai-c) 0%, var(--ai-a) 45%, var(--ai-b) 100%);
    --ai-vivid: linear-gradient(115deg, var(--ai-c2) 0%, var(--ai-a) 42%, var(--ai-b2) 100%);
}

/* Der Knopf, der den Abschnitt aufruft – in der Werkzeugleiste des Berichts und im
   Reiter selbst. Gefüllt und nicht umrandet: Er ist die einzige Stelle im Bericht, an
   der etwas Neues entsteht. */
.btn-ai {
    background: var(--ai-grad); background-size: 160% 160%;
    /* Die Schriftfarbe kommt aus dem Theme und ist nicht fest weiß: Zwei der acht Themen
       (amber, midnight) tragen auf ihrem Akzent dunkle Schrift, und weiß darauf wäre
       kaum zu lesen. */
    color: var(--on-accent); border-color: transparent; font-weight: 600;
}
.btn-ai:hover { filter: brightness(1.09); box-shadow: 0 8px 20px -10px var(--accent-glow); }
.btn-ai:disabled { filter: grayscale(0.7) opacity(0.5); box-shadow: none; }

/* Das Funkeln trägt den Verlauf als Schrift-Füllung. Es steht im Reiter und im Knopf
   und ist das einzige, was diesen Abschnitt in einer Zeile aus sechs Reitern markiert. */
.ai-spark { background: var(--ai-vivid); -webkit-background-clip: text; background-clip: text; color: transparent; }
.btn-ai .ai-spark { background: none; color: var(--on-accent); }

/* Der eine Reiter, hinter dem keine Tabelle steht. Er trägt eine getönte Kante statt der
   grauen – gerade so viel, dass das Auge ihn in einer Zeile aus sieben Reitern zuerst
   findet, und nicht so viel, dass er wie ein Alarm aussieht. */
.an-tabs button.ai-tab { border-color: color-mix(in srgb, var(--ai-b2) 42%, transparent); }
.an-tabs button.ai-tab:hover { background: color-mix(in srgb, var(--ai-b2) 10%, transparent); }
.an-tabs button.ai-tab.active {
    border-color: color-mix(in srgb, var(--ai-b2) 62%, transparent);
    background: color-mix(in srgb, var(--ai-b2) 13%, var(--panel-raised));
}
.an-tabs button.active .ai-spark { background: var(--ai-vivid); -webkit-background-clip: text; background-clip: text; color: transparent; }

/* Die Karte. Der Verlauf liegt als ein Pixel breiter Rand darunter – ein gefüllter
   Kasten wäre in einem Bericht aus Tabellen ein Fremdkörper, ein leuchtender Rand ist
   eine Auszeichnung. Während das Modell schreibt, wandert er. */
.ai-hero {
    border-radius: var(--radius); padding: 1px;
    background: var(--ai-vivid); background-size: 220% 220%;
    box-shadow: 0 16px 40px -26px color-mix(in srgb, var(--ai-b2) 75%, transparent);
}
.ai-hero.busy { animation: ai-flow 4.5s linear infinite; }
.ai-hero-in {
    border-radius: calc(var(--radius) - 1px);
    /* Ein Schimmer aus der oberen rechten Ecke. Er ist der Grund, warum die Karte auch
       ohne gefüllten Rahmen als eigener Bereich gelesen wird – eine Fläche in Panelfarbe
       mit einem Strich drumherum wäre nur ein Kasten mehr. */
    background:
        radial-gradient(130% 90% at 100% 0%, color-mix(in srgb, var(--ai-b2) 13%, transparent) 0%, transparent 62%),
        radial-gradient(90% 70% at 0% 100%, color-mix(in srgb, var(--ai-c2) 9%, transparent) 0%, transparent 58%),
        var(--panel-raised);
    padding: 15px 16px; display: flex; flex-direction: column; gap: 13px;
}
@keyframes ai-flow { 0% { background-position: 0% 50%; } 100% { background-position: 200% 50%; } }

.ai-head { display: flex; align-items: center; gap: 11px; flex-wrap: wrap; }
.ai-avatar {
    width: 37px; height: 37px; border-radius: 11px; flex: none;
    background: var(--ai-vivid); color: var(--on-accent);
    display: grid; place-items: center;
    box-shadow: 0 7px 18px -8px color-mix(in srgb, var(--ai-b2) 80%, transparent);
}
.ai-id { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1 1 200px; }
.ai-name { font-family: var(--font-ui); font-size: 13.5px; font-weight: 600; color: var(--text); letter-spacing: -0.01em; }
.ai-sub { font-size: 10.5px; color: var(--text-dim); }

/* Was der Assistent tut, in drei Worten je Plakette. Steht nur im leeren Zustand: Wer
   den Text vor sich hat, braucht das Versprechen nicht mehr. */
.ai-caps { display: flex; gap: 6px; flex-wrap: wrap; }
.ai-cap {
    font-size: 10.5px; padding: 3px 9px; border-radius: 999px;
    border: 1px solid color-mix(in srgb, var(--ai-a) 35%, transparent);
    background: color-mix(in srgb, var(--ai-a) 8%, transparent);
    color: var(--text-dim);
}

/* Der Befund selbst. Der erste Satz ist das Gesamturteil und steht deshalb größer und
   in der Schrift der Oberfläche – er ist die Zeile, die auch liest, wer sonst nichts
   liest. Die Befunde darunter tragen einen Punkt im Verlauf statt eines Strichs. */
.ai-lead {
    margin: 0; max-width: 80ch;
    font-family: var(--font-ui); font-size: 14.5px; line-height: 1.5; font-weight: 600;
    color: var(--text); letter-spacing: -0.01em;
}
.ai-points { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 9px; }
.ai-point { display: flex; gap: 10px; max-width: 92ch; font-size: 12.5px; line-height: 1.62; color: var(--text); }
.ai-point::before {
    content: ""; width: 6px; height: 6px; border-radius: 50%; margin-top: 7px; flex: none;
    background: var(--ai-vivid);
}
.ai-para { margin: 0; max-width: 92ch; font-size: 12.5px; line-height: 1.62; color: var(--text-dim); }

/* Der Schreibbalken am Textende. Er ist das Einzige, was sagt, dass noch etwas kommt –
   ohne ihn liest sich ein halber Satz wie ein abgebrochener. */
.ai-caret {
    display: inline-block; width: 2px; height: 1em; margin-left: 2px;
    vertical-align: -0.15em; background: var(--ai-b); animation: ai-blink 1.05s step-end infinite;
}
@keyframes ai-blink { 0%, 50% { opacity: 1; } 50.01%, 100% { opacity: 0; } }

/* Denkt das Modell, kommt noch kein Wort. Drei laufende Punkte sagen das besser als ein
   Kreisel, der auch beim Laden einer Tabelle steht. */
.ai-status { display: flex; align-items: center; gap: 8px; font-size: 11.5px; color: var(--text-dim); }
.ai-dots { display: inline-flex; gap: 4px; }
.ai-dots i { width: 5px; height: 5px; border-radius: 50%; background: var(--ai-b); animation: ai-bounce 1.25s ease-in-out infinite; }
.ai-dots i:nth-child(2) { animation-delay: .16s; }
.ai-dots i:nth-child(3) { animation-delay: .32s; }
@keyframes ai-bounce { 0%, 60%, 100% { opacity: .28; transform: translateY(0); } 30% { opacity: 1; transform: translateY(-3px); } }

/* Die Fußzeile: das Modell als Plakette und daneben der Satz, der sagt, was dieser Text
   ist und was er nicht ist. Er bleibt stehen, auch wenn der Abschnitt jetzt schöner
   aussieht – gerade dann. */
.ai-foot { display: flex; align-items: flex-start; gap: 10px; flex-wrap: wrap; padding-top: 2px; border-top: 1px solid var(--line); }
.ai-model {
    display: inline-flex; align-items: center; gap: 6px; flex: none;
    font-family: var(--font-mono); font-size: 10px; padding: 3px 8px; border-radius: 999px;
    border: 1px solid color-mix(in srgb, var(--ai-a) 35%, transparent);
    background: color-mix(in srgb, var(--ai-a) 8%, transparent); color: var(--text-dim);
}
.ai-model::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--ai-vivid); flex: none; }
.ai-note { flex: 1 1 320px; min-width: 0; margin: 0; font-size: 10.5px; line-height: 1.5; color: var(--text-faint); }

.ai-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* Die Faktentafel unter dem Text. Klein, einfarbig und rollend: Sie ist der Beleg und
   nicht der Inhalt – wer sie aufmacht, sucht eine bestimmte Zahl und liest sie nicht
   von oben. */
.an-fi-facts {
    margin: 0; padding: 8px 10px; max-height: 320px; overflow: auto;
    background: var(--panel-inset); border-radius: var(--radius-sm);
    font-family: var(--font-mono); font-size: 10.5px; line-height: 1.5;
    color: var(--text-dim); white-space: pre;
}

@media (prefers-reduced-motion: reduce) {
    .ai-hero.busy, .ai-caret, .ai-dots i { animation: none; }
}

/* Aufklappbare Blöcke je Signal: die Zahl steht in der Kopfzeile, die Liste dahinter.
   Bei acht Signalen mit je fünfundzwanzig Zeilen wäre der Bericht sonst eine Rolle. */
.an-det { border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel-raised); }
.an-det > summary {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    padding: 6px 9px; cursor: pointer; font-size: 12px; list-style: none;
}
.an-det > summary::-webkit-details-marker { display: none; }
.an-det > summary::before { content: "▸"; color: var(--text-faint); font-size: 10px; flex: none; }
.an-det[open] > summary::before { content: "▾"; }
.an-det > summary:hover { background: var(--panel-inset); border-radius: var(--radius-sm); }
.an-det > .table-wrap { padding: 0 9px 9px; }
.an-det > .an-foot { padding: 0 9px 9px; }

/* Befunde der Plausibilisierung: je Regel eine Zeile, damit „12× über 100 %“ und
   „3× zu schnell geändert“ nicht zu einem Satz verkleben. */
.an-findings { display: flex; flex-direction: column; gap: 2px; white-space: normal; }
.an-finding { font-size: 11.5px; line-height: 1.45; }

/* Der Balken zeigt den Betrag von r. Er steht neben der Zahl und nicht statt ihr: die
   Länge ist zum Vergleichen da, die Zahl zum Ablesen. */
.an-bar { display: inline-block; width: 54px; height: 6px; border-radius: 3px; background: var(--panel-inset); overflow: hidden; vertical-align: middle; }
.an-bar > span { display: block; height: 100%; border-radius: 3px; }
.an-corr-strong { background: var(--accent); }
.an-corr-mid { background: color-mix(in srgb, var(--accent) 60%, var(--text-faint)); }
.an-corr-weak { background: var(--text-faint); }

/* ---------- Zusammenhänge: Filter, Zeilen, Punktwolke ---------- */
.an-corr-tools { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px 12px; }
.an-corr-field { display: flex; flex-direction: column; gap: 3px; font-size: 10.5px; color: var(--text-dim); }
.an-corr-field .input, .an-corr-field .select { height: 29px; font-size: 12px; }
.an-corr-search { flex: 1 1 260px; max-width: 380px; }
.an-corr-count { font-size: 11px; color: var(--text-dim); padding-bottom: 6px; }

/* Die Zeile ist der Schalter für ihre Wolke – der Pfeil sagt es, bevor man es probiert. */
.an-corr-table tbody tr.an-corr-row { cursor: pointer; }
.an-corr-table tbody tr.an-corr-row:hover { background: var(--panel-raised); }
.an-corr-table tbody tr.an-corr-row.open { background: color-mix(in srgb, var(--accent) 9%, transparent); }
.an-corr-caret { display: inline-block; width: 12px; margin-right: 4px; font-size: 9px; color: var(--text-faint); transition: transform .15s; }
.an-corr-row.open .an-corr-caret { transform: rotate(90deg); color: var(--accent); }
.an-corr-table tbody tr.an-corr-detail > td { padding: 8px 8px 12px; white-space: normal; background: var(--panel-inset); }

/* Die Tabelle ist breiter als ihr Panel (die Zellen brechen nicht um), und die Detailzeile
   ist so breit wie die Tabelle. Ohne sticky und feste Breite lag die Zahlenspalte rechts
   außerhalb des Sichtbaren; so bleibt die Wolke samt Zahlen links im Blick, auch wenn die
   Tabelle seitlich gerollt ist. */
.an-corr-wrap { container-type: inline-size; }
.an-scatter {
    position: sticky; left: 0; width: min(790px, calc(100cqw - 16px));
    display: grid; grid-template-columns: minmax(0, 540px) 230px; gap: 14px; align-items: start;
}
.an-scatter-plot { min-width: 0; }
.an-scatter-plot svg { display: block; width: 100%; height: auto; }
.an-scatter-side { display: flex; flex-direction: column; gap: 9px; font-size: 11.5px; }

.an-sc-grid { stroke: var(--line); stroke-width: 1; }
.an-sc-frame { fill: none; stroke: var(--line-strong); stroke-width: 1; }
.an-sc-tick { font-family: var(--font-mono); font-size: 10px; fill: var(--text-faint); }
.an-sc-axis { font-family: var(--font-ui); font-size: 11.5px; font-weight: 600; fill: var(--text-dim); }
.an-sc-pt { fill-opacity: .62; }
/* Früh nach spät in fünf Stufen, von der Info- zur Warnfarbe – dieselbe Rampe wie in der Legende. */
.an-sc-t0 { fill: var(--info); }
.an-sc-t1 { fill: color-mix(in srgb, var(--info) 75%, var(--warn)); }
.an-sc-t2 { fill: color-mix(in srgb, var(--info) 50%, var(--warn)); }
.an-sc-t3 { fill: color-mix(in srgb, var(--info) 25%, var(--warn)); }
.an-sc-t4 { fill: var(--warn); }
.an-sc-fit { stroke: var(--text); stroke-width: 1.6; stroke-dasharray: 6 4; opacity: .85; }

.an-sc-facts { display: grid; grid-template-columns: auto 1fr; gap: 3px 10px; margin: 0; }
.an-sc-facts dt { color: var(--text-dim); }
.an-sc-facts dd { margin: 0; }
.an-sc-eq { white-space: normal; word-break: break-word; font-size: 11px; }
.an-sc-legend { display: flex; align-items: center; gap: 6px; font-size: 10.5px; color: var(--text-dim); }
.an-sc-ramp { flex: 1; max-width: 120px; height: 6px; border-radius: 3px; background: linear-gradient(90deg, var(--info), var(--warn)); }
.an-sc-note { font-size: 10.5px; line-height: 1.45; color: var(--text-faint); }

@media (max-width: 900px) {
    .an-scatter { grid-template-columns: 1fr; }
}

/* =====================================================================
   Fahrzeugbilder
   ---------------------------------------------------------------------
   Die Aufnahmen des Katalogs sind Studiorenderings vor dunklem Verlauf, alle im
   Verhaeltnis 3:2. Deshalb steht das Verhaeltnis hier fest und nicht die Hoehe:
   ein Bild, das seine Zeile mal 24 und mal 31 Pixel hoch macht, laesst eine
   Liste flackern. Der eigene Upload wird im Browser auf dasselbe Mass
   beschnitten (js/vehicleimage.js).

   Auf hellen Themes trägt das Bild einen eigenen dunklen Grund: die Renderings
   haben keinen freigestellten Hintergrund, und ein dunkler Kasten auf weissem
   Panel sieht ohne Rahmen wie ein Ladefehler aus.
   ===================================================================== */
.veh-img {
    position: relative; flex: none; display: block;
    aspect-ratio: 3 / 2; overflow: hidden;
    border-radius: var(--radius-sm);
    border: 1px solid var(--line);
    background: var(--panel-inset);
}
.veh-img > img { width: 100%; height: 100%; object-fit: cover; display: block; }

.veh-img-xs { width: 40px; }
.veh-img-sm { width: 56px; }
/* Fuer Listen, in denen das Bild zum Wiedererkennen da ist und nicht als Beiwerk
   neben der Kennung steht - der Fahrzeugbestand. Bei 40 Pixeln ist ein Kombi von
   einem Transporter nicht zu unterscheiden; bei 96 schon, und die Zeile bleibt
   mit 64 Pixeln Hoehe noch eine Tabellenzeile und keine Karte. */
.veh-img-list { width: 96px; }
.veh-img-md { width: 132px; }
.veh-img-lg { width: 200px; }
/* Fuellt die Breite des Kastens - fuer Karten und Panels, wo das Bild die
   Ueberschrift trägt statt sie zu begleiten. */
.veh-img-card { width: 100%; }

/* Kein Bild: die Kontur sitzt mittig und bleibt zurueckhaltend. Sie soll sagen
   „hier gehört ein Fahrzeug hin", nicht um Aufmerksamkeit bitten. */
/* Die Strichzeichnung: nur noch in der Katalogverwaltung, wo sie eine leere Kachel
   markiert. Als Platzhalter fuer ein Fahrzeug wurde sie durch ein Foto ersetzt - siehe
   .veh-img-mock. */
.veh-img-empty { display: grid; place-items: center; color: var(--text-faint); }
.veh-img-empty > svg { width: 62%; height: 62%; opacity: .55; }

/* Der Platzhalter: ein verhuelltes Fahrzeug. Er traegt dieselben Regeln wie ein echtes
   Bild (.veh-img > img), damit sich Zuschnitt und Rahmen nicht unterscheiden - ein
   Platzhalter, der anders sitzt als die Bilder daneben, faellt in einer Liste mehr auf als
   das fehlende Bild selbst.

   Der Unterschied steckt nur im Tooltip und, bei genauem Hinsehen, im Bild: Ein Fahrzeug
   unter einer Plane ist erkennbar kein Modellfoto. Bewusst NICHT abgedunkelt oder
   ausgegraut: Das saehe aus wie ein Ladefehler, und genau davon kommt diese Aenderung.

   Deshalb steht hier auch KEINE eigene Regel. Die Klasse .veh-img-mock gibt es trotzdem
   im Markup: Sie sagt beim Lesen der Seite und beim Pruefen, dass dieses Bild ein
   Platzhalter ist - eine Auskunft, die man aus der Bildadresse sonst erst heraussuchen
   muesste. */

/* Von Hand gesetzt: eine angeschnittene Ecke oben rechts. Kein Text und kein
   Symbol - in einer 40 Pixel breiten Kachel waere beides unlesbar. */
.veh-img-pin {
    position: absolute; top: 0; right: 0; width: 0; height: 0;
    border-top: 9px solid var(--accent);
    border-left: 9px solid transparent;
    opacity: .85;
}

/* Anklickbar: das Bild ist in Listen zugleich der Weg in den Auswahldialog. */
.veh-img-btn { cursor: pointer; padding: 0; background: none; }
.veh-img-btn:hover .veh-img { border-color: var(--accent); }
.veh-img-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-sm); }

/* ---------- Auswahldialog ---------- */
.vip { display: flex; flex-direction: column; gap: 14px; }

.vip-now { display: flex; gap: 16px; align-items: flex-start; }
.vip-now-title { font-family: var(--font-ui); font-size: 14.5px; line-height: 1.3; }

/* Was die VIN hergibt: zwei Spalten, damit die Beschriftungen untereinander
   stehen und sich die Zeilen mit einem Blick abtasten lassen. */
.vip-vin {
    display: grid; grid-template-columns: auto 1fr; gap: 2px 12px;
    margin-top: 6px; padding: 9px 11px;
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--panel-inset);
    font-size: 11.5px;
}
.vip-vin-k { font-family: var(--font-mono); font-size: 10px; letter-spacing: .06em;
             text-transform: uppercase; color: var(--text-dim); white-space: nowrap; }
.vip-vin-v { font-family: var(--font-mono); word-break: break-word; }

.vip-acts { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* Der Hinweis auf die Reichweite steht rechts aussen und leise: er korrigiert
   eine Erwartung, er ist keine Warnung. */
.vip-scope { margin-left: auto; font-size: 11px; }

.vip-filter { display: flex; gap: 8px; align-items: center; }
.vip-filter .input { flex: 1; min-width: 0; }
.vip-filter .select { width: 170px; flex: none; }

.vip-group { margin-top: 2px; }

.vip-grid {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(158px, 1fr));
    gap: 10px;
}
.vip-card {
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--panel-raised); overflow: hidden; cursor: pointer;
    transition: border-color .12s, transform .05s;
}
.vip-card:hover { border-color: var(--accent); }
.vip-card:active { transform: scale(.99); }
.vip-card.active { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.vip-card > img { width: 100%; aspect-ratio: 3 / 2; object-fit: cover; display: block;
                  background: var(--panel-inset); }
.vip-card-body { padding: 7px 9px; display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.vip-card-name { font-size: 12px; font-weight: 600; line-height: 1.25;
                 overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Die Herkunftsmarke an nachgepflegten Modellen. Klein und blass: sie ist eine
   Randnotiz zur Kachel und nicht deren Aussage - die steht im Bild. */
.vip-card-src {
    margin-left: 5px;
    padding: 0 4px;
    border: 1px solid var(--line);
    border-radius: 3px;
    font-size: 9px;
    font-weight: 500;
    color: var(--text-faint);
    vertical-align: 1px;
}
/* Die zweite Zeile trägt bei den Vorschlaegen die Begruendung und im Katalog die
   volle Bezeichnung. Zwei Zeilen Platz, danach abgeschnitten - drei Zeilen
   machten aus dem Raster eine Liste. */
.vip-card-hint {
    font-size: 10.5px; color: var(--text-dim); line-height: 1.3;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.vip-more { font-size: 11.5px; text-align: center; padding: 6px 0 2px; }

/* ---------- Fahrzeugliste, Dashboard, Live-Board ---------- */
/* Die Spalte mit dem Bild trägt keine Ueberschrift und soll auch keine Breite
   verhandeln: sie ist so breit wie das Bild plus Zellenrand. */
table.data td.cell-img, table.data th.cell-img { width: 1%; padding-right: 0; }

/* Auf dem Dashboard sitzt das Bild vor der Kennung. Die Zeile wird dadurch hoeher
   als vorher - das ist beabsichtigt: eine Statuskachel mit zwanzig 20-Pixel-Zeilen
   liest niemand, mit acht Fahrzeugen samt Bild schon. */
.veh-row .veh-img { margin-right: 2px; }
.veh-row-lead { display: flex; align-items: center; gap: 9px; min-width: 0; }

/* Im Gruppenkopf des Live-Boards steht das Bild vor dem Namen des Ordners. Klein
   genug, dass der Kopf einzeilig bleibt. */
.bg-head .veh-img { margin-right: 2px; }

/* =====================================================================
   Kommunikation: Antwortzeiten der Steuergeräte
   ---------------------------------------------------------------------
   Die Ansicht beantwortet zwei Fragen, und die Gestaltung hält sie
   getrennt: was üblich ist (Kennzahlen, Verteilung – ruhig, einfarbig)
   und was herausfällt (Zeitverlauf, Liste – und nur dort Rot).

   Die Farben sind deshalb streng verteilt: --text-faint für den
   Normalfall, --warn für „zäh, aber unter der Schwelle“, --fault für
   Ausreißer und verdrehte Uhren. Kein Grün für „in Ordnung“ außer im
   Häkchen der leeren Befundliste: eine Fläche in Grün neben einer in Rot
   liest sich als Wertung, und die meisten Antwortzeiten sind einfach nur
   unauffällig.
   ===================================================================== */

/* Die Ausreißerschwelle in der Kopfzeile des Panels – dasselbe Maß wie die
   Schwellen der Signalauswertung (.an-f), damit der Wechsel zwischen den beiden
   Reitern die Kopfzeile nicht umbaut. */
.ct-f { margin: 0; width: 152px; flex: none; }

/* Nur der Bericht rollt, nicht die Seite: die Zeitreihenseite bindet ihre Höhe an
   das Fenster (.ts-page), und ein Reiter, der sie sprengt, schiebt Kopf- und
   Auswahlleiste aus dem Bild. Gleiche Lösung wie in der Signalauswertung. */
.ct-body {
    display: flex; flex-direction: column; gap: 18px;
    min-width: 0; min-height: 0; flex: 1; overflow: auto;
    padding-right: 2px;
}

/* ---------- Kennzahlen ---------- */
/* Auto-fit statt fester Spaltenzahl: die Reihe trägt sechs oder sieben Kacheln,
   je nachdem, ob verdrehte Uhren vorkommen. */
.ct-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(148px, 1fr)); gap: 8px; }

.ct-kpi {
    display: flex; flex-direction: column; gap: 1px; min-width: 0;
    padding: 9px 11px;
    background: var(--panel-inset); border: 1px solid var(--line);
    border-left-width: 3px; border-left-color: var(--line-strong);
    border-radius: var(--radius-sm);
}
/* Die Kante links trägt die Wertung, nicht die Fläche: sechs verschieden getönte
   Kacheln nebeneinander wären ein Farbmuster und keine Auskunft. */
.ct-kpi.ok    { border-left-color: var(--ok); }
.ct-kpi.warn  { border-left-color: var(--warn); }
.ct-kpi.alarm { border-left-color: var(--fault); }

.ct-kpi-k {
    font-family: var(--font-mono); font-size: 9.5px; letter-spacing: 0.1em;
    text-transform: uppercase; color: var(--text-dim); white-space: nowrap;
}
.ct-kpi-v {
    font-family: var(--font-ui); font-size: 19px; font-weight: 700; line-height: 1.15;
    color: var(--text); font-variant-numeric: tabular-nums;
}
/* Die dritte Zeile sagt, was die Zahl bedeutet – „19 von 20 sind schneller“
   erklärt p95 besser als jede Legende daneben. Zwei Zeilen Platz, dann
   abgeschnitten: die Kachelhöhe darf nicht am Namen der langsamsten Abfrage
   hängen. */
.ct-kpi-n {
    font-size: 10.5px; line-height: 1.3; color: var(--text-faint);
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

/* ---------- Abschnitte ---------- */
.ct-sec { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.ct-sec-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.ct-sec-head h4 {
    margin: 0; font-family: var(--font-ui); font-size: 13px; font-weight: 600;
    letter-spacing: -0.01em;
}
/* Der Kurzhinweis rechts neben der Abschnittsüberschrift. Eigene Klasse und nicht
   "span": die Legende des Zeitverlaufs ist ebenfalls ein span an dieser Stelle und
   trägt ihre eigene Größe. */
.ct-sec-head .ct-hint { font-size: 11px; color: var(--text-dim); }

/* Der erklärende Satz unter jedem Abschnitt. Er steht bewusst da: eine Kennzahl
   ohne diesen Satz ist eine Zahl, die geglaubt wird, statt gelesen zu werden. */
.ct-note { margin: 0; max-width: 92ch; font-size: 11px; line-height: 1.55; color: var(--text-faint); }

.ct-warn, .ct-clean {
    display: flex; align-items: flex-start; gap: 9px;
    padding: 8px 11px; border-radius: var(--radius-sm);
    font-size: 11.5px; line-height: 1.5; color: var(--text-dim);
}
.ct-warn  { background: color-mix(in srgb, var(--warn) 9%, transparent);
            border: 1px solid color-mix(in srgb, var(--warn) 30%, transparent); }
.ct-clean { background: var(--panel-inset); border: 1px solid var(--line); }

.ct-mark { flex: none; font-size: 12px; line-height: 1.4; }
.ct-mark.ok       { color: var(--ok); }
.ct-mark.warn     { color: var(--warn); }
.ct-mark.slow     { color: var(--warn); }
.ct-mark.outlier  { color: var(--fault); }
.ct-mark.reversed { color: var(--fault); }

/* ---------- Legende des Zeitverlaufs ---------- */
.ct-legend {
    display: inline-flex; align-items: center; gap: 5px; flex-wrap: wrap;
    margin-left: auto; font-size: 10.5px; color: var(--text-dim);
}
.ct-lg { width: 10px; height: 2px; border-radius: 1px; flex: none; margin-left: 6px; }
.ct-lg:first-child { margin-left: 0; }
.ct-lg.normal   { background: var(--text-faint); }
.ct-lg.slow     { background: var(--warn); }
.ct-lg.outlier  { background: var(--fault); height: 3px; }
.ct-lg.reversed { background: var(--fault); opacity: .4; }

/* ---------- Zeitverlauf ---------- */
/* Zwei Spalten: links die Höhenachse als Text, rechts die Zeichenfläche. Der Text
   steht außerhalb des SVG, weil das SVG ungleich skaliert wird
   (preserveAspectRatio="none") – Schrift darin würde in die Breite gezogen. */
.ct-plot { display: grid; grid-template-columns: 52px 1fr; gap: 0 8px; }

.ct-plot-scale { position: relative; }
.ct-plot-lab {
    position: absolute; right: 0; transform: translateY(-50%);
    font-family: var(--font-mono); font-size: 9.5px; white-space: nowrap;
    color: var(--text-faint);
}
/* Median und Schwelle in den Farben ihrer Linien im Diagramm – ohne diese
   Zuordnung sind es drei Zahlen am Rand, von denen man nicht weiß, welche zu
   welcher Linie gehört. */
.ct-plot-lab.med { color: var(--accent); }
.ct-plot-lab.thr { color: var(--warn); }

.ct-plot-area { position: relative; min-width: 0; }
.ct-plot-svg {
    display: block; width: 100%; height: 170px;
    background: var(--panel-inset);
    border: 1px solid var(--line); border-radius: var(--radius-sm);
}

/* Die Marke über einem Ausreißer. Ein einzelner Ausschlag in einem Bild mit
   tausend Nadeln ist sonst genau das, was man übersieht – und er ist der Grund,
   warum man das Bild ansieht. Als Dreieck am oberen Rand, weil die Nadel selbst
   in der Fläche steckt und die Marke darüber hinausragen soll. */
.ct-flag {
    position: absolute; top: -1px; transform: translateX(-50%);
    width: 0; height: 0;
    border-left: 4px solid transparent; border-right: 4px solid transparent;
    border-top: 6px solid var(--fault);
    cursor: help;
}
.ct-flag.rev { opacity: .5; }

.ct-plot-x {
    display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
    margin-left: 60px;
    font-family: var(--font-mono); font-size: 9.5px; color: var(--text-faint);
}
.ct-plot-span { color: var(--text-dim); }

/* ---------- Verteilung und Auffälligkeiten nebeneinander ---------- */
/* Sie beantworten die Gegenfrage zueinander – „wie breit ist das übliche Feld“
   und „was liegt außerhalb“. Untereinander wäre zwischen ihnen eine Rolle. */
.ct-split { display: grid; grid-template-columns: minmax(0, 4fr) minmax(0, 5fr); gap: 20px; }
@media (max-width: 1000px) { .ct-split { grid-template-columns: 1fr; } }

.ct-hist {
    display: flex; align-items: flex-end; gap: 2px; height: 104px;
    padding: 6px 8px;
    background: var(--panel-inset); border: 1px solid var(--line); border-radius: var(--radius-sm);
}
/* min-height, damit ein Balken mit einem einzigen Wert nicht unsichtbar ist:
   „kommt einmal vor“ ist eine andere Auskunft als „kommt nicht vor“. */
.ct-hist-bar {
    flex: 1; min-width: 0; min-height: 2px; border-radius: 1.5px 1.5px 0 0;
    background: var(--accent); opacity: .85; cursor: help;
}
.ct-hist-bar:hover { opacity: 1; }
/* Der Sammelbalken rechts trägt alles über der Skala – er ist keine weitere
   Klasse von Antwortzeiten, sondern der Rest, und steht deshalb gestreift statt
   gefüllt. */
.ct-hist-bar.over {
    background: repeating-linear-gradient(45deg, var(--fault) 0 3px, transparent 3px 6px);
    border: 1px solid color-mix(in srgb, var(--fault) 50%, transparent);
    border-bottom: none; flex: 0 0 14px; margin-left: 6px;
}

.ct-hist-x {
    display: flex; justify-content: space-between; gap: 8px;
    font-family: var(--font-mono); font-size: 9.5px; color: var(--text-faint);
}
.ct-hist-over { color: var(--fault); }

/* ---------- Liste der Auffälligkeiten ---------- */
/* Rollt selbst, statt die Seite zu verlängern: dreißig Zeilen neben einem
   Diagramm von hundert Pixeln Höhe schöben sonst die Tabelle darunter außer
   Sicht. */
.ct-finds {
    display: flex; flex-direction: column; gap: 2px;
    max-height: 232px; overflow: auto; padding-right: 2px;
}
.ct-find {
    display: grid; grid-template-columns: auto 1fr auto auto auto;
    align-items: center; gap: 10px;
    padding: 4px 8px; border-radius: 4px;
    border-left: 2px solid var(--fault);
    background: color-mix(in srgb, var(--fault) 7%, transparent);
    font-size: 11.5px;
}
.ct-find.reversed {
    border-left-style: dashed;
    background: color-mix(in srgb, var(--fault) 4%, transparent);
}
.ct-find-time { color: var(--text-dim); font-size: 10.5px; white-space: nowrap; cursor: help; }
.ct-find-what { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ct-find-ms { font-weight: 600; color: var(--text); white-space: nowrap; }
/* Der Faktor ist die Aussage der Zeile: „×340“ sagt mehr als „1360 ms“, weil
   1360 ms für eine Fehlerspeicherabfrage normal und für einen Kilometerstand ein
   Aussetzer ist. */
.ct-find-fac { color: var(--fault); font-weight: 600; white-space: nowrap; cursor: help; }

/* ---------- Plaketten ---------- */
.ct-tag, .ct-tag-ok {
    display: inline-flex; align-items: center; gap: 3px;
    padding: 1px 6px; border-radius: 999px;
    font-family: var(--font-mono); font-size: 10px; white-space: nowrap;
}
.ct-tag.hot { color: var(--fault); background: color-mix(in srgb, var(--fault) 12%, transparent); }
.ct-tag.rev { color: var(--fault); background: color-mix(in srgb, var(--fault) 8%, transparent); }
.ct-tag.nrc { color: var(--warn); background: color-mix(in srgb, var(--warn) 12%, transparent); }
/* Unauffällig heißt: ein Punkt. Kein „ok“, kein Häkchen, keine Farbe – in einer
   Tabelle mit dreißig Zeilen wäre dreißigmal „ok“ der lauteste Inhalt der Seite,
   und die eine Zeile mit dem Befund ginge darin unter. */
.ct-tag-ok { color: var(--text-faint); cursor: help; }

/* ---------- Tabelle je Abfrageart ---------- */
/* Ohne eigene Höhenbegrenzung: der Bericht rollt als Ganzes (.ct-body). Zwei
   Rollbereiche übereinander sind beim Lesen einer langen Tabelle das Ärgerlichste,
   was man bauen kann – der äußere fängt das Rad, sobald der innere am Ende ist. Das
   waagerechte Rollen bringt .table-wrap schon mit; senkrecht wächst die Tabelle
   einfach mit ihren Zeilen. */
.ct-tw { max-height: none; }
.ct-table { font-size: 12px; }
.ct-table th, .ct-table td { padding: 6px 10px; }

.ct-th-mark { width: 22px; padding-left: 8px; padding-right: 0; }
.ct-th-bar { width: 120px; }
.ct-th-trail { width: 92px; }
.ct-table .ct-td-mark { text-align: center; padding-left: 8px; padding-right: 0; }

/* Steuergerät und Abfrage in zwei Zeilen: „BMU“ und „SOH_PACK“ nebeneinander
   ergäben eine Spalte, die bei zwölf Zeichen Steuergerätename die halbe Tabelle
   einnimmt. Übereinander bleibt die Spalte schmal, und die Zuordnung ist trotzdem
   eindeutig. */
.ct-row-ecu { display: block; font-size: 11.5px; font-weight: 600; line-height: 1.25; }
.ct-row-svc {
    display: block; font-size: 10.5px; color: var(--text-dim); line-height: 1.25;
    max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Der Median ist die Zahl, wegen der man die Zeile liest – die übrigen sind ihr
   Rahmen. */
.ct-table .ct-td-med { color: var(--text); font-weight: 600; }
.ct-table .ct-td-hot { color: var(--fault); font-weight: 600; }

/* Die Zeile mit Befund wird nicht flächig eingefärbt, sondern nur an der Kante
   markiert: eine rote Fläche über acht Spalten macht ihre eigenen Zahlen
   unlesbar. */
.ct-table tbody tr.outlier > td:first-child,
.ct-table tbody tr.reversed > td:first-child { box-shadow: inset 2px 0 0 var(--fault); }
.ct-table tbody tr.outlier,
.ct-table tbody tr.reversed { background: color-mix(in srgb, var(--fault) 5%, transparent); }

/* Ein Balken mit zwei Marken statt dreier Balken: es ist eine Größe in drei
   Ausprägungen. Die Füllung reicht bis p95 („so weit reicht der Normalfall“), der
   Strich sitzt auf dem Median, die Raute auf dem Höchstwert. Bezugsgröße ist die
   langsamste Antwort der ganzen Tabelle – nur dann sind die Zeilen untereinander
   vergleichbar. */
.ct-scale {
    position: relative; display: block; height: 8px; min-width: 100px;
    background: var(--panel-inset); border-radius: 4px; cursor: help;
}
.ct-scale-fill {
    position: absolute; left: 0; top: 0; bottom: 0;
    background: color-mix(in srgb, var(--accent) 55%, transparent);
    border-radius: 4px;
}
.ct-scale-med {
    position: absolute; top: -1px; bottom: -1px; width: 2px; margin-left: -1px;
    background: var(--accent); border-radius: 1px;
}
.ct-scale-max {
    position: absolute; top: 1px; width: 6px; height: 6px; margin-left: -3px;
    background: var(--text-faint); transform: rotate(45deg);
}
.ct-scale-max.hot { background: var(--fault); }

.ct-table .ct-td-trail { padding: 2px 10px; }
.ct-td-trail .sparkline { opacity: .9; }
.ct-td-find { white-space: nowrap; }

@media (max-width: 860px) {
    /* Auf schmalen Fenstern fallen Balken und Verlauf weg – sie sind der Vergleich
       zwischen den Zeilen, und der ist bei vier sichtbaren Spalten ohnehin nicht
       zu ziehen. Die Zahlen bleiben. */
    .ct-th-bar, .ct-td-bar, .ct-th-trail, .ct-td-trail { display: none; }
}

/* =====================================================================
   Kommunikation, zweite Ebene: Abschnitt, Zoom, Einzelansicht
   ---------------------------------------------------------------------
   Der Bericht beantwortet dieselbe Frage in drei Auflösungen, und die
   Gestaltung hält sie auseinander: der ABSCHNITT sagt, welches Stück
   überhaupt gelesen wird (ganz oben, waagerechter Streifen – eine
   Landkarte, kein Diagramm), der ZOOM zeigt die gelesenen Antworten
   genauer (im Bild selbst, mit Werkzeugen in seiner Kopfzeile), die
   EINZELANSICHT eine Abfrage für sich (als Auflage, weil sie den Bericht
   nicht ersetzt, sondern in ihn hineinsieht).

   Die Farbregel der Ansicht gilt weiter: --accent für das, was geladen
   ist, --warn für das, was eingestellt aber noch nicht gelesen ist,
   --fault nur für Befunde.
   ===================================================================== */

/* ---------- Abschnitt ---------- */
.ct-range { gap: 7px; }

/* Der Streifen ist die ganze Spanne der gewählten Messungen. Zwei Marken darin,
   und ihr Unterschied ist die Aussage: satt gefüllt, was ausgewertet IST –
   umrissen, was eingestellt ist. Ohne diese Trennung sähe ein noch nicht
   geladener Entwurf aus wie eine Auskunft über Daten. */
.ct-range-strip {
    position: relative; height: 24px; overflow: hidden;
    background: var(--panel-inset); border: 1px solid var(--line);
    border-radius: var(--radius-sm);
}
.ct-range-win {
    position: absolute; top: 0; bottom: 0;
    background: color-mix(in srgb, var(--accent) 26%, transparent);
    border-left: 1px solid var(--accent); border-right: 1px solid var(--accent);
}
.ct-range-edit {
    position: absolute; top: 2px; bottom: 2px;
    border: 1px dashed var(--warn); border-radius: 2px;
    background: color-mix(in srgb, var(--warn) 7%, transparent);
}
.ct-range-x {
    display: flex; justify-content: space-between; gap: 10px;
    font-family: var(--font-mono); font-size: 9.5px; color: var(--text-faint);
}

.ct-range-ctl { display: flex; align-items: flex-end; gap: 8px; flex-wrap: wrap; }
.ct-range-ctl .field { margin: 0; gap: 3px; }
.ct-range-ctl .field > label { font-size: 10.5px; }
.ct-range-ctl .input, .ct-range-ctl .select { height: 30px; font-size: 12px; padding: 0 8px; }
.ct-f-time { width: 196px; flex: none; }
.ct-f-len { width: 132px; flex: none; }
.ct-range-pager { display: flex; gap: 3px; flex: none; }

/* ---------- Zeitverlauf: Wirt, Werkzeuge, Vollbild ---------- */
.ct-plot-host { display: flex; flex-direction: column; gap: 8px; min-width: 0; outline: none; }

/* Die Werkzeuge des Bildes stehen in seiner eigenen Kopfzeile und nicht in der des
   Panels: dort steht die Ausreißerschwelle, die für den ganzen Bericht gilt. Ein
   Zoomknopf daneben ließe offen, was er zoomt. */
.ct-plot-tools { display: flex; align-items: center; gap: 4px; margin-left: auto; flex-wrap: wrap; }
.ct-plot-tools .btn { height: 26px; }
.ct-plot-tools .btn-icon.btn-sm { width: 26px; }
.ct-plot-tools + .ct-legend, .ct-legend + .ct-plot-tools { margin-left: 10px; }
.ct-zoom-chip { height: 22px; color: var(--accent); border-color: var(--accent); }

/* Die Bedienhinweise stehen unter dem Bild und nicht darin: im Bild wären sie eine
   Schrift über den Nadeln, und beim zweiten Ansehen liest sie niemand mehr. */
.ct-plot-hint { color: var(--text-faint); }

/* Vollbild – dieselbe Auszeichnung, nur als Auflage über dem Fenster. Wie beim
   Signaldiagramm (.ts-chart-host.ts-fs) bleibt dabei dasselbe Bauteil im Baum und
   behält seinen Ausschnitt: ein zweites fürs Vollbild hätte den Zoom zurückgesetzt,
   den man gerade sehen will. Der z-Index liegt unter dem der Dialoge (100), damit
   die Einzelansicht darüber malt und nicht darunter. */
.ct-plot-host.ct-fs {
    position: fixed; inset: 0; z-index: 60; padding: 12px 14px;
    background: var(--bg); gap: 10px;
}
/* Die Zeile des Rasters muss ausdruecklich fuellen: ohne grid-template-rows ist die
   implizite Zeile inhaltsgross, und eine Hoehe von 100 Prozent darin bezoege sich auf
   sich selbst - das SVG blieb bei seinen 170 Pixeln. */
.ct-plot-host.ct-fs .ct-plot { flex: 1; min-height: 0; grid-template-rows: 1fr; }
.ct-plot-host.ct-fs .ct-plot-area { min-height: 0; }
.ct-plot-host.ct-fs .ct-plot-svg { height: 100%; }
.ct-plot-host.ct-fs .ct-sec-head h4 { font-size: 15px; }

/* Flacher für die Einzelansicht: dort trägt das Bild eine Abfrage und nicht
   zwanzigtausend Antworten – die halbe Höhe reicht, und der Dialog bleibt lesbar. */
.ct-plot-svg.sm { height: 118px; }

/* ---------- Fadenkreuz und Zeiger ---------- */
/* In einem Bild aus tausend gleich aussehenden Nadeln ist der Zeiger der einzige Weg
   von einer Nadel zu der Abfrage, die sie erzeugt hat. */
.ct-cursor {
    position: absolute; top: 0; bottom: 0; width: 1px; margin-left: -0.5px;
    background: var(--accent); opacity: .8; pointer-events: none; z-index: 3;
}
.ct-tip {
    position: absolute; top: 6px; margin-left: 8px; z-index: 6; pointer-events: none;
    display: flex; flex-direction: column; gap: 2px; max-width: 260px;
    padding: 6px 9px; border-radius: var(--radius-sm);
    background: var(--panel); border: 1px solid var(--line-strong); box-shadow: var(--shadow);
}
/* Am rechten Rand klappt das Fenster auf die andere Seite – sonst stünde es zur Hälfte
   außerhalb des Bildes, und zwar genau dort, wo die jüngsten Antworten liegen. */
.ct-tip.flip { transform: translateX(-100%); margin-left: -8px; }
.ct-tip-t { font-size: 10px; color: var(--text-dim); }
.ct-tip-w { font-size: 11.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ct-tip-v { font-size: 13px; font-weight: 600; color: var(--text); }
.ct-tip-v.slow { color: var(--warn); }
.ct-tip-v.outlier, .ct-tip-v.reversed { color: var(--fault); }

/* Auswahlrechteck beim Aufziehen – von charts.js eingehängt, damit es der Maus ohne
   Umweg über den Server folgt. Die Regel liegt hier und nicht mehr im Signaldiagramm,
   weil sie inzwischen zwei Bilder betrifft: eine Auszeichnung, die von einem Bauteil
   ins DOM gesetzt wird, kann nicht in dessen eigenem <style> stehen, wenn ein zweites
   Bauteil sie ebenfalls braucht. */
.chart-brush {
    position: absolute; top: 0; bottom: 0; display: none; pointer-events: none;
    background: var(--accent); opacity: 0.16;
    border-left: 1px solid var(--accent); border-right: 1px solid var(--accent);
    z-index: 4;
}

/* ---------- Weg in die Einzelansicht ---------- */
/* Die Aktionsspalte steht am Ende der Zeile und trägt keine Überschrift: „🔍“ über
   einer Spalte mit Lupen wäre die Beschriftung eines Knopfes mit sich selbst. Was er
   tut, sagt der Hinweis in der Abschnittsüberschrift. */
.ct-th-act { width: 34px; padding-left: 0; padding-right: 8px; }
.ct-table .ct-td-act { text-align: right; padding-left: 0; padding-right: 8px; }
.ct-table .ct-td-act .btn { width: 26px; height: 26px; }

/* Eine Auffälligkeit ohne Weg in ihre Abfrage ist eine Behauptung, die man nicht
   nachsehen kann. Der Zeiger sagt, dass es einen gibt. */
.ct-clickable { cursor: pointer; }
.ct-clickable:hover { background: color-mix(in srgb, var(--fault) 13%, transparent); }
.ct-clickable:focus-visible { outline: 1px solid var(--accent); outline-offset: 1px; }

/* ---------- Einzelansicht ---------- */
/* Breiter als die übrigen Dialoge: der Inhalt ist eine Tabelle mit sieben Spalten und
   ein Diagramm. In 760 Pixeln (.modal.lg) bräche beides um. */
.modal.xl { max-width: 1180px; }

/* Ein Dialog, der in drei Spalten arbeitet, ist von Natur aus hoeher als einer mit
   einer Spalte Text. Die 88vh der schmalen Dialoge kosteten den Regeleditor auf einem
   1080p-Bildschirm genau die letzten anderthalb Zeilen - und damit eine Bildlaufleiste
   fuer nichts. Vier Prozent mehr, und die Polsterung des Koerpers von 20 auf 14: Beides
   zusammen sind gut siebzig Pixel, die der Inhalt bekommt. */
.modal.xl { max-height: 92vh; }
.modal.xl > .modal-body { padding: 14px 16px; }

/* Auch Kopf und Fuß geben je acht Pixel her. Sie tragen eine Zeile Text bzw. zwei
   Knöpfe – die Polsterung von 16 Pixeln stammt von den schmalen Dialogen, in denen der
   Kopf die halbe Aussage ist. */
.modal.xl > .modal-head,
.modal.xl > .modal-foot { padding: 12px 16px; }

.ct-detail { display: flex; flex-direction: column; gap: 16px; }
/* Die Liste rollt für sich: der Dialog soll sich nicht auf die Länge einer Messung
   ausdehnen, und Kennzahlen und Diagramm sollen beim Blättern stehen bleiben. */
.ct-detail-tw { max-height: 44vh; overflow: auto; }
.ct-detail .ct-kpis { grid-template-columns: repeat(auto-fit, minmax(136px, 1fr)); }

/* Der Balken der Einzelzeile füllt bis zur Antwortzeit und trägt eine Marke auf dem
   Median – dieselbe Lesart wie in der Übersicht, nur ist die Bezugsgröße hier die
   langsamste Antwort DIESER Abfrage. */
.ct-scale-fill.hot { background: color-mix(in srgb, var(--fault) 60%, transparent); }

@media (max-width: 700px) {
    /* Auf schmalen Fenstern fallen die Datumsfelder auf ganze Breite – zwei
       Zeitpunkte nebeneinander sind dort nicht zu tippen. */
    .ct-f-time, .ct-f-len { width: 100%; }
}

/* =====================================================================
   Fahrtenbuch
   ---------------------------------------------------------------------
   Diese Seite ist ein Steuerbeleg und keine Auswertung, und die
   Gestaltung sagt das: eine Tabelle in Buchform, keine Diagramme, keine
   Farbflächen. Farbe trägt nur, was den Beleg gefährdet – eine fehlende
   Angabe (--warn) und eine Lücke in der Kilometerkette (--fault). Alles
   Vollständige bleibt einfarbig; in einem Buch mit dreihundert Zeilen
   wäre dreihundertmal Grün der lauteste Inhalt der Seite, und die eine
   Zeile mit dem Mangel ginge darin unter.

   Die zweite Regel betrifft die Herkunft: Gemessenes und Eingetragenes
   sehen unterschiedlich aus (.fb-src, .fb-tag.hand). Eine gemessene
   Zeitangabe ist etwas anderes als eine erinnerte, und ein Prüfer muss
   das sehen können.
   ===================================================================== */

/* Wie .ts-page: der Kopf steht fest, gescrollt wird im Panel. Ein
   Fahrtenbuch mit dreihundert Zeilen soll den Zeitraumwähler nicht aus dem
   Bild schieben – er ist die Stelle, an die man ständig zurückgeht. */
.fb-page {
    display: flex; flex-direction: column; gap: 12px;
    height: 100%; min-height: 0;
}
.fb-bar { flex: none; }
.fb-sep { width: 1px; height: 18px; background: var(--line-strong); margin: 0 3px; flex: none; }

.fb-f-dongle { min-width: 230px; flex: 1 1 230px; max-width: 440px; }

/* Die Prüfung schliesst die Leiste rechts ab. Sie schiebt sich mit margin-left:auto
   ans Ende, damit zwischen ihr und den Einstellungen links Luft bleibt: Was links
   steht, stellt das Buch ein, dies hier sieht es durch. Bricht die Leiste um, faellt
   der Strich weg - in einer neuen Zeile trennte er nichts mehr. */
.fb-f-check { margin-left: auto; align-self: flex-end; flex: none; }
.fb-f-check-sep { align-self: flex-end; margin-bottom: 8px; margin-left: 4px; }
@media (max-width: 1100px) {
    .fb-f-check { margin-left: 0; }
    .fb-f-check-sep { display: none; }
}
.fb-f-period { width: 268px; flex: none; }
.fb-f-driver { width: 190px; flex: none; }
.fb-f-kind { margin: 0; width: 210px; flex: none; }
.fb-f-opt { margin: 0; width: 124px; flex: none; }

.fb-period { display: flex; align-items: center; gap: 4px; }
.fb-period .input { text-align: center; }

/* Die Zahl am Reiter: wie viele Vorschläge warten bzw. wie viele Befunde offen
   sind. Sie steht dort, weil man auf dem Fahrtenbuch-Reiter arbeitet und den
   Prüfreiter sonst nicht aufmacht. */
.fb-badge {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 16px; height: 16px; margin-left: 6px; padding: 0 4px;
    border-radius: 8px; background: var(--accent-soft); color: var(--accent);
    font-family: var(--font-mono); font-size: 9.5px;
}
.fb-badge.alarm { background: color-mix(in srgb, var(--fault) 14%, transparent); color: var(--fault); }

/* ---------- Kennzahlen ---------- */
/* Die Aufteilung geschäftlich / privat / Wohnung–Betrieb ist der Zweck des ganzen
   Buches: aus ihr ergibt sich der private Nutzungsanteil. Sie steht deshalb oben
   und nicht am Ende. */
.fb-kpis {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(146px, 1fr)); gap: 8px;
    flex: none;
}
.fb-kpi {
    display: flex; flex-direction: column; gap: 1px; min-width: 0;
    padding: 9px 11px;
    background: var(--panel-inset); border: 1px solid var(--line);
    border-left-width: 3px; border-left-color: var(--line-strong);
    border-radius: var(--radius-sm);
}
.fb-kpi.accent { border-left-color: var(--accent); }
.fb-kpi.ok     { border-left-color: var(--ok); }
.fb-kpi.alarm  { border-left-color: var(--fault); }
.fb-kpi-k {
    font-family: var(--font-mono); font-size: 9.5px; letter-spacing: 0.1em;
    text-transform: uppercase; color: var(--text-dim); white-space: nowrap;
}
.fb-kpi-v {
    font-family: var(--font-ui); font-size: 18px; font-weight: 700; line-height: 1.15;
    color: var(--text); font-variant-numeric: tabular-nums;
}
.fb-kpi-n {
    font-size: 10.5px; line-height: 1.3; color: var(--text-faint);
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

/* ---------- Buchtabelle ---------- */
.fb-p { flex: 1; min-height: 0; }
.fb-tw { flex: 1; min-height: 0; overflow: auto; }
.fb-table { font-size: 12px; }
.fb-table th, .fb-table td { padding: 6px 10px; vertical-align: top; }
.fb-th-no { width: 34px; }
.fb-th-check { width: 34px; text-align: center; }
.fb-th-act { width: 96px; }
.fb-table .fb-td-no { color: var(--text-faint); }
.fb-table .fb-td-act { text-align: right; white-space: nowrap; padding-left: 0; }
.fb-table .fb-td-act .btn { width: 26px; height: 26px; }
.fb-table .fb-td-km { font-weight: 600; }
.fb-table .fb-td-state { white-space: nowrap; }
.fb-table .fb-td-geo { font-size: 10.5px; color: var(--text-dim); white-space: nowrap; }

/* Datum und Uhrzeit übereinander: nebeneinander ergäben sie eine Spalte, die bei
   „Mittwoch, 12.03.2026 08:14 – 09:02“ ein Drittel der Tabelle einnimmt. */
.fb-date { display: block; font-size: 11.5px; font-weight: 600; line-height: 1.25; }
.fb-time { display: block; font-size: 10.5px; color: var(--text-dim); line-height: 1.25; }

/* Textspalten dürfen nicht die Tabelle sprengen; der ganze Inhalt steht im Titel. */
.fb-td-text {
    max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Eine fehlende Angabe ist nicht „leer“, sondern ein Mangel am Beleg – und sie ist
   der einzige Grund, in dieser Tabelle Farbe zu verwenden. */
.fb-miss { color: var(--warn); font-weight: 600; }

.fb-table tbody tr.fb-incomplete > td:first-child { box-shadow: inset 2px 0 0 var(--warn); }
.fb-table tbody tr.fb-incomplete { background: color-mix(in srgb, var(--warn) 5%, transparent); }
/* Storniertes bleibt stehen und wird durchgestrichen: eine gelöschte Zeile wäre eine
   Lücke, und eine Lücke ist genau das, was ein Fahrtenbuch nicht haben darf. */
.fb-table tbody tr.fb-voided { opacity: .55; text-decoration: line-through; }
.fb-table tbody tr.fb-voided .fb-tag { text-decoration: none; }
.fb-table tbody tr.sel { background: var(--accent-soft); }
.fb-table tbody tr[class*="fb-"]:hover, .fb-table tbody tr:hover { background: var(--panel-inset); }

/* Die Lücke steht als eigene Zeile VOR der Fahrt, an die sie anschließt: dort ist sie
   entstanden, und dort sucht man die fehlende Fahrt. */
.fb-gap-row td {
    background: color-mix(in srgb, var(--fault) 8%, transparent);
    border-top: 1px solid color-mix(in srgb, var(--fault) 40%, transparent);
    border-bottom: 1px solid color-mix(in srgb, var(--fault) 40%, transparent);
    font-size: 11.5px; color: var(--text-dim);
}
.fb-gap-row .btn { margin-left: 8px; }

.fb-kind {
    display: inline-flex; align-items: center; padding: 1px 7px; border-radius: 999px;
    font-size: 10.5px; white-space: nowrap;
    background: var(--panel-inset); border: 1px solid var(--line); color: var(--text-dim);
}
/* Geschäftlich ist der Regelfall und trägt deshalb die Akzentfarbe – nach ihm wird
   das Buch gelesen. Privat bleibt neutral: es ist die Kilometerzahl, die zählt. */
.fb-kind.k-business { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, transparent); }
.fb-kind.k-commute { color: var(--text); }

.fb-tag {
    display: inline-flex; align-items: center; gap: 3px;
    padding: 1px 6px; margin-right: 3px; border-radius: 999px;
    font-family: var(--font-mono); font-size: 10px; white-space: nowrap;
}
.fb-tag.ok   { color: var(--ok); }
.fb-tag.lock { color: var(--text-dim); background: var(--panel-inset); }
.fb-tag.miss { color: var(--warn); background: color-mix(in srgb, var(--warn) 12%, transparent); }
.fb-tag.void { color: var(--fault); background: color-mix(in srgb, var(--fault) 12%, transparent); }
.fb-tag.corr { color: var(--fault); background: color-mix(in srgb, var(--fault) 10%, transparent); }
.fb-tag.rev  { color: var(--text-faint); }
.fb-tag.hand { color: var(--text-faint); background: var(--panel-inset); }
.fb-tag.act  { color: var(--accent); background: var(--accent-soft); }

.fb-note { margin: 10px 0 0; max-width: 96ch; font-size: 11px; line-height: 1.55; color: var(--text-faint); }
.fb-note code { font-size: 10.5px; }

/* ---------- Meldungen ---------- */
.fb-warn, .fb-clean {
    display: flex; align-items: flex-start; gap: 9px;
    padding: 8px 11px; margin: 10px 0; border-radius: var(--radius-sm);
    font-size: 11.5px; line-height: 1.5; color: var(--text-dim);
}
.fb-warn  { background: color-mix(in srgb, var(--warn) 9%, transparent);
            border: 1px solid color-mix(in srgb, var(--warn) 30%, transparent); }
.fb-clean { background: var(--panel-inset); border: 1px solid var(--line); }
.fb-mark { flex: none; font-size: 12px; line-height: 1.4; }
.fb-mark.ok    { color: var(--ok); }
.fb-mark.warn  { color: var(--warn); }
.fb-mark.alarm { color: var(--fault); }

/* ---------- Erkennung ---------- */
/* Die Schwellen stehen sichtbar und verstellbar da: wo eine Fahrt anfängt und
   aufhört, ist eine Entscheidung und keine Messung. */
.fb-detect-opts {
    display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap;
    padding: 9px 11px; margin-bottom: 12px;
    background: var(--panel-inset); border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.fb-detect-opts .field > label { font-size: 10.5px; }
.fb-detect-opts .select { height: 30px; font-size: 12px; padding: 0 8px; }
.fb-detect-info { display: flex; align-items: center; gap: 6px; margin-left: auto; flex-wrap: wrap; }

/* Der Ortsvorschlag ist der einzige, der aus Messwerten stammt und nicht aus einer
   Tippgewohnheit – „hier waren Sie schon“, nicht „das schreiben Sie oft“. Deshalb
   trägt er die Akzentfarbe. */
.fb-hint-place { color: var(--accent); font-size: 11.5px; }

/* ---------- Prüfung ---------- */
.fb-check { display: flex; flex-direction: column; gap: 20px; overflow: auto; min-height: 0; flex: 1; }
.fb-sec { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.fb-sec-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.fb-sec-head h4 { margin: 0; font-family: var(--font-ui); font-size: 13px; font-weight: 600; }
.fb-sec-head .fb-hint { font-size: 11px; color: var(--text-dim); }

.fb-finds { display: flex; flex-direction: column; gap: 3px; max-height: 300px; overflow: auto; }
.fb-find {
    display: grid; grid-template-columns: auto 1fr auto auto; align-items: center; gap: 12px;
    padding: 5px 9px; border-radius: 4px;
    border-left: 2px solid var(--warn);
    background: color-mix(in srgb, var(--warn) 7%, transparent);
    font-size: 11.5px;
}
.fb-find.alarm { border-left-color: var(--fault); background: color-mix(in srgb, var(--fault) 6%, transparent); }
.fb-find-time { color: var(--text-dim); font-size: 10.5px; white-space: nowrap; }
.fb-find-what { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fb-find-km { font-weight: 600; white-space: nowrap; }

.fb-rules { display: flex; flex-direction: column; gap: 8px; }
.fb-rule {
    display: flex; align-items: flex-start; gap: 10px;
    padding: 9px 11px; border-radius: var(--radius-sm);
    background: var(--panel-inset); border: 1px solid var(--line);
}
.fb-rule.open { border-color: color-mix(in srgb, var(--warn) 30%, transparent); }
.fb-rule > div { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.fb-rule strong { font-size: 12.5px; }
.fb-rule span { font-size: 11.5px; line-height: 1.5; color: var(--text-dim); }

/* ---------- Formular ---------- */
.fb-modal .modal-body { display: flex; flex-direction: column; gap: 18px; }
.fb-form-sec { display: flex; flex-direction: column; gap: 8px; }
.fb-form-sec > h5 {
    margin: 0; display: flex; align-items: baseline; gap: 8px;
    font-family: var(--font-ui); font-size: 12.5px; font-weight: 600;
    padding-bottom: 5px; border-bottom: 1px solid var(--line);
}
/* Woher die Angaben kommen, steht an der Überschrift: „aus den Zeitreihen“ neben
   Zeit und Kilometerstand, „nur der Fahrer weiß es“ neben Ziel und Zweck. Das ist
   die zentrale Aufteilung dieser Seite, und sie gehört an die Felder, nicht in eine
   Erklärung darunter. */
.fb-form-sec > h5 .fb-src {
    font-family: var(--font-mono); font-size: 9.5px; letter-spacing: 0.06em;
    text-transform: uppercase; color: var(--text-faint); font-weight: 400;
}
.fb-inline { display: flex; align-items: center; gap: 6px; }
.fb-inline .input { min-width: 0; }
.fb-inline .btn { flex: none; font-family: var(--font-mono); }
.fb-kind-seg button { height: 28px; padding: 0 14px; font-size: 12px; }
/* Der Stern markiert, was die Vorschrift verlangt – und nur, wo sie es verlangt:
   an einer Privatfahrt steht er nicht. */
.fb-req { color: var(--fault); }

.fb-chips { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.fb-chips-k { font-size: 11px; color: var(--text-dim); }
button.fb-chip-place { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, transparent); }

.fb-hist { display: flex; flex-direction: column; gap: 8px; }
.fb-hist-row {
    padding: 8px 11px; border-radius: var(--radius-sm);
    background: var(--panel-inset); border: 1px solid var(--line);
}
.fb-hist-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 11.5px; }
.fb-hist-time { font-size: 10.5px; color: var(--text-dim); }
.fb-hist-sum { margin-top: 4px; font-size: 11.5px; line-height: 1.5; word-break: break-word; }
.fb-hist-reason {
    margin-top: 4px; padding: 5px 8px; border-radius: 4px;
    background: color-mix(in srgb, var(--warn) 8%, transparent);
    font-size: 11.5px; line-height: 1.5;
}

/* =====================================================================
   Der steuerliche Ausdruck
   ---------------------------------------------------------------------
   Auf dem Bildschirm eine Auflage, auf Papier das ganze Blatt. Bewusst
   hell und ohne die Farben der Anwendung: ein Beleg wird gedruckt und
   abgelegt, und ein dunkles Panel wäre auf Papier eine schwarze Fläche.
   ===================================================================== */
.fb-print-modal { max-width: 1240px; width: 100%; max-height: 92vh; display: flex; flex-direction: column; }
.fb-print-tools { flex: none; }
.fb-print {
    overflow: auto; background: #FFFFFF; color: #101418;
    padding: 26px 30px; font-size: 11px; line-height: 1.45;
}
.fb-print h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -0.01em; }
.fb-print h3 { margin: 0 0 8px; font-size: 13px; }
.fb-print p { margin: 0 0 8px; max-width: 110ch; }
.fb-print-dim { color: #5A6472; }

.fb-print-meta { border-collapse: collapse; margin-bottom: 18px; }
.fb-print-meta th, .fb-print-meta td { padding: 2px 12px 2px 0; text-align: left; vertical-align: top; }
.fb-print-meta th { font-weight: 600; color: #5A6472; white-space: nowrap; font-size: 10.5px; }

.fb-print-table { width: 100%; border-collapse: collapse; margin-bottom: 6px; }
.fb-print-table th, .fb-print-table td {
    border: 1px solid #C3CBD6; padding: 3px 5px; text-align: left; vertical-align: top;
    font-size: 9.5px; line-height: 1.35;
}
.fb-print-table th { background: #EEF1F5; font-weight: 600; }
.fb-print-table td.num { text-align: right; font-variant-numeric: tabular-nums; }
.fb-print-table .fb-print-src { font-size: 8.5px; color: #5A6472; white-space: nowrap; }
.fb-print-table .fb-print-sum { max-width: 34ch; }
/* Storniertes wird durchgestrichen und bleibt lesbar – der Grund steht im eigenen
   Abschnitt darunter. */
.fb-print-table tr.voided td { text-decoration: line-through; color: #6B7382; }

.fb-print-sec { margin-top: 20px; break-inside: avoid; }
.fb-print-foot { margin-top: 22px; padding-top: 10px; border-top: 1px solid #C3CBD6; font-size: 9.5px; }

@media print {
    /* Gedruckt wird der Beleg und nicht die Anwendung: Navigation, Kopfzeile und
       die Seite hinter der Auflage verschwinden, der Ausdruck füllt das Blatt.
       visibility statt display, weil die Auflage ein Kind des ausgeblendeten
       Baums ist – mit display:none wäre sie mit weg. */
    body * { visibility: hidden !important; }
    .fb-print-overlay, .fb-print-overlay * { visibility: visible !important; }

    .fb-print-overlay {
        position: absolute !important; inset: 0 !important;
        background: #FFFFFF !important; backdrop-filter: none !important;
        padding: 0 !important; display: block !important;
    }
    .fb-print-modal {
        max-width: none !important; max-height: none !important; width: 100% !important;
        border: none !important; border-radius: 0 !important; box-shadow: none !important;
        background: #FFFFFF !important; overflow: visible !important;
    }
    /* Die Werkzeugleiste der Auflage gehört auf den Bildschirm und nicht aufs
       Papier: ein gedruckter Knopf „Drucken“ ist der Beweis, dass niemand den
       Ausdruck angesehen hat. */
    .fb-print-tools { display: none !important; }
    .fb-print { overflow: visible !important; padding: 0 !important; }

    /* Die Kopfzeile der Tabelle wiederholt sich auf jeder Seite – ein Fahrtenbuch
       über drei Blätter ist ohne sie ab Blatt zwei nicht mehr zu lesen. */
    .fb-print-table thead { display: table-header-group; }
    .fb-print-table tr { break-inside: avoid; }

    @page { size: A4 landscape; margin: 12mm 10mm; }
}

@media (max-width: 860px) {
    /* Auf schmalen Fenstern fallen die Spalten weg, die den Vergleich zwischen den
       Zeilen tragen. Die geforderten Angaben bleiben: Datum, Kilometerstände,
       Ziel, Zweck, Partner. */
    .fb-table .fb-td-geo, .fb-table .fb-td-state { display: none; }
    .fb-f-period, .fb-f-driver { width: 100%; }
}

/* =====================================================================
   Mobilfunk: der gemeinsame Datenpool
   ---------------------------------------------------------------------
   Die entscheidende Größe ist der POOL und nicht die einzelne Karte:
   jede aktivierte SIM bringt ihr Monatskontingent ein, verbraucht wird
   daraus gemeinsam. Deshalb ist der Poolbalken das größte Element der
   Anzeige und die Gerätespalte nur eine Zeile darin.

   Die Farben sind streng gestaffelt und tragen genau eine Aussage – wie
   voll es ist: --accent bis drei Viertel, --warn ab drei Vierteln,
   --fault ab neun Zehnteln und darüber. Kein Grün für „reichlich frei“:
   der Normalfall ist frei, und eine grüne Fläche daneben machte aus einer
   Auskunft eine Wertung.
   ===================================================================== */

.mob-p { margin-bottom: 14px; }

/* ---------- Der Poolbalken ---------- */
/* Er IST das Kontingent: die volle Breite ist das, was der Vertrag hergibt, die
   Füllung das, was davon weg ist. Zweifarbig getrennt, was der eigene Bereich
   beiträgt und was die übrigen – ohne diese Trennung wäre bei einem vollen Pool
   nicht zu sagen, ob es an den eigenen Geräten liegt. */
.mob-pool { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }

.mob-pool-bar {
    position: relative; height: 30px; overflow: hidden;
    background: var(--panel-inset);
    border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
}
.mob-pool-dept, .mob-pool-other {
    position: absolute; top: 0; bottom: 0;
    transition: width .25s ease-out;
}
.mob-pool-dept { left: 0; background: var(--accent); opacity: .85; }
.mob-pool-other { background: color-mix(in srgb, var(--accent) 34%, transparent); }

/* Ab drei Vierteln wechselt die Füllung die Farbe, nicht der Rahmen: gefragt ist
   „wie voll“, und das ist eine Eigenschaft der Füllung. */
.mob-pool.elevated .mob-pool-dept { background: var(--warn); }
.mob-pool.elevated .mob-pool-other { background: color-mix(in srgb, var(--warn) 34%, transparent); }
.mob-pool.high .mob-pool-dept,
.mob-pool.over .mob-pool-dept { background: var(--fault); }
.mob-pool.high .mob-pool-other,
.mob-pool.over .mob-pool-other { background: color-mix(in srgb, var(--fault) 34%, transparent); }

/* Viertelmarken statt einer Beschriftung je Schritt: gefragt ist „wie weit ist es“
   und nicht „wie viele Megabyte sind 25 %“. */
.mob-pool-tick {
    position: absolute; top: 0; bottom: 0; width: 1px;
    background: var(--bg); opacity: .5;
}

/* Über dem Kontingent legt sich ein gestreiftes Stück über das rechte Ende des
   Balkens; seine Länge ist die Überschreitung. Ohne diese Marke sähe ein Pool bei
   130 % genauso aus wie einer bei genau 100 % – der Balken ist bei beiden voll, und
   das ist der Unterschied, um den es geht. */
.mob-pool-over {
    position: absolute; top: 0; bottom: 0; right: 0;
    background: repeating-linear-gradient(45deg,
        var(--fault) 0 4px, color-mix(in srgb, var(--fault) 45%, transparent) 4px 8px);
    box-shadow: inset 1px 0 0 var(--bg);
}

.mob-pool-scale {
    display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
    font-family: var(--font-mono); font-size: 10px; color: var(--text-faint);
}
.mob-pool-used { color: var(--text); font-size: 11.5px; font-weight: 600; }

.mob-legend {
    display: flex; align-items: center; gap: 5px; flex-wrap: wrap;
    font-size: 10.5px; color: var(--text-dim);
}
.mob-lg { width: 10px; height: 10px; border-radius: 2px; flex: none; margin-left: 8px; }
.mob-lg:first-child { margin-left: 0; }
.mob-lg.dept { background: var(--accent); opacity: .85; }
.mob-lg.other { background: color-mix(in srgb, var(--accent) 34%, transparent); }
.mob-lg.free { background: var(--panel-inset); border: 1px solid var(--line-strong); }

/* ---------- Kennzahlen ---------- */
.mob-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(148px, 1fr)); gap: 8px; }
.mob-kpi {
    display: flex; flex-direction: column; gap: 1px; min-width: 0;
    padding: 9px 11px;
    background: var(--panel-inset); border: 1px solid var(--line);
    border-left-width: 3px; border-left-color: var(--line-strong);
    border-radius: var(--radius-sm);
}
.mob-kpi.ok       { border-left-color: var(--ok); }
.mob-kpi.elevated { border-left-color: var(--warn); }
.mob-kpi.high,
.mob-kpi.over     { border-left-color: var(--fault); }
.mob-kpi-k {
    font-family: var(--font-mono); font-size: 9.5px; letter-spacing: 0.1em;
    text-transform: uppercase; color: var(--text-dim); white-space: nowrap;
}
.mob-kpi-v {
    font-family: var(--font-ui); font-size: 18px; font-weight: 700; line-height: 1.15;
    color: var(--text); font-variant-numeric: tabular-nums;
}
.mob-kpi-n {
    font-size: 10.5px; line-height: 1.3; color: var(--text-faint);
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

/* ---------- Wer zieht / wie verteilt ---------- */
/* Sie beantworten die Gegenfrage zueinander: „wer zieht“ nennt die Geräte,
   „wie verteilt“ sagt, ob es überhaupt einzelne sind. Untereinander wäre
   zwischen ihnen eine Rolle. */
.mob-split {
    display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 4fr); gap: 20px;
    margin-top: 16px;
}
@media (max-width: 1000px) { .mob-split { grid-template-columns: 1fr; } }

.mob-sec { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.mob-sec-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.mob-sec-head h4 { margin: 0; font-family: var(--font-ui); font-size: 13px; font-weight: 600; }
.mob-sec-head .mob-hint { font-size: 11px; color: var(--text-dim); margin-left: auto; }

.mob-note { margin: 0; max-width: 92ch; font-size: 11px; line-height: 1.55; color: var(--text-faint); }

.mob-bars { display: flex; flex-direction: column; gap: 3px; }
/* Die Zeile ist ein Knopf: sie führt in den Einzeldialog. Als div mit Zeiger wäre
   sie mit der Tastatur nicht erreichbar. */
.mob-bar-row {
    display: grid; grid-template-columns: minmax(0, 150px) 1fr auto;
    align-items: center; gap: 10px;
    padding: 4px 8px; border: none; border-radius: 4px;
    background: transparent; cursor: pointer; text-align: left; font: inherit;
}
.mob-bar-row:hover { background: var(--panel-inset); }
.mob-bar-row:focus-visible { outline: 1px solid var(--accent); outline-offset: 1px; }
.mob-bar-name {
    font-size: 11.5px; color: var(--text);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mob-bar-track {
    position: relative; height: 10px; border-radius: 5px;
    background: var(--panel-inset); overflow: hidden;
}
.mob-bar-fill {
    position: absolute; left: 0; top: 0; bottom: 0; border-radius: 5px;
    background: color-mix(in srgb, var(--accent) 70%, transparent);
}
.mob-bar-fill.elevated { background: color-mix(in srgb, var(--warn) 70%, transparent); }
.mob-bar-fill.high, .mob-bar-fill.over { background: color-mix(in srgb, var(--fault) 72%, transparent); }
/* Der Strich ist das Kontingent, das die Karte in den Pool einbringt. Er steht IM
   Balken, damit ein Gerät darüber hinausragen kann, statt am Rand anzustoßen. */
.mob-bar-quota {
    position: absolute; top: -1px; bottom: -1px; width: 2px; margin-left: -1px;
    background: var(--text-dim); opacity: .8;
}
.mob-bar-val { font-size: 11px; font-weight: 600; white-space: nowrap; min-width: 68px; text-align: right; }
.mob-bar-val.elevated { color: var(--warn); }
.mob-bar-val.high, .mob-bar-val.over { color: var(--fault); }

/* Die Verteilung: fünf Stufen, gestaffelt am Kontingent je Karte. min-height an den
   Balken, damit „kommt einmal vor“ nicht wie „kommt nicht vor“ aussieht. */
.mob-hist {
    display: grid; grid-template-columns: repeat(5, 1fr); gap: 6px; align-items: end;
    height: 132px; padding: 6px 8px;
    background: var(--panel-inset); border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.mob-hist-col {
    display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
    gap: 3px; height: 100%; min-width: 0; cursor: help;
}
.mob-hist-n { font-family: var(--font-mono); font-size: 10px; color: var(--text-dim); }
.mob-hist-bar {
    width: 100%; min-height: 0; border-radius: 2px 2px 0 0;
    background: color-mix(in srgb, var(--accent) 65%, transparent);
}
.mob-hist-bar.elevated { background: color-mix(in srgb, var(--warn) 60%, transparent); }
.mob-hist-bar.high     { background: color-mix(in srgb, var(--fault) 55%, transparent); }
.mob-hist-bar.over {
    background: repeating-linear-gradient(45deg, var(--fault) 0 3px, transparent 3px 6px);
    border: 1px solid color-mix(in srgb, var(--fault) 50%, transparent); border-bottom: none;
}
.mob-hist-x {
    font-family: var(--font-mono); font-size: 8.5px; color: var(--text-faint);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}

.mob-warn, .mob-clean {
    display: flex; align-items: flex-start; gap: 9px;
    padding: 8px 11px; margin-bottom: 12px; border-radius: var(--radius-sm);
    font-size: 11.5px; line-height: 1.5; color: var(--text-dim);
}
.mob-warn  { background: color-mix(in srgb, var(--warn) 9%, transparent);
             border: 1px solid color-mix(in srgb, var(--warn) 30%, transparent); }
.mob-clean { background: var(--panel-inset); border: 1px solid var(--line); }
.mob-mark { flex: none; font-size: 12px; line-height: 1.4; }
.mob-mark.ok   { color: var(--ok); }
.mob-mark.warn { color: var(--warn); }

/* ---------- Die Spalte in der Geräteliste ---------- */
/* Zahl UND Balken: die Zahl beantwortet die Frage nach der Menge, der Balken den
   Vergleich zwischen den Zeilen. Als Zahl allein müsste man zweihundert Werte im
   Kopf sortieren, als Balken allein bliebe offen, ob es um MB oder GB geht. */
/* Die Spalte trägt inzwischen, wofür man vorher den Dialog öffnen musste: Anteil am
   eigenen Kontingent, Vielfaches des Durchschnitts, Anteil am Pool und den Stand der
   Zahl. Eine Zahl ohne diese Einordnung sagt nichts – „1,4 GB“ ist weder viel noch
   wenig, bevor man weiß, wogegen. Der Dialog bleibt für die Rohangabe der
   Schnittstelle und die Herkunft der Zuordnung. */
.mob-th { width: 176px; }
.mob-td { width: 176px; }
.mob-cell { width: 100%; }
.mob-cell-row { display: flex; align-items: center; gap: 2px; }
.mob-cell-main {
    flex: 1; min-width: 0;
    display: flex; flex-direction: column; gap: 2px;
    padding: 2px 4px; border: none; border-radius: 4px;
    background: transparent; cursor: pointer; text-align: left; font: inherit;
}
.mob-cell-main:hover { background: var(--panel-inset); }
.mob-cell-main:focus-visible { outline: 1px solid var(--accent); outline-offset: 1px; }
.mob-cell-head { display: flex; align-items: baseline; justify-content: space-between; gap: 6px; }
.mob-cell-sub { font-size: 10px; color: var(--text-faint); white-space: nowrap; }
.mob-cell-foot { font-size: 9.5px; color: var(--text-faint); white-space: nowrap; }

/* Der Knopf je Zeile ist gedämpft, aber sichtbar: er beantwortet die Frage „stimmt das
   hier noch“, und eine Antwort, die man erst durch Darüberfahren findet, beantwortet
   sie nicht. Auf der Zeile, an der man gerade ist, tritt er hervor. */
.mob-cell-refresh {
    flex: none; width: 22px; height: 22px; padding: 0;
    font-size: 12px; opacity: .4; transition: opacity .12s;
}
tr:hover .mob-cell-refresh { opacity: .85; }
.mob-cell-refresh:hover, .mob-cell-refresh:focus-visible { opacity: 1; }

/* Der Knopf in einer Zelle ohne Zahl: die Seite holt den Mobilfunkbestand beim
   Oeffnen nicht mehr, und wo nichts steht, soll wenigstens der Weg dorthin
   stehen. Zurueckhaltender als eine Zahl - er ist ein Angebot und keine
   Auskunft - und ohne den Hover-Trick des Knopfes daneben: dieser hier IST der
   Inhalt der Zelle, und ein unsichtbarer Inhalt waere eine leere Zelle. */
.mob-cell-load {
    gap: 4px;
    padding: 2px 7px;
    font-size: 10.5px;
    color: var(--text-dim);
    white-space: nowrap;
}
.mob-cell-load:hover { color: var(--accent); }
.mob-cell-val { font-size: 11.5px; font-weight: 600; color: var(--text); }
.mob-cell.elevated .mob-cell-val { color: var(--warn); }
.mob-cell.high .mob-cell-val, .mob-cell.over .mob-cell-val { color: var(--fault); }
.mob-cell-track {
    position: relative; height: 5px; border-radius: 3px;
    background: var(--panel-inset); overflow: hidden;
}
.mob-cell-fill {
    position: absolute; left: 0; top: 0; bottom: 0; border-radius: 3px;
    background: color-mix(in srgb, var(--accent) 70%, transparent);
}
.mob-cell-fill.elevated { background: color-mix(in srgb, var(--warn) 70%, transparent); }
.mob-cell-fill.high, .mob-cell-fill.over { background: color-mix(in srgb, var(--fault) 72%, transparent); }
.mob-cell-quota {
    position: absolute; top: -1px; bottom: -1px; width: 1.5px; margin-left: -0.75px;
    background: var(--text-dim); opacity: .75;
}

/* ---------- Einzeldialog ---------- */
.mob-detail { display: flex; flex-direction: column; }
.mob-detail-table { margin-top: 8px; font-size: 12px; }
.mob-detail-table th {
    width: 220px; text-align: left; color: var(--text-dim); font-weight: 500;
    white-space: nowrap;
}
.mob-detail-table th, .mob-detail-table td { padding: 5px 10px; }

@media (max-width: 900px) {
    /* Auf schmalen Fenstern fällt die Mobilfunkspalte weg – die Zahlen stehen im
       Poolpanel darüber und im Einzeldialog. Was bleibt, ist die Geräteverwaltung. */
    .mob-th, .mob-td { display: none; }
}


/* =====================================================================
   My R.Devices: kompakter Datenpool und dichte Geräteliste
   ---------------------------------------------------------------------
   Der Datenpool beantwortet eine Frage über den Vertrag – trägt er noch.
   Das ist nicht die Frage, mit der jemand diese Seite aufruft: gesucht
   wird ein Gerät. Ein Block, der die halbe Seite einnimmt, bevor die
   erste Tabellenzeile beginnt, drängt eine Antwort auf, nach der niemand
   gefragt hat. Er steht deshalb in einer Zeile zusammengefasst; die
   Aufschlüsselung ist einen Klick entfernt.
   ===================================================================== */

.mob-compact > .panel-body {
    padding: 10px 14px 12px;
    display: flex; flex-direction: column; gap: 9px;
}
.mob-compact > .panel-head { padding: 9px 14px; }
.mob-compact .mob-pool { gap: 0; margin-bottom: 0; }
/* Schmaler Balken: er bleibt die größte Fläche des Blocks und damit dessen Aussage,
   nimmt aber nicht mehr Platz als eine Textzeile. */
.mob-compact .mob-pool-bar { height: 14px; }
.mob-compact .mob-warn, .mob-compact .mob-clean { margin: 0; }

/* Die Kennzahlen als Zeile statt als Kachelfeld: acht Kacheln sind acht Überschriften,
   von denen im Vorbeigehen keine gelesen wird. In einer Zeile steht dieselbe Auskunft
   in der Höhe von zwei Zeilen. */
.mob-facts {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: 3px 0;
    font-size: 11.5px; color: var(--text-dim);
}
.mob-fact { display: inline-flex; align-items: baseline; gap: 5px; white-space: nowrap; }
.mob-fact + .mob-fact::before {
    content: '·'; color: var(--line-strong); margin: 0 10px;
}
.mob-fact b {
    color: var(--text); font-weight: 600;
    font-family: var(--font-mono); font-size: 11.5px;
}
.mob-fact em {
    font-style: normal; color: var(--text-faint);
    font-family: var(--font-mono); font-size: 10.5px;
}
.mob-fact em.elevated { color: var(--warn); }
.mob-fact em.high, .mob-fact em.over { color: var(--fault); }
.mob-fact.elevated b { color: var(--warn); }
.mob-fact.over, .mob-fact.over b { color: var(--fault); }

/* ---------- Filterleiste der Geräteliste ---------- */
/* Jede hier gesparte Zeile ist eine Tabellenzeile mehr. */
/* Die Filterleiste steht im Kopf, Umfang und „＋ Gerät" rechts daneben. Umbrechen
   darf der Kopf: Auf einem schmalen Fenster rutschen die Schaltflächen unter die
   Filter, statt beides zusammenzuquetschen. */
.dev-panel > .panel-head { align-items: flex-start; padding: 10px 12px; flex-wrap: wrap; }
.dev-panel > .panel-head > .panel-title { flex: 1 1 340px; }
.dev-panel > .panel-body { padding: 0; }
/* Tabelle randlos im Panel – der Panel-Rahmen fasst sie ein, sonst zwei Rahmen. Der
   untere Radius liegt beim Blättern darunter. */
.dev-panel .table-wrap { border: 0; border-radius: 0; max-height: 64vh; overflow: auto; }
.dev-filters .toolbar { gap: 8px; margin: 0; }
.dev-filters .field { gap: 2px; }
.dev-filters .field > label { font-size: 10.5px; letter-spacing: 0.02em; }
.dev-filters .input, .dev-filters .select { height: 30px; font-size: 12.5px; padding-left: 9px; }
.dev-filters .select { background-position: right 9px center; padding-right: 26px; }

/* ---------- Die Tabelle ---------- */
/* Flacher als die übrigen Tabellen: hier zählt die Menge. Bei zweihundert Geräten ist
   jede gesparte Zeilenhöhe eine Zeile, die ohne Rollen sichtbar bleibt. */
.dev-table { font-size: 12.5px; }
.dev-table th { padding: 7px 10px; }
.dev-table td { padding: 6px 10px; }
.dev-table .chip { height: 20px; padding: 0 8px; font-size: 10.5px; }
.dev-table .btn-sm { height: 22px; padding: 0 7px; font-size: 11px; }
/* Die Sortiermarke gehört zur Spaltenüberschrift und nicht neben sie: in Akzentfarbe
   und klein ist sie zu finden, ohne die Beschriftung zu verdrängen. */
.dev-sort { color: var(--accent); font-size: 8px; vertical-align: middle; }

/* =====================================================================
   Der Abruf an der Telekom-Schnittstelle, während er läuft
   ---------------------------------------------------------------------
   Er kostet eine Anfrage je SIM-Karte; bei zweihundert Karten vergehen
   Sekunden, in denen die Seite sonst fertig aussieht. Ein Spinner von
   dreizehn Pixeln in der Kopfzeile ist in dieser Zeit keine Anzeige,
   sondern eine Zierde – wer ihn nicht sucht, hält die leere
   Mobilfunkspalte für die Antwort.

   Deshalb drei Stellen, die zusammen eine Aussage tragen: eine Meldezeile
   mit laufendem Balken über dem Inhalt, ein schimmernder Platzhalter je
   Zelle, die noch keine Zahl hat, und eine Marke im Spaltenkopf. Keine
   davon verdeckt die Zahlen des letzten Abrufs – die bleiben brauchbar,
   solange dabeisteht, dass neue unterwegs sind.
   ===================================================================== */

.mob-loading {
    display: flex; flex-direction: column; gap: 7px;
    padding: 10px 14px;
    background: color-mix(in srgb, var(--accent) 7%, transparent);
    border-bottom: 1px solid var(--line);
}
.mob-loading-text { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; font-size: 12px; }
.mob-loading-text b { color: var(--text); font-weight: 600; }
.mob-loading-text .text-faint { font-size: 11px; }
.mob-loading-spin { width: 15px; height: 15px; border-width: 2px; flex: none; }

/* Ein laufender Balken ohne Fortschrittswert: wie weit der Abzug ist, weiß die
   Oberfläche nicht – die Einzelabrufe laufen nebeneinander. Einen Balken zu zeigen,
   der sich füllt, hieße eine Auskunft zu geben, die niemand hat. */
.mob-loading-bar {
    position: relative; height: 4px; border-radius: 3px;
    background: var(--panel-inset); overflow: hidden;
}
.mob-loading-bar::after {
    content: ''; position: absolute; top: 0; bottom: 0; left: 0; width: 34%;
    border-radius: inherit;
    background: linear-gradient(90deg, transparent, var(--accent), transparent);
    animation: mob-sweep 1.15s linear infinite;
}
@keyframes mob-sweep {
    from { transform: translateX(-110%); }
    to   { transform: translateX(410%); }
}

/* Platzhalter in einer Zelle, die noch keine Zahl hat. Ein Gedankenstrich wäre hier
   eine Behauptung – „keine Karte“ –, und die stimmt nicht, solange die Antwort
   unterwegs ist. */
.mob-skel {
    display: block; height: 28px; border-radius: 4px;
    background: linear-gradient(90deg,
        var(--panel-inset) 25%, var(--panel-raised) 37%, var(--panel-inset) 63%);
    background-size: 400% 100%;
    animation: mob-shimmer 1.3s ease-in-out infinite;
}
@keyframes mob-shimmer {
    from { background-position: 100% 0; }
    to   { background-position: 0 0; }
}

/* Die Zelle, die gerade einzeln nachgelesen wird: die alte Zahl bleibt lesbar, aber
   gedämpft, und der Balken schimmert. So ist zu sehen, dass sie gleich eine andere
   sein kann, ohne dass sie vorher verschwindet. */
.mob-cell.busy .mob-cell-main { opacity: .5; }
.mob-cell.busy .mob-cell-track {
    background: linear-gradient(90deg,
        var(--panel-inset) 25%, var(--panel-raised) 37%, var(--panel-inset) 63%);
    background-size: 400% 100%;
    animation: mob-shimmer 1.3s ease-in-out infinite;
}

/* Eine pulsende Marke im Spaltenkopf – sie sagt, welche Spalte sich noch füllt. */
.mob-th-load {
    display: inline-block; width: 6px; height: 6px; margin-left: 6px;
    border-radius: 50%; background: var(--accent); vertical-align: middle;
    animation: mob-pulse 1s ease-in-out infinite;
}
@keyframes mob-pulse {
    0%, 100% { opacity: .25; transform: scale(.8); }
    50%      { opacity: 1;   transform: scale(1); }
}

/* Bewegung abschalten, wo der Anwender das so eingestellt hat. Die Anzeige bleibt –
   sie ist dann eine Fläche statt einer Bewegung, aber sie ist da. */
@media (prefers-reduced-motion: reduce) {
    .mob-loading-bar::after { animation: none; width: 100%; opacity: .45; }
    .mob-skel, .mob-cell.busy .mob-cell-track { animation: none; }
    .mob-th-load { animation: none; opacity: .8; }
}

/* =====================================================================
   Kommunikation: Reiter, Lesehilfen, eingeklappter Abschnitt
   ---------------------------------------------------------------------
   Der Bericht beantwortet vier Fragen, und er stellte sie bisher alle
   untereinander auf eine Bahn. Das war nicht zu viel Inhalt, sondern zu
   viel auf einmal: wer den einen Aussetzer sucht, rollt an drei
   Auswertungen vorbei, die er gerade nicht liest.

   Getrennt wird nach der Frage und nicht nach der Darstellungsform –
   Überblick, Zeitverlauf, Auffälligkeiten, Abfragen. Was für alle vier
   gilt (fehlende Uhren, erreichte Leseobergrenze, der gelesene
   Abschnitt), steht über der Reiterleiste: hinter einem Reiter versteckt
   schränkte es eine Zahl ein, die man gerade woanders liest.
   ===================================================================== */

/* ---------- Reiterleiste ---------- */
/* Klebt oben im rollenden Bericht: die vier Reiter sind der Weg zwischen den Fragen,
   und ein Weg, den man erst wieder hochrollen muss, ist keiner. */
.ct-tabs {
    display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
    position: sticky; top: 0; z-index: 3;
    padding: 2px 0 8px;
    background: var(--panel);
    border-bottom: 1px solid var(--line);
}
.ct-tabs .tabs { flex: none; }
.ct-tabs .tabs button { height: 28px; padding: 0 12px; }

/* Die Zahl im Reiter beantwortet die erste Frage, bevor man ihn öffnet: ob es dort
   überhaupt etwas zu sehen gibt. */
.ct-tab-n {
    display: inline-block; margin-left: 6px; padding: 0 5px;
    border-radius: 9px; background: var(--panel-inset);
    font-family: var(--font-mono); font-size: 10px; line-height: 15px;
    color: var(--text-dim);
}
.ct-tab-n.alarm { color: var(--fault); background: color-mix(in srgb, var(--fault) 13%, transparent); }
.tabs button.active .ct-tab-n { color: var(--text); }
.tabs button.active .ct-tab-n.alarm { color: var(--fault); }

.ct-tab-hint {
    min-width: 0; flex: 1;
    font-size: 11.5px; color: var(--text-dim); line-height: 1.45;
}

/* ---------- Lesehilfe am Kopf eines Reiters ---------- */
/* Ein Absatz und keine Kachel: er wird einmal gelesen und danach überflogen, und ein
   gerahmter Kasten behielte dafür dauerhaft das Gewicht einer Auswertung. Die linke
   Kante hält ihn trotzdem vom Fließtext der Notizen darunter auseinander. */
.ct-lead {
    margin: 0; max-width: 96ch;
    padding: 2px 0 2px 11px;
    border-left: 2px solid var(--line-strong);
    font-size: 12.5px; line-height: 1.6; color: var(--text-dim);
}
.ct-lead b { color: var(--text); font-weight: 600; }

/* ---------- Begriffe ---------- */
/* Zweispaltig, solange Platz ist: die Erklärungen sind unterschiedlich lang, und
   untereinander entstünde eine Spalte, die man von oben nach unten liest, obwohl man
   genau einen Begriff sucht. */
.ct-terms {
    margin: 0; display: grid; gap: 4px 22px;
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
}
.ct-terms dt {
    grid-column: 1 / -1;
    font-family: var(--font-mono); font-size: 10.5px; letter-spacing: 0.08em;
    text-transform: uppercase; color: var(--accent);
    margin-top: 7px;
}
.ct-terms dt:first-child { margin-top: 0; }
.ct-terms dd {
    grid-column: 1 / -1; margin: 0;
    font-size: 11.5px; line-height: 1.6; color: var(--text-faint);
    max-width: 96ch;
}
.ct-terms dd b { color: var(--text-dim); font-weight: 600; }

/* ---------- Der Abschnitt, eingeklappt ---------- */
/* Eine Zeile: Beschriftung, Streifen, Satz, Knopf. Der Streifen bleibt auch eingeklappt
   sichtbar – er ist die einzige Anzeige, die ohne Lesen sagt, ob der Bericht über die
   ganze Auswahl geht oder über ein Stück davon. */
.ct-range-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ct-range-bar .ct-range-strip { flex: 1; min-width: 120px; margin: 0; }
.ct-range-lab {
    flex: none;
    font-family: var(--font-mono); font-size: 10.5px; letter-spacing: 0.1em;
    text-transform: uppercase; color: var(--text-dim);
}
.ct-range-val { flex: none; font-size: 11px; color: var(--text-dim); white-space: nowrap; }

/* Ein Knopf, der wie Fließtext aussieht – im Hinweis auf die Leseobergrenze steht er
   mitten im Satz, und ein Schaltflächenrahmen zerrisse ihn. */
.ct-link {
    padding: 0; border: 0; background: none; cursor: pointer;
    font: inherit; color: var(--accent); font-weight: 600;
    text-decoration: underline; text-underline-offset: 2px;
}
.ct-link:hover { color: var(--accent-hover); }

@media (max-width: 720px) {
    /* Schmale Fenster: der Satz neben den Reitern rutscht unter sie, statt sie zu
       quetschen. */
    .ct-tab-hint { flex-basis: 100%; }
    .ct-range-bar .ct-range-strip { flex-basis: 100%; }
}

/* =====================================================================
   Kommunikation: Reiter, Lesehilfen, eingeklappter Abschnitt
   ---------------------------------------------------------------------
   Der Bericht beantwortet vier Fragen, und er stellte sie bisher alle
   untereinander auf eine Bahn. Das war nicht zu viel Inhalt, sondern zu
   viel auf einmal: wer den einen Aussetzer sucht, rollt an drei
   Auswertungen vorbei, die er gerade nicht liest.

   Getrennt wird nach der Frage und nicht nach der Darstellungsform –
   Überblick, Zeitverlauf, Auffälligkeiten, Abfragen. Was für alle vier
   gilt (fehlende Uhren, erreichte Leseobergrenze, der gelesene
   Abschnitt), steht über der Reiterleiste: hinter einem Reiter versteckt
   schränkte es eine Zahl ein, die man gerade woanders liest.
   ===================================================================== */

/* ---------- Reiterleiste ---------- */
/* Klebt oben im rollenden Bericht: die vier Reiter sind der Weg zwischen den Fragen,
   und ein Weg, den man erst wieder hochrollen muss, ist keiner. */
.ct-tabs {
    display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
    position: sticky; top: 0; z-index: 3;
    padding: 2px 0 8px;
    background: var(--panel);
    border-bottom: 1px solid var(--line);
}
.ct-tabs .tabs { flex: none; }
.ct-tabs .tabs button { height: 28px; padding: 0 12px; }

/* Die Zahl im Reiter beantwortet die erste Frage, bevor man ihn öffnet: ob es dort
   überhaupt etwas zu sehen gibt. */
.ct-tab-n {
    display: inline-block; margin-left: 6px; padding: 0 5px;
    border-radius: 9px; background: var(--panel-inset);
    font-family: var(--font-mono); font-size: 10px; line-height: 15px;
    color: var(--text-dim);
}
.ct-tab-n.alarm { color: var(--fault); background: color-mix(in srgb, var(--fault) 13%, transparent); }
.tabs button.active .ct-tab-n { color: var(--text); }
.tabs button.active .ct-tab-n.alarm { color: var(--fault); }

.ct-tab-hint {
    min-width: 0; flex: 1;
    font-size: 11.5px; color: var(--text-dim); line-height: 1.45;
}

/* ---------- Lesehilfe am Kopf eines Reiters ---------- */
/* Ein Absatz und keine Kachel: er wird einmal gelesen und danach überflogen, und ein
   gerahmter Kasten behielte dafür dauerhaft das Gewicht einer Auswertung. Die linke
   Kante hält ihn trotzdem vom Fließtext der Notizen darunter auseinander. */
.ct-lead {
    margin: 0; max-width: 96ch;
    padding: 2px 0 2px 11px;
    border-left: 2px solid var(--line-strong);
    font-size: 12.5px; line-height: 1.6; color: var(--text-dim);
}
.ct-lead b { color: var(--text); font-weight: 600; }

/* ---------- Begriffe ---------- */
/* Zweispaltig, solange Platz ist: die Erklärungen sind unterschiedlich lang, und
   untereinander entstünde eine Spalte, die man von oben nach unten liest, obwohl man
   genau einen Begriff sucht. */
.ct-terms {
    margin: 0; display: grid; gap: 4px 22px;
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
}
.ct-terms dt {
    grid-column: 1 / -1;
    font-family: var(--font-mono); font-size: 10.5px; letter-spacing: 0.08em;
    text-transform: uppercase; color: var(--accent);
    margin-top: 7px;
}
.ct-terms dt:first-child { margin-top: 0; }
.ct-terms dd {
    grid-column: 1 / -1; margin: 0;
    font-size: 11.5px; line-height: 1.6; color: var(--text-faint);
    max-width: 96ch;
}
.ct-terms dd b { color: var(--text-dim); font-weight: 600; }

/* ---------- Der Abschnitt, eingeklappt ---------- */
/* Eine Zeile: Beschriftung, Streifen, Satz, Knopf. Der Streifen bleibt auch eingeklappt
   sichtbar – er ist die einzige Anzeige, die ohne Lesen sagt, ob der Bericht über die
   ganze Auswahl geht oder über ein Stück davon. */
.ct-range-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ct-range-bar .ct-range-strip { flex: 1; min-width: 120px; margin: 0; }
.ct-range-lab {
    flex: none;
    font-family: var(--font-mono); font-size: 10.5px; letter-spacing: 0.1em;
    text-transform: uppercase; color: var(--text-dim);
}
.ct-range-val { flex: none; font-size: 11px; color: var(--text-dim); white-space: nowrap; }

/* Ein Knopf, der wie Fließtext aussieht – im Hinweis auf die Leseobergrenze steht er
   mitten im Satz, und ein Schaltflächenrahmen zerrisse ihn. */
.ct-link {
    padding: 0; border: 0; background: none; cursor: pointer;
    font: inherit; color: var(--accent); font-weight: 600;
    text-decoration: underline; text-underline-offset: 2px;
}
.ct-link:hover { color: var(--accent-hover); }

@media (max-width: 720px) {
    /* Schmale Fenster: der Satz neben den Reitern rutscht unter sie, statt sie zu
       quetschen. */
    .ct-tab-hint { flex-basis: 100%; }
    .ct-range-bar .ct-range-strip { flex-basis: 100%; }
}

/* Verteilung und Auffälligkeiten standen nebeneinander in einer geteilten Zeile; beide
   waren auf diese halbe Breite und eine gedeckelte Höhe bemessen. Jetzt hat jede ihren
   eigenen Reiter – und dort ist ein Deckel, der vier Zeilen zeigt und den Rest in ein
   zweites Rollfeld sperrt, nur noch im Weg: gerollt wird ohnehin der Berichtskörper. */
.ct-finds { max-height: none; overflow: visible; padding-right: 0; }

/* Über die ganze Breite darf die Verteilung auch höher sein: die Form des Feldes – ein
   Berg, zwei Buckel, ein langer Schwanz – ist die Aussage, und bei 104 Pixeln liegt sie
   in den obersten zwanzig davon. */
.ct-hist { height: 132px; }

/* =====================================================================
   Live-Board: das Karten-Panel (Maps GPS)
   ---------------------------------------------------------------------
   Der Pin sagt, wo das Fahrzeug ist; die Spur dahinter, wo es herkommt.
   Das ist der Unterschied zum Positions-Panel daneben, in dem Breite und
   Länge als Zahlen stehen: die liest man ab und gibt sie weiter, die
   Karte beantwortet die Frage nach dem Wo.

   Die Karte füllt das Panel bis auf eine Fußzeile. Sie trägt, was auf
   dem Kartenbild selbst nicht lesbar ist – Tempo, Kurs und vor allem das
   Alter der Angabe: ein Pin sieht bei einer fünf Minuten alten Position
   genauso aus wie bei einer frischen.
   ===================================================================== */

.lp-map { gap: 7px; }

/* Die Karte nimmt sich, was die Fußzeile übrig lässt. min-height: 0 ist nötig, sonst
   wächst ein Flex-Kind nie unter seine Inhaltsgröße – und Leaflet meldet dann eine
   Höhe, die das Panel gar nicht hat. */
.lp-map-body { flex: 1; min-height: 0; }
.lp-map-body .map-wrap { height: 100%; border-radius: var(--radius-sm); }

.lp-map-foot {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    font-size: 11px; color: var(--text-dim);
}
.lp-map-fact { white-space: nowrap; }
.lp-map-fact b { font-family: var(--font-mono); font-weight: 700; color: var(--text); }
.lp-map-foot .lp-age { margin-left: auto; }

/* Der Hinweis unter der Leermeldung: er nennt die Bedingung, unter der hier etwas
   erscheint. Ohne ihn liest sich „noch keine Position“ wie ein Defekt der Anzeige. */
.lp-map-hint {
    display: block; margin-top: 5px;
    font-size: 10.5px; color: var(--text-faint); max-width: 44ch;
}

/* ---------- Der Folgen-Knopf in der Kartenleiste ---------- */
/* Eingeschaltet trägt er die Akzentfarbe: er ist ein Schalter und kein Befehl, und ein
   Schalter muss seinen Zustand zeigen, ohne dass man ihn drückt. */
.map-tool--follow.is-on {
    color: var(--on-accent); background: var(--accent); border-color: var(--accent);
}
.map-tool--follow.is-on:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.map-tool--follow.is-on svg { animation: map-follow-pulse 2.4s ease-in-out infinite; }

@keyframes map-follow-pulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: .55; }
}

@media (prefers-reduced-motion: reduce) {
    .map-tool--follow.is-on svg { animation: none; }
}

/* Auf einem kleinen Panel bleibt vom Knopf das Symbol – die Kartenleiste liegt über dem
   Kartenbild, und drei beschriftete Knöpfe nebeneinander decken davon zu viel zu. */
@container (max-width: 340px) {
    .lp-map-body .map-tool-label { display: none; }
}

/* ---------- Die CAR-ID, wo ein Fahrzeug benannt wird ----------
   Die VIN führt – sie ist die Kennung für Werkstatt, Papiere und Diagnose. Zum
   Wiedererkennen taugt sie schlecht: siebzehn Stellen, von denen sich die Fahrzeuge
   einer Baureihe in dreien unterscheiden. Gefragt wird im Alltag nach der CAR-ID, und
   die stand bisher nur im Popup – also erst, nachdem man den richtigen Pin gefunden
   hatte. Sie steht deshalb jetzt daneben: klein, in Monospace (sie ist eine Nummer, die
   man Stelle für Stelle vergleicht) und gedämpft, damit sie den Bezeichner nicht
   verdrängt. */
.live-veh-sub {
    font-family: var(--font-mono); font-size: 10.5px; color: var(--accent);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.live-veh.active .live-veh-sub { color: var(--accent); }

/* In der Fußzeile des Karten-Panels steht sie vor dem Zustand: erst welches Fahrzeug,
   dann wie es ihm geht. */
.lp-map-car {
    font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.04em;
    color: var(--accent); white-space: nowrap; flex: none;
}

/* Das Gerät an der einzelnen Messung.
   Die Auswahl oben ist das Fahrzeug – es ist der Gegenstand der Messung, der Dongle nur
   das Mittel. Nach einem Gerätetausch ist er trotzdem die Erklärung dafür, dass sich
   Signalnamen oder Abtastraten ändern, ohne dass am Fahrzeug etwas war. Er steht deshalb
   an der Zeile, gedämpft und in Monospace: eine Kennung, die man vergleicht, keine, die
   man liest. */
.ms-dongle {
    margin-left: 8px; padding-left: 8px;
    border-left: 1px solid var(--line-strong);
    font-family: var(--font-mono); font-size: 10px;
    color: var(--text-faint); white-space: nowrap;
}

/* =====================================================================
   Flotten: Organigramm, Kacheln, Ansichtswechsel
   ---------------------------------------------------------------------
   Die Seite zeigte Flottenliste, Karte und Fahrzeugtabelle nebeneinander
   – eine Anordnung als Antwort auf vier verschiedene Fragen. Jede bekam
   ein Drittel der Breite, und die Gliederung des Bestands war eine Liste
   ohne jede Struktur.

   Jetzt hat jede Frage ihre Ansicht und die ganze Seite: die Gliederung
   als Organigramm, der Zustand je Fahrzeug als Kacheln, der Bestand als
   Tabelle, die Orte als Karte.
   ===================================================================== */

.fleet-views button { height: 28px; padding: 0 12px; }

/* ---------- Flottenfilter ---------- */
/* Die Flottenliste stand als Spalte links und nahm dauerhaft ein Viertel der Breite für
   eine Auswahl, die man einmal trifft. Als Leiste kostet sie eine Zeile und gilt für alle
   Ansichten. */
.fleet-bar {
    display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
    margin-bottom: 12px;
}
.fleet-chip {
    display: inline-flex; align-items: center; gap: 7px; height: 28px; padding: 0 11px;
    border-radius: 20px; cursor: pointer;
    background: var(--panel); border: 1px solid var(--line);
    color: var(--text-dim); font-family: var(--font-ui); font-size: 12px;
    transition: color .12s, border-color .12s, background .12s;
}
.fleet-chip:hover { color: var(--text); border-color: var(--line-strong); }
.fleet-chip.active {
    color: var(--accent); border-color: var(--accent); background: var(--accent-soft);
}
.fc-n {
    font-family: var(--font-mono); font-size: 10.5px; padding: 0 6px;
    border-radius: 9px; background: var(--panel-inset); color: var(--text-faint);
}
.fleet-chip.active .fc-n { color: var(--accent); background: transparent; }
.fleet-search { height: 30px; max-width: 300px; font-size: 12.5px; }

/* ---------- Organigramm ---------- */
/* Die Linien sind nicht Zierrat, sie sind die Aussage: welches Fahrzeug hängt an welcher
   Flotte. Eine eingerückte Liste sagt dasselbe, aber man muss sie lesen – ein Organigramm
   sieht man. */
.org { display: flex; flex-direction: column; align-items: stretch; }

.org-root { display: flex; justify-content: center; padding-bottom: 18px; position: relative; }
/* Der Stamm: eine Linie von der Wurzel hinunter zu den Ästen. */
.org-root::after {
    content: ''; position: absolute; bottom: 0; left: 50%;
    width: 1px; height: 18px; background: var(--line-strong);
}

.org-box {
    display: flex; align-items: center; gap: 10px; min-width: 0;
    padding: 10px 14px;
    background: var(--panel-raised); border: 1px solid var(--line-strong);
    border-radius: var(--radius-sm);
}
.org-box--dept {
    flex-direction: column; align-items: center; gap: 2px; text-align: center;
    border-color: var(--accent); background: var(--accent-soft);
    padding: 12px 22px;
}
.org-kind {
    font-family: var(--font-mono); font-size: 9.5px; letter-spacing: 0.1em;
    text-transform: uppercase; color: var(--text-faint);
}
.org-name { font-size: 13.5px; font-weight: 600; color: var(--text); }
.org-desc {
    font-size: 11px; color: var(--text-faint);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.org-facts { font-family: var(--font-mono); font-size: 11px; color: var(--text-dim); }

/* Die Äste hängen an einer waagerechten Leitung unter der Wurzel. */
.org-branches { display: flex; flex-direction: column; gap: 14px; position: relative; }

.org-branch { position: relative; padding-left: 26px; }
/* Senkrechte Leitung links, waagerechter Abzweig zum Flottenkasten. */
.org-branch::before {
    content: ''; position: absolute; left: 0; top: 0; bottom: 0;
    width: 1px; background: var(--line-strong);
}
.org-branch:last-child::before { bottom: auto; height: 22px; }
.org-branch::after {
    content: ''; position: absolute; left: 0; top: 22px;
    width: 26px; height: 1px; background: var(--line-strong);
}

.org-box--fleet { justify-content: flex-start; }
.org-box--fleet .org-box-facts {
    margin-left: auto; display: flex; gap: 12px; flex: none;
    font-family: var(--font-mono); font-size: 11px; color: var(--text-dim);
}
.org-box-facts .hot { color: var(--fault); }

.org-toggle {
    flex: none; width: 22px; height: 22px; padding: 0;
    border: 1px solid var(--line-strong); border-radius: 4px;
    background: var(--panel); color: var(--text-dim);
    font-size: 11px; cursor: pointer;
}
.org-toggle:hover { color: var(--accent); border-color: var(--accent); }

/* Die Fahrzeuge als Kacheln nebeneinander und nicht als weitere Ebene von Kästen: eine
   Flotte mit dreißig Fahrzeugen wäre sonst dreißig Bildschirme breit. */
.org-leaves {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(215px, 1fr));
    gap: 7px; padding: 12px 0 2px 26px; position: relative;
}
.org-leaves::before {
    content: ''; position: absolute; left: 0; top: 0; height: 12px;
    width: 1px; background: var(--line-strong);
}
.org-empty { font-size: 11.5px; color: var(--text-faint); padding: 4px 0; }

.org-leaf {
    display: flex; flex-direction: column; gap: 6px; min-width: 0;
    padding: 8px 10px; cursor: pointer; text-align: left; font: inherit;
    background: var(--panel); border: 1px solid var(--line);
    border-left-width: 3px; border-left-color: var(--line-strong);
    border-radius: var(--radius-sm);
    transition: border-color .12s, background .12s;
}
.org-leaf:hover { background: var(--panel-raised); border-color: var(--line-strong); }
.org-leaf-head { display: flex; align-items: center; gap: 8px; min-width: 0; }
.org-leaf-name {
    font-size: 12px; font-weight: 600; color: var(--text);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.org-leaf-sub {
    font-family: var(--font-mono); font-size: 10px; color: var(--accent);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.org-leaf-facts {
    display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
    font-family: var(--font-mono); font-size: 10px; color: var(--text-faint);
}
.org-leaf-seen { margin-left: auto; white-space: nowrap; }

/* ---------- Fahrzeugkacheln ---------- */
.veh-cards {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(290px, 1fr)); gap: 12px;
}
.veh-card {
    display: flex; flex-direction: column; gap: 10px; min-width: 0;
    padding: 12px;
    background: var(--panel); border: 1px solid var(--line);
    border-left-width: 3px; border-left-color: var(--line-strong);
    border-radius: var(--radius-sm);
}
.vc-head { display: flex; align-items: center; gap: 10px; min-width: 0; }
.vc-name {
    font-size: 13px; font-weight: 600; color: var(--text);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.vc-sub {
    font-family: var(--font-mono); font-size: 10.5px; color: var(--accent);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.vc-fleet { font-size: 10.5px; color: var(--text-faint); }

.vc-facts { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
.vc-fact {
    display: flex; flex-direction: column; gap: 1px; min-width: 0;
    padding: 6px 8px; border-radius: 4px; background: var(--panel-inset);
}
.vc-k {
    font-family: var(--font-mono); font-size: 8.5px; letter-spacing: 0.06em;
    text-transform: uppercase; color: var(--text-faint);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.vc-v { font-family: var(--font-mono); font-size: 14px; font-weight: 600; color: var(--text); }
.vc-fact.hot .vc-v { color: var(--fault); }

.vc-foot {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    font-size: 11px; color: var(--text-dim);
}
.vc-seen { white-space: nowrap; }
.vc-task { font-family: var(--font-mono); font-size: 10px; color: var(--text-faint); }

.vc-devices { display: flex; gap: 5px; flex-wrap: wrap; }
.vc-dev {
    display: inline-flex; align-items: center; gap: 4px;
    padding: 2px 7px; border-radius: 20px;
    background: var(--panel-inset); border: 1px solid var(--line);
    font-size: 10px; color: var(--text-dim);
}
.vc-dev .hot { color: var(--fault); }

/* ---------- Wie frisch ist das Fahrzeug ----------
   Die Kante am Rand ordnet alle Zahlen der Kachel ein: 120 Messpakete an einem Fahrzeug,
   das seit einem Jahr schweigt, sind etwas anderes als 120 an einem, das gestern gefahren
   ist. Keine Wertung – bei einem Fahrzeug im Winterlager ist „still“ der Normalzustand,
   deshalb steht am oberen Ende kein Grün und am unteren kein Rot. */
.act-live   { border-left-color: var(--ok); }
.act-recent { border-left-color: var(--accent); }
.act-idle   { border-left-color: var(--warn); }
.act-cold   { border-left-color: var(--line-strong); }
.act-none   { border-left-color: var(--line); border-left-style: dashed; }

/* ---------- Die Ansichten füllen die Seite ---------- */
.org-panel > .panel-body,
.fleet-cards-panel > .panel-body { padding: 14px; }
.fleet-map-view,
.fleet-table-view { height: calc(100vh - 210px); min-height: 380px; }
.fleet-table-view > .panel { height: 100%; }
.fleet-map-view > .panel { height: 100%; display: flex; flex-direction: column; }
.fleet-map-view > .panel > .panel-body { flex: 1; min-height: 0; padding: 0; }

@media (max-width: 720px) {
    /* Schmal: die vier Kennzahlen einer Kachel stehen zu zweit statt zu viert. */
    .vc-facts { grid-template-columns: repeat(2, 1fr); }
    .org-branch { padding-left: 16px; }
    .org-branch::after { width: 16px; }
    .org-leaves { padding-left: 16px; }
}

/* ============================================================================
   Live-Board: freie Anordnung, Warnsignale, durchsuchbare Auswahl
   ========================================================================= */

/* ---------- Raster mit festen Plätzen ----------
   Jedes Panel trägt seine Zelle selbst (siehe LiveBoardArrange). Das Raster braucht
   dafür nichts weiter als seine Spalten – die Zeilen entstehen implizit und sind alle
   gleich hoch (grid-auto-rows). Eine Lücke ist damit eine Lücke und wird nicht mehr
   von der nächsten Kachel aufgefüllt. */
.lm-grid { align-content: start; }

/* Im Bearbeiten-Modus wird auch das Zeilenmaß sichtbar. Bisher zeigte der Hintergrund
   nur die Spalten; beim freien Ablegen ist die Zeile aber die zweite Hälfte der
   Entscheidung – ohne sie zielt man auf ein Raster, von dem man die Hälfte nicht
   sieht. 92px Zeile plus 14px Abstand, wie in .dash-grid. */
.dash-grid.lm-grid.is-editing {
    background-image:
        repeating-linear-gradient(to right,
            color-mix(in srgb, var(--accent) 12%, transparent) 0 1px,
            transparent 1px calc((100% - 11 * 14px) / 12 + 14px)),
        repeating-linear-gradient(to bottom,
            color-mix(in srgb, var(--accent) 9%, transparent) 0 1px,
            transparent 1px 106px);
}

/* Freie Fläche am Ende eines Abschnitts: die Ablagefläche. Sie ist im Ruhezustand
   kaum zu sehen und tritt erst hervor, während etwas am Zeiger hängt – ein dauerhaft
   gestrichelter Kasten unter jedem Ordner wäre auf einem Board mit vier Ordnern die
   auffälligste Fläche der Seite. */
.dash-pad {
    display: grid; place-items: center;
    border: 1px dashed color-mix(in srgb, var(--line-strong) 70%, transparent);
    border-radius: var(--radius-sm);
    color: transparent; font-size: 11.5px;
    transition: border-color .15s, background .15s, color .15s;
    pointer-events: none;
}
.dash-grid.is-moving .dash-pad {
    border-color: color-mix(in srgb, var(--accent) 55%, transparent);
    background: color-mix(in srgb, var(--accent) 5%, transparent);
    color: var(--text-faint);
}

/* Der Platzhalter zeigt, wo das Panel landet. Das Panel selbst hängt am Zeiger – ohne
   diesen Unterschied sieht man beim Ziehen nur, wo es gerade ist, nicht, wo es liegen
   bleiben wird. Er wird von js/dashgrid.js erzeugt und wieder entfernt. */
.dash-ghost {
    border: 2px dashed var(--accent);
    border-radius: var(--radius);
    background: color-mix(in srgb, var(--accent) 12%, transparent);
    box-shadow: inset 0 0 26px color-mix(in srgb, var(--accent) 14%, transparent);
    pointer-events: none;
    z-index: 2;
}

/* Das gegriffene Panel liegt über allem und folgt dem Zeiger (transform in JS). */
.widget.moving {
    z-index: 30; cursor: grabbing;
    opacity: .92;
    outline: 2px solid var(--accent); outline-offset: 2px;
    box-shadow: 0 18px 42px rgba(0,0,0,.42);
}
.dash-grid.is-moving .widget:not(.moving) { opacity: .72; }
body.dash-moving { cursor: grabbing; user-select: none; }
body.dash-moving .widget .panel-body { pointer-events: none; }

/* Die Rasterzelle als Rückmeldung neben dem Maß: zwei Kacheln auf derselben Spalte
   sind bündig, und das liest man an der Zahl schneller ab als am Pixel. */
.pe-at {
    font-family: var(--font-mono); font-size: 10px; color: var(--text-faint);
    padding-left: 6px; border-left: 1px solid var(--line);
}
@container widget (max-width: 260px) { .widget.editing .pe-at { display: none; } }

/* ---------- Ungespeichert ----------
   Eine Lage, kein Knopf – deshalb ein Chip und kein gefüllter Button. */
.chip.chip-dirty {
    border-color: var(--warn); color: var(--warn);
    font-weight: 600;
}
.chip.chip-dirty::before {
    content: ""; width: 6px; height: 6px; border-radius: 50%;
    background: var(--warn); margin-right: 6px; flex: none;
}

/* "Anordnen" ist der Gegenpol zur freien Anordnung: wer Kacheln von Hand verteilt,
   erzeugt irgendwann ein schiefes Board. Der Knopf trägt deshalb ein eigenes Gesicht,
   ohne mit "Panel" (btn-add) oder "Speichern" (btn-primary) zu konkurrieren. */
.btn.btn-arrange { border-color: var(--line-strong); }
.btn.btn-arrange:hover { border-color: var(--accent); color: var(--accent); }
.btn.btn-arrange svg { width: 15px; height: 15px; }

/* Der Ton-Schalter: eingeschaltet ist der Normalfall und bleibt ruhig, stumm ist die
   Ausnahme und wird angezeigt. */
.btn.btn-mute svg { width: 16px; height: 16px; }
.btn.btn-mute:not(.is-on) { color: var(--text-dim); }
.btn.btn-mute.is-on { border-color: color-mix(in srgb, var(--accent) 50%, var(--line-strong)); }

/* ---------- Anordnungs-Vorlagen ---------- */
.arr-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(215px, 1fr)); gap: 10px; }
.arr-card {
    display: flex; flex-direction: column; gap: 9px; text-align: left;
    padding: 10px; border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--panel-raised); color: var(--text); cursor: pointer;
    transition: border-color .14s, background .14s, box-shadow .14s, transform .05s;
}
.arr-card:hover { border-color: var(--accent); background: var(--accent-soft); }
.arr-card:active { transform: translateY(1px); }
.arr-card.active {
    border-color: var(--accent);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 24%, transparent);
}

/* Das Vorschaubild ist dasselbe Raster wie das Board, nur klein – zwölf Spalten, sechs
   Zeilen. "Kompakt" und "Nach Größe" unterscheiden sich als Wörter nicht, als Form
   sofort. */
.arr-prev {
    display: grid; grid-template-columns: repeat(12, 1fr); grid-template-rows: repeat(6, 9px);
    gap: 2px; padding: 5px; border-radius: 4px;
    background: var(--panel-inset); border: 1px solid var(--line);
}
.arr-prev > span {
    border-radius: 2px;
    background: color-mix(in srgb, var(--accent) 42%, transparent);
}
.arr-card:hover .arr-prev > span,
.arr-card.active .arr-prev > span { background: var(--accent); }

.arr-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.arr-name { font-size: 13px; font-weight: 600; }
.arr-note { font-size: 11px; color: var(--text-dim); line-height: 1.45; }

/* ---------- Rückfrage vor dem Verwerfen ---------- */
.disc-list {
    margin: 0; padding: 10px 12px 10px 28px; list-style: disc;
    border: 1px solid var(--line); border-left: 3px solid var(--warn);
    border-radius: var(--radius-sm); background: var(--panel-inset);
    font-size: 12.5px; line-height: 1.75; color: var(--text);
}

/* ---------- Warnsignale im Panel-Dialog ---------- */
.lp-grid-sound { grid-template-columns: 1.3fr 1.3fr 1fr; }
.snd-row { display: flex; gap: 8px; align-items: stretch; }
.snd-row > .select { flex: 1; min-width: 0; }
.snd-play { flex: none; }
.snd-play .btn-ico { color: var(--accent); }

/* ---------- Wertgröße und Wertlage ----------
   Statt zweier Auswahlfelder: Knöpfe, die zeigen, was sie tun, und daneben dieselbe
   Kachel, wie sie auf dem Board aussehen wird. */
.vp-lay { display: grid; grid-template-columns: minmax(0, 1fr) 260px; gap: 16px; align-items: start; }
.vp-controls { display: flex; flex-direction: column; gap: 14px; min-width: 0; }

.vp-sizes { display: flex; gap: 6px; flex-wrap: wrap; }
.vp-size {
    flex: 1 1 0; min-width: 62px;
    display: flex; flex-direction: column; align-items: center; justify-content: flex-end; gap: 4px;
    height: 62px; padding: 6px 4px;
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--panel-raised); color: var(--text); cursor: pointer;
    transition: border-color .14s, background .14s, color .14s;
}
.vp-size:hover { border-color: var(--accent); background: var(--accent-soft); }
.vp-size.on {
    border-color: var(--accent); background: var(--accent-soft); color: var(--accent);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 20%, transparent);
}
/* Die Zahl auf dem Knopf trägt die Stufe, um die es geht – mit derselben Bewegung wie
   in der Vorschau, damit erkennbar bleibt, dass beides dasselbe meint. */
.vp-size-num {
    font-family: var(--font-mono); font-weight: 700; line-height: 1;
    transition: font-size .2s ease;
}
.vp-size-name { font-size: 10px; color: var(--text-dim); }
.vp-size.on .vp-size-name { color: var(--accent); }

.vp-pos { display: flex; gap: 6px; flex-wrap: wrap; }
.vp-pos-btn {
    flex: 1 1 0; min-width: 70px;
    display: flex; flex-direction: column; align-items: center; gap: 5px;
    padding: 7px 5px;
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--panel-raised); color: var(--text); cursor: pointer;
    transition: border-color .14s, background .14s, color .14s;
}
.vp-pos-btn:hover { border-color: var(--accent); background: var(--accent-soft); }
.vp-pos-btn.on {
    border-color: var(--accent); background: var(--accent-soft); color: var(--accent);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 20%, transparent);
}
.vp-pos-name { font-size: 10px; color: var(--text-dim); }
.vp-pos-btn.on .vp-pos-name { color: var(--accent); }

/* Sinnbild der Kachel: der dicke Strich ist die Zahl, der dünne die Fußzeile. Genau so
   verschiebt sich beides im Panel – die Skizze ist keine Verzierung, sondern dasselbe
   Layout in klein. */
.vp-mini {
    width: 46px; height: 30px; padding: 4px;
    display: flex; flex-direction: column; justify-content: space-between;
    border: 1px solid var(--line-strong); border-radius: 3px;
    background: var(--panel-inset);
}
.vp-mini-num { height: 7px; width: 20px; border-radius: 2px; background: currentColor; opacity: .85; transition: all .18s; }
.vp-mini-foot { height: 3px; width: 26px; border-radius: 2px; background: var(--text-faint); }
.vp-mini.at-tr { align-items: flex-end; }
.vp-mini.at-bl { flex-direction: column-reverse; }
.vp-mini.at-br { flex-direction: column-reverse; align-items: flex-end; }
.vp-mini.at-c { justify-content: center; align-items: center; gap: 4px; }
.vp-mini.at-c .vp-mini-foot { width: 18px; }

/* ---- Einstellungen der Fahrzeugkachel ---- */
/* Sinnbild der Bildlage: die volle Flaeche ist das Bild, der Streifen sind Lampen und
   Angaben. Wie beim Wertpositions-Schalter ist die Skizze keine Verzierung, sondern
   dasselbe Layout in klein - man erkennt die Wirkung, ohne sie auszuprobieren. */
.veh-mini {
    width: 46px; height: 30px; padding: 3px;
    display: grid; gap: 3px;
    grid-template-rows: 11px 1fr;
    border: 1px solid var(--line-strong); border-radius: 3px;
    background: var(--panel-inset);
}
.veh-mini-img { border-radius: 1px; background: var(--accent); opacity: .5; }
.veh-mini-body { border-radius: 1px; background: var(--text-faint); opacity: .45; }
.vp-pos-btn.on .veh-mini-img { opacity: .8; }

/* Vollflaechig: das Bild nimmt fast alles, der Streifen unten sind die Statuszeilen -
   und er steht unten, weil er im Panel auch unten steht. */
.veh-mini.vm-fill { grid-template-rows: 1fr 6px; }

.veh-mini.vm-left { grid-template-rows: 1fr; grid-template-columns: 15px 1fr; }
.veh-mini.vm-right { grid-template-rows: 1fr; grid-template-columns: 1fr 15px; }
/* Rechts liegt das Bild hinter dem Rumpf - dieselbe order-Verschiebung wie in der
   Kachel selbst, damit Sinnbild und Ergebnis sich gleich verhalten. */
.veh-mini.vm-right .veh-mini-img { order: 2; }
.veh-mini.vm-off { grid-template-rows: 1fr; }
.veh-mini.vm-off .veh-mini-img { display: none; }

/* Zwoelf Bausteine als umbrechende Reihe waeren eine Wand aus Haken in wechselnden
   Abstaenden. Feste Spalten halten die Kaestchen untereinander, und damit liest sich die
   Liste als Liste. */
.veh-parts {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(165px, 1fr));
    gap: 5px 16px;
}

/* Die Vorschau ist ein Panel im Kleinen: Kopf mit Kanal und Titel, darunter der Körper
   mit dem laufenden Messwert. */
.vp-prev-field { min-width: 0; }
.vp-prev {
    border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
    background: var(--panel); overflow: hidden;
}
.vp-prev-head { padding: 8px 11px; border-bottom: 1px solid var(--line); }
.vp-prev-head .eyebrow,
.vp-prev-head h3 {
    display: block; margin: 0; max-width: 100%;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.vp-prev-head h3 { font-size: 13px; }
.vp-prev-body { padding: 11px; height: 132px; container: widget / inline-size; }
.vp-prev-body > .lp { animation: vp-settle .26s ease-out; }

/* Die Bewegung beim Wechsel der Lage. Ein Sprung ohne Übergang lässt offen, ob man
   überhaupt etwas getroffen hat – der Schlüssel am Element stößt sie neu an. */
@keyframes vp-settle {
    from { opacity: .25; transform: scale(.97); }
    to { opacity: 1; transform: none; }
}
/* Die Größe bewegt sich als Übergang und nicht als Sprung: so ist zu sehen, in welche
   Richtung die Stufe wirkt. */
.vp-prev .lp-number { transition: font-size .22s ease; }

.vp-prev-bar {
    height: 8px; border-radius: 5px; background: var(--panel-inset);
    border: 1px solid var(--line); overflow: hidden;
}
.vp-prev-bar > span {
    display: block; height: 100%; background: var(--accent);
    transition: width .3s ease;
}

@media (prefers-reduced-motion: reduce) {
    .vp-prev-body > .lp { animation: none; }
    .vp-prev .lp-number, .vp-size-num, .vp-mini-num { transition: none; }
}

@media (max-width: 820px) {
    .vp-lay { grid-template-columns: 1fr; }
    .lp-grid-sound { grid-template-columns: 1fr; }
}

/* ---------- Durchsuchbares Auswahlfeld ----------
   Ein select reicht für zehn Zeilen. Ein Bereich mit sechzig Fahrzeugen oder ein
   Fahrzeug mit vierhundert Signalen ist damit nicht zu bedienen – man rollt durch eine
   Liste, in der jede Zeile aussieht wie die davor. */
.sels { position: relative; min-width: 0; }
.sels-btn {
    display: flex; align-items: center; gap: 8px; width: 100%;
    height: 36px; padding: 0 10px;
    border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
    background: var(--panel-inset); color: var(--text); cursor: pointer;
    font-family: var(--font-ui); font-size: 13px; text-align: left;
    transition: border-color .14s, background .14s;
}
.sels-btn:hover:not(:disabled) { border-color: var(--accent); }
.sels-btn:disabled { opacity: .5; cursor: not-allowed; }
.sels.open .sels-btn { border-color: var(--accent); }
.sels-text { display: flex; align-items: baseline; gap: 8px; min-width: 0; flex: 1; }
.sels-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sels-label.placeholder { color: var(--text-faint); }
.sels-sub {
    font-family: var(--font-mono); font-size: 10.5px; color: var(--text-faint);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
.sels-caret { color: var(--text-dim); flex: none; font-size: 11px; }
.sels-mono .sels-label { font-family: var(--font-mono); font-size: 12.5px; }
/* Zusatz hinter dem Label, z. B. die VIN hinter der CAR-ID: gleiche Zeile, eigene Schrift. */
.sels-note {
    margin-left: 6px; font-family: var(--font-mono); font-size: .88em; font-weight: 400;
    color: var(--text-dim);
}
.sels-item.sel .sels-note { color: inherit; opacity: .8; }

.sels-pop {
    position: absolute; z-index: 60; top: calc(100% + 4px); left: 0; right: 0;
    min-width: 260px;
    border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
    background: var(--panel); box-shadow: var(--shadow);
    display: flex; flex-direction: column; overflow: hidden;
}
.sels-search {
    display: flex; align-items: center; gap: 7px; padding: 7px 9px;
    border-bottom: 1px solid var(--line); background: var(--panel-raised);
}
.sels-search svg { width: 14px; height: 14px; color: var(--text-faint); flex: none; }
.sels-search .input { height: 28px; border: 0; background: transparent; padding: 0; flex: 1; min-width: 0; }
.sels-search .input:focus { border: 0; }
.sels-count { font-family: var(--font-mono); font-size: 10px; color: var(--text-faint); flex: none; }

.sels-list { max-height: 264px; overflow: auto; padding: 5px; }
.sels-group {
    font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .09em;
    text-transform: uppercase; color: var(--text-faint);
    padding: 7px 8px 3px;
}
.sels-item {
    display: flex; align-items: center; gap: 8px; width: 100%;
    padding: 6px 8px; border: 0; border-radius: var(--radius-sm);
    background: transparent; color: var(--text); cursor: pointer;
    text-align: left; font-family: var(--font-ui); font-size: 12.5px;
}
.sels-item:hover, .sels-item.active { background: var(--panel-raised); }
.sels-item.sel { background: var(--accent-soft); color: var(--accent); }
.sels-item.active { box-shadow: inset 0 0 0 1px var(--accent); }
.sels-item-main { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
.sels-item-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sels-mono .sels-item-label { font-family: var(--font-mono); font-size: 12px; }
.sels-item-sub {
    font-size: 10.5px; color: var(--text-faint);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sels-item-badge {
    font-family: var(--font-mono); font-size: 10px; color: var(--text-dim);
    flex: none; padding: 1px 6px; border-radius: 20px; background: var(--panel-inset);
}
/* Was sendet, ist das, worauf man ein Panel einrichtet – es steht oben und trägt Gewicht. */
.sels-item.online .sels-item-label { font-weight: 600; }
.sels-item.empty-item { color: var(--text-dim); font-style: italic; }
.sels-none { padding: 14px 10px; text-align: center; font-size: 12px; color: var(--text-faint); }

/* ---------- Mehrfachauswahl von Signalen ----------
   Eine Heatmap über ein Pack sind dreistellig viele Häkchen. Ohne Schnellsuche und
   Sammelknöpfe ist das keine Konfiguration mehr. */
.smp { display: flex; flex-direction: column; gap: 8px; }
.smp-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.smp-search {
    display: flex; align-items: center; gap: 7px; flex: 1 1 220px; min-width: 180px;
    padding: 0 10px; height: 34px;
    border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
    background: var(--panel-inset);
}
.smp-search svg { width: 14px; height: 14px; color: var(--text-faint); flex: none; }
.smp-search .input { height: 30px; border: 0; background: transparent; padding: 0; flex: 1; min-width: 0; }
.smp-only { font-size: 12px; color: var(--text-dim); flex: none; }
.smp-count {
    font-family: var(--font-mono); font-size: 11px; font-weight: 600;
    color: var(--accent); flex: none; margin-left: auto;
    padding: 3px 9px; border-radius: 20px; background: var(--accent-soft);
}
.smp-count.none { color: var(--text-faint); background: var(--panel-inset); }
.smp-hint { margin: 0; font-size: 11px; color: var(--text-faint); }
.smp-list { max-height: 340px; }
.smp-none { padding: 18px 10px; text-align: center; font-size: 12px; color: var(--text-faint); }

/* Knopf statt Beschriftung mit Kästchen: nur so kommt die Umschalttaste im Ereignis an,
   und ohne sie gäbe es keine Spannenauswahl. Das Kästchen ist deshalb gezeichnet. */
.smp-item {
    display: flex; align-items: center; gap: 7px; width: 100%; min-width: 0;
    padding: 3px 5px; border: 0; border-radius: 4px;
    background: transparent; color: var(--text); cursor: pointer;
    text-align: left; font-size: 12px;
}
.smp-item:hover { background: var(--panel-raised); }
.smp-box {
    width: 14px; height: 14px; flex: none; border-radius: 3px;
    border: 1px solid var(--line-strong); background: var(--panel);
    display: grid; place-items: center;
    font-size: 10px; line-height: 1; color: var(--on-accent);
}
.smp-item.on .smp-box { background: var(--accent); border-color: var(--accent); }
.smp-item.on .smp-name { color: var(--accent); }
.smp-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 1; }

/* ============================================================================
   Reiter am Seitenkopf

   Die Reiter oben rechts schalten zwischen den Ansichten einer Seite um – auf den
   Zeitreihen zwischen Kurve, Strecke, Auswertung, Kommunikation und Tabelle, auf den
   Flotten zwischen vier Blicken auf denselben Bestand. Sie sind damit das Wichtigste,
   was die Kopfzeile zu bieten hat, sahen aber aus wie eine Beschriftung: 30 Pixel hoch,
   12 Pixel Schrift, der aktive Reiter nur eine Spur heller als die anderen. Wer die
   Seite zum ersten Mal öffnete, hat sie schlicht nicht gesehen – und damit vier Fünftel
   der Seite nie gefunden.

   Jetzt: größer, fetter, und der aktive Reiter trägt die Akzentfarbe gefüllt. Das ist
   dieselbe Sprache, die sonst „hier bist du" bedeutet (Navigationsleiste, gewählte
   Zeile) – und es ist der einzige gefüllte Akzent in der Kopfzeile, weil dort sonst nur
   umrandete Knöpfe stehen.

   Die Reiter INNERHALB eines Panels bleiben, wie sie waren: sie gliedern einen Inhalt,
   der schon eine Überschrift hat, und würden in dieser Größe mit ihr konkurrieren.
   ========================================================================= */
.tabs {
    gap: 3px;
    padding: 4px;
    border-color: var(--line-strong);
    border-radius: 10px;
}
.tabs button {
    height: 38px;
    padding: 0 18px;
    font-size: 13.5px;
    font-weight: 600;
    border-radius: 7px;
    transition: background .15s, color .15s, box-shadow .15s;
}
.tabs button:hover:not(.active):not(:disabled) {
    background: var(--panel-raised);
    color: var(--text);
}
.tabs button.active {
    background: var(--accent);
    color: var(--on-accent);
    box-shadow: 0 1px 3px rgba(0, 0, 0, .28),
                0 0 0 1px color-mix(in srgb, var(--accent) 55%, transparent);
}
.tabs button:disabled { opacity: .42; cursor: not-allowed; }
.tabs button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Reiter tragen gelegentlich eine Zahl (offene Meldungen, Anzahl Zeilen). Auf gefülltem
   Akzent braucht sie einen eigenen Grund, sonst verschwindet sie darin. */
.tabs button.active .an-badge,
.tabs button.active .ct-tab-n {
    background: color-mix(in srgb, #000 26%, transparent);
    color: var(--on-accent);
}

/* Die Flotten-Reiter tragen vier lange Wörter nebeneinander; sie bekommen etwas weniger
   Polsterung, damit die Kopfzeile bei mittlerer Fensterbreite nicht umbricht. */
.fleet-views button { height: 38px; padding: 0 13px; }

/* ---------- Reiter im Panel bleiben klein ----------
   Sie gliedern den Inhalt einer Kachel, die schon eine Überschrift trägt. In der Größe
   der Seitenreiter würden zwei Überschriften übereinander stehen und sich gegenseitig
   die Bedeutung nehmen. */
.panel-body .tabs {
    gap: 2px; padding: 3px; border-color: var(--line); border-radius: var(--radius-sm);
}
.panel-body .tabs button {
    height: 30px; padding: 0 14px; font-size: 12px; font-weight: 500; border-radius: 4px;
}
.panel-body .tabs button.active {
    background: var(--panel-raised); color: var(--text);
    box-shadow: inset 0 0 0 1px var(--line-strong);
}
.panel-body .tabs button.active .an-badge,
.panel-body .tabs button.active .ct-tab-n {
    background: var(--panel-inset); color: var(--text);
}

@media (max-width: 760px) {
    /* Schmal zählt die Lesbarkeit mehr als die Wucht – sonst bricht die Kopfzeile um,
       und die Reiter landen unter der Überschrift statt neben ihr. */
    .tabs button { height: 34px; padding: 0 12px; font-size: 12.5px; }
    .fleet-views button { padding: 0 10px; }
}

/* ============================================================================
   ODX – Validierung & Korrekturen

   Ein Befund besteht aus vier Teilen, und alle vier braucht man: was nicht stimmt
   (Titel), die konkreten Werte (Detail, in Festbreite), warum das ein Fehler ist
   (Begründung) und der Satz aus der Norm, auf den er sich stützt (ausklappbar). Die
   Korrektur steht daneben – mit ihrer Wirkung im Klartext, denn bei Herstellerdaten
   ist ein Knopf ohne Vorschau ein Risiko.
   ========================================================================= */
.odx-val { display: flex; flex-direction: column; gap: 14px; }

/* ---------- Bilanz nach Prüfart ----------
   Sie beantwortet die erste Frage nach dem Prüflauf: liegt es an der Form oder am
   Inhalt? Zugleich Filter – ein Klick zeigt nur diese Art. */
.odx-val-stats {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(122px, 1fr));
    gap: 8px; margin-top: 12px;
}
.ovs {
    display: flex; flex-direction: column; gap: 3px; align-items: flex-start;
    padding: 9px 11px; border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--panel-inset); color: var(--text); cursor: pointer; text-align: left;
    transition: border-color .14s, background .14s;
}
.ovs:hover { border-color: var(--accent); }
.ovs.on {
    border-color: var(--accent); background: var(--accent-soft);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 22%, transparent);
}
.ovs-n {
    font-family: var(--font-mono); font-size: 19px; font-weight: 700; line-height: 1;
    color: var(--text-dim);
}
.ovs-n.err { color: var(--fault); }
.ovs-n.warn { color: var(--warn); }
.ovs-t { font-size: 11px; color: var(--text-dim); }

/* ---------- Befundbaum ----------
   Gezeigt werden nur die Äste, an denen etwas hängt: ein Steuergerätedokument hat
   sechsstellig viele Elemente, davon sind ein paar Dutzend auffällig. Die Einrückung
   kommt aus --d, damit die Verschachtelung ohne verschachtelte Elemente auskommt. */
.odx-val-tree { display: flex; flex-direction: column; gap: 2px; }
.ovt-node { display: flex; flex-direction: column; gap: 2px; }
.ovt-head {
    display: flex; align-items: center; gap: 8px;
    margin-left: calc(var(--d, 0) * 16px);
    padding: 6px 9px; border-radius: var(--radius-sm);
    border: 1px solid transparent; cursor: pointer;
    transition: background .12s, border-color .12s;
}
.ovt-head:hover { background: var(--panel-raised); border-color: var(--line); }
.ovt-caret { color: var(--text-faint); font-size: 10px; flex: none; width: 10px; }
.ovt-el {
    font-family: var(--font-mono); font-size: 11px; letter-spacing: .04em;
    color: var(--text-dim); flex: none;
}
.ovt-name {
    font-size: 12.5px; font-weight: 600; color: var(--text);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
.ovt-badge {
    font-family: var(--font-mono); font-size: 10px; font-weight: 700;
    padding: 1px 7px; border-radius: 20px; flex: none; color: #fff;
}
.ovt-badge.err { background: var(--fault); }
.ovt-badge.warn { background: var(--warn); color: #14100a; }
.ovt-badge.fix { background: var(--accent); color: var(--on-accent); }
/* Die Zählungen rücken nach rechts, sobald links Platz frei bleibt. */
.ovt-badge:first-of-type { margin-left: auto; }

/* ---------- Ein Befund ---------- */
.ovf {
    display: flex; flex-direction: column; gap: 6px;
    margin-left: calc((var(--d, 0) + 1) * 16px);
    padding: 10px 12px; border-radius: var(--radius-sm);
    border: 1px solid var(--line); border-left-width: 3px;
    background: var(--panel-inset);
}
.ovf.err { border-left-color: var(--fault); }
.ovf.warn { border-left-color: var(--warn); }

.ovf-top { display: flex; align-items: baseline; gap: 8px; }
.ovf-sev { flex: none; font-size: 13px; }
.ovf.err .ovf-sev { color: var(--fault); }
.ovf.warn .ovf-sev { color: var(--warn); }
.ovf-title { font-size: 13px; font-weight: 600; flex: 1; min-width: 0; }
.ovf-rule {
    font-size: 10px; color: var(--text-faint); flex: none;
    padding: 1px 7px; border-radius: 20px; background: var(--panel);
    border: 1px solid var(--line);
}

/* Die konkreten Werte in Festbreite: sie werden zeichenweise gelesen und mit der
   eigenen Datei verglichen. */
.ovf-detail {
    font-size: 11.5px; color: var(--text); line-height: 1.6;
    padding: 6px 9px; border-radius: 4px; background: var(--panel);
    overflow-x: auto; white-space: pre-wrap; word-break: break-word;
}
.ovf-why { font-size: 12px; color: var(--text-dim); line-height: 1.65; }

.ovf-foot { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.ovf-std-btn {
    border: 1px solid var(--line-strong); background: var(--panel);
    color: var(--text-dim); border-radius: 20px;
    font-family: var(--font-ui); font-size: 10.5px; font-weight: 600;
    padding: 2px 10px; cursor: pointer; flex: none;
    transition: color .12s, border-color .12s;
}
.ovf-std-btn:hover { color: var(--accent); border-color: var(--accent); }
.ovf-path {
    font-size: 10px; color: var(--text-faint); flex: 1; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ovf-fix { flex: none; }

/* Das Normzitat als Zitat und nicht als Fließtext: es ist fremder Wortlaut, und man
   muss ihm ansehen, dass er nicht von uns stammt. */
.ovf-std {
    margin: 2px 0 0; padding: 9px 12px;
    border-left: 3px solid var(--accent);
    background: color-mix(in srgb, var(--accent) 7%, transparent);
    border-radius: 0 4px 4px 0;
    font-size: 11.5px; line-height: 1.7; color: var(--text-dim); font-style: italic;
}

/* Was die Korrektur tut, im Klartext – vor dem Klick lesbar. */
.ovf-effect {
    display: flex; gap: 8px; align-items: baseline;
    font-size: 11.5px; line-height: 1.6; color: var(--text-dim);
    padding: 7px 10px; border-radius: 4px;
    background: color-mix(in srgb, var(--ok) 8%, transparent);
    border: 1px solid color-mix(in srgb, var(--ok) 28%, transparent);
}
.ovf-effect-lab {
    font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .08em;
    text-transform: uppercase; color: var(--ok); flex: none; font-weight: 700;
}
.ovf-effect b { color: var(--warn); }

/* ---------- Sichern ---------- */
.odx-val-save { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }
.odx-val-copy { display: flex; gap: 8px; align-items: center; flex: 1 1 320px; min-width: 260px; }
.odx-val-copy .input { flex: 1; min-width: 0; }

@media (max-width: 760px) {
    .odx-val-save { flex-direction: column; align-items: stretch; }
    .ovt-head, .ovf { margin-left: calc(var(--d, 0) * 8px); }
}

/* ============================================================================
   Panel-Dialog: Reiter, und das Notiz-Panel mit Bildern
   ========================================================================= */

/* ---------- Reiter im Dialog ----------
   Der Dialog trug bis zu acht Abschnitte untereinander; bei einem Verlauf mit
   Grenzwerten und Ton waren das drei Bildschirmhöhen, und die Grenzwerte standen
   ganz unten, wo sie niemand vermutet. Die Leiste steht deshalb oben und klebt beim
   Rollen fest – sonst verliert man beim langen Abschnitt die Orientierung. */
/* Die Reiter sahen aus wie eine Gruppe von Schaltflächen: ein Kästchen oben,
   darunter frei stehender Inhalt - und damit war nicht zu sehen, dass dieser Inhalt
   zum markierten Reiter gehört und der Rest dahinter liegt. Jetzt setzt der Rahmen
   des Inhalts an der aktiven Lasche an: sie ist nach unten offen, die anderen sitzen
   auf der Kante. Das ist die Form, die jeder als Reiter liest.

   Sticky bleibt es: der Dialog wird lang, und Reiter, die man zum Wechseln erst
   wieder heraufscrollen muss, wechselt niemand. */
.cfg-tabs,
.ts-tabs {
    display: flex;
    gap: 3px;
    position: relative;
    z-index: 1;
    margin-bottom: 0;
    padding: 0;
    background: transparent;
    border: 0;
    border-bottom: 1px solid var(--line);
    border-radius: 0;
}

/* Im Dialog bleiben die Reiter beim Scrollen stehen und brauchen dafür einen eigenen
   Grund unter sich – sonst läuft der Inhalt sichtbar hindurch. */
.cfg-tabs {
    position: sticky;
    top: 0;
    z-index: 3;
    background: var(--panel);
}

.cfg-tabs button,
.ts-tabs button {
    position: relative;
    height: 34px;
    padding: 0 15px;

    /* Deckt die Kante unter der aktiven Lasche zu - daher kommt die Verbindung
       zwischen Reiter und Inhalt. */
    margin-bottom: -1px;
    border: 1px solid transparent;
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
    background: transparent;
    box-shadow: none;
}

.cfg-tabs button:hover:not(.active),
.ts-tabs button:hover:not(.active) {
    background: var(--panel-inset);
    border-color: var(--line);
    border-bottom-color: transparent;
}

.cfg-tabs button.active,
.ts-tabs button.active {
    background: var(--panel);
    border-color: var(--line);
    border-bottom-color: var(--panel);
    color: var(--text);
    font-weight: 600;
    box-shadow: none;
}

/* Die farbige Kante oben trägt weiter als ein Farbton: auf einem flau
   eingestellten Bildschirm ist sie das, was den aktiven Reiter ausweist. */
.cfg-tabs button.active::before,
.ts-tabs button.active::before {
    content: "";
    position: absolute;
    inset: -1px -1px auto -1px;
    height: 2px;
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
    background: var(--accent);
}

/* Der Inhalt: derselbe Rahmen, oben offen zur Lasche hin. */
.cfg-tabpanel {
    padding: 16px 16px 18px;
    border: 1px solid var(--line);
    border-top: 0;
    border-radius: 0 0 var(--radius-sm) var(--radius-sm);
    background: var(--panel);
}

/* Der Punkt sagt: unter diesem Reiter steht etwas. Ohne ihn muss man jeden Reiter
   einmal öffnen, um zu wissen, ob dort etwas eingestellt ist. */
.cfg-tab-dot {
    display: inline-block; width: 6px; height: 6px; border-radius: 50%;
    background: var(--accent); margin-left: 7px; vertical-align: middle;
}
.cfg-tabs button.active .cfg-tab-dot { background: var(--accent); }

/* Die aktive Lasche malt über die Oberkante des Panels darunter – erst dadurch geht
   sie in die Ansicht über, statt darauf zu sitzen. */
.ts-tabs { margin-bottom: -1px; }
.ts-tabs button.active { z-index: 1; }

/* Die Zeitreihenseite setzt alle Reiter klein (.ts-page .tabs). Die Laschen brauchen
   etwas mehr: sie tragen die Gliederung der ganzen Ansicht und nicht nur einen
   Schalter in einer Werkzeugleiste. */
.ts-page .ts-tabs { padding: 0; }
.ts-page .ts-tabs button { height: 30px; padding: 0 14px; font-size: 12px; }

/* ---------- Bilder im Notiz-Dialog ---------- */
.nimg-list { display: flex; flex-direction: column; gap: 10px; }
.nimg {
    display: flex; gap: 12px; align-items: flex-start;
    padding: 10px; border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--panel-inset);
}
.nimg > img {
    width: 132px; height: 92px; flex: none;
    object-fit: cover; border-radius: 4px;
    border: 1px solid var(--line-strong); background: var(--panel);
}
.nimg-side { display: flex; flex-direction: column; gap: 8px; flex: 1; min-width: 0; }
.nimg-tools { display: flex; gap: 4px; }
.nimg-tools .btn { min-width: 30px; padding: 0 8px; }
.nimg-del:hover { color: #fff; background: var(--fault); border-color: var(--fault); }

/* ---------- Notiz-Panel auf dem Board ----------
   Text oben, Bilder darunter. Beides ist optional: eine Notiz kann nur Text sein,
   nur Bilder, oder beides. */
.lp-note { display: flex; flex-direction: column; gap: 10px; }
.lp-note-text { white-space: pre-wrap; font-size: 13px; color: var(--text-dim); }
.lp-note.has-img .lp-note-text { flex: none; }

/* Die Bilder füllen die Breite der Kachel und rücken bei schmalen Panels
   untereinander. auto-fit statt fester Spaltenzahl: ein Notiz-Panel ist mal vier und
   mal acht Rasterspalten breit, und beides soll ohne eigene Einstellung passen. */
.lp-note-imgs {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: 8px; align-content: start;
}
.lp-note-fig {
    margin: 0; cursor: zoom-in; min-width: 0;
    border: 1px solid var(--line); border-radius: 5px;
    background: var(--panel-inset); overflow: hidden;
    transition: border-color .14s, transform .1s;
}
.lp-note-fig:hover { border-color: var(--accent); }
.lp-note-fig > img {
    display: block; width: 100%; height: 104px; object-fit: cover;
}
.lp-note-fig > figcaption {
    padding: 5px 8px; font-size: 10.5px; color: var(--text-faint);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Vergrößert: das Bild nimmt die ganze Kachel und wird vollständig gezeigt
   (contain statt cover) – beim Vergrößern will man den Rand sehen, nicht den
   Ausschnitt. Als Auflage im Panel und nicht über der Seite: eine Notiz steht neben
   anderen Kacheln, und ein Bild, das die halbe Seite verdeckt, nimmt genau die
   Messwerte weg, wegen derer man das Board offen hat. */
.lp-note-fig.zoom {
    position: absolute; inset: 0; z-index: 6; cursor: zoom-out;
    border-radius: 0; border: 0;
    background: var(--panel);
    display: flex; flex-direction: column;
}
.lp-note-fig.zoom > img { height: auto; flex: 1; min-height: 0; object-fit: contain; }
.lp-note-fig.zoom > figcaption {
    flex: none; white-space: normal; font-size: 11.5px; color: var(--text-dim);
    border-top: 1px solid var(--line);
}

/* Die Auflage braucht einen Bezugspunkt. Der Panelkörper ist ohnehin der Rahmen, in
   dem sie liegen soll. */
.widget > .panel > .panel-body:has(.lp-note) { position: relative; }

/* Enge Kacheln: die Bildunterschrift tritt zurück, das Bild bleibt. */
.widget.dense-y .lp-note-fig > img { height: 72px; }
.widget.dense-y .lp-note-fig > figcaption { display: none; }

/* ============================================================================
   ODX-Editor

   Vier Bereiche gleichzeitig: Baum links, Knoten in der Mitte, echtes XML rechts,
   Konsole unten. Die Breiten und die Höhe der Konsole stehen als CSS-Variablen am
   Wurzelelement und werden von js/splitpane.js verstellt – Blazor darf dazwischen
   beliebig neu zeichnen, ohne die Aufteilung anzufassen.

   Die Fläche nimmt sich die Seite: ein Editor, der mit dem Inhalt wächst, hat seine
   Konsole mal hier und mal dort, und man rollt bei jedem Blick neu.
   ========================================================================= */
.odxe {
    --odxe-c1: 340px;
    --odxe-c3: 440px;
    --odxe-console: 190px;

    display: grid;
    grid-template-rows: auto minmax(0, 1fr) 6px var(--odxe-console);
    height: calc(100vh - 210px);
    min-height: 520px;
    border: 1px solid var(--line-strong);
    border-radius: var(--radius);
    background: var(--panel);
    overflow: hidden;
}

.odxe-bar {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    padding: 9px 12px;
    border-bottom: 1px solid var(--line-strong);
    background: var(--panel-raised);
}

.odxe-main {
    display: grid;
    grid-template-columns: var(--odxe-c1) 6px minmax(0, 1fr) 6px var(--odxe-c3);
    min-height: 0;
}

/* ---------- Bereich ---------- */
.odxe-pane { display: flex; flex-direction: column; min-width: 0; min-height: 0; }
.odxe-head {
    display: flex; align-items: center; gap: 8px; flex: none;
    padding: 7px 10px;
    border-bottom: 1px solid var(--line);
    background: var(--panel-inset);
}
.odxe-title {
    font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .1em;
    text-transform: uppercase; color: var(--text-dim); flex: none;
}
.odxe-body { flex: 1; min-height: 0; overflow: auto; }
.odxe-search { height: 28px; font-size: 12px; flex: 1; min-width: 90px; }

/* ---------- Trennsteg ----------
   Schmal im Ruhezustand, deutlich unter dem Zeiger. Ein Steg, den man erst suchen
   muss, wird nicht benutzt – einer, der ständig leuchtet, zerschneidet die Fläche. */
.odxe-split {
    background: var(--line);
    cursor: col-resize;
    position: relative;
    transition: background .12s;
}
.odxe-split::after {
    content: ''; position: absolute; inset: 0 -4px;
}
.odxe-split:hover, .odxe-split.dragging { background: var(--accent); }
.odxe-split-y { cursor: row-resize; }
body.split-dragging-x { cursor: col-resize; user-select: none; }
body.split-dragging-y { cursor: row-resize; user-select: none; }

/* ---------- Baum ----------
   Echtes Markup und keine Beschriftung: wer eine ODX bearbeitet, denkt in Tags, und
   die Übersetzung in eine hübschere Form kostet bei jedem Blick eine Rückübersetzung. */
.odxe-tree { font-family: var(--font-mono); font-size: 11.5px; padding: 4px 0; }
.odxe-row {
    display: flex; align-items: center; gap: 5px;
    padding: 2px 8px 2px calc(6px + var(--d, 0) * 13px);
    cursor: pointer; white-space: nowrap;
    border-left: 2px solid transparent;
}
.odxe-row:hover { background: var(--panel-raised); }
.odxe-row.sel {
    background: var(--accent-soft);
    border-left-color: var(--accent);
}
.odxe-row.has-err { border-left-color: var(--fault); }
.odxe-row.has-warn { border-left-color: var(--warn); }

.odxe-caret {
    width: 13px; flex: none; text-align: center; color: var(--text-faint);
    border: 0; background: transparent; cursor: pointer; padding: 0;
    transition: transform .12s;
}
.odxe-caret.open { transform: rotate(90deg); color: var(--accent); }
.odxe-caret.leaf { cursor: default; opacity: .45; }

.odxe-tag { flex: none; }
.odxe-br { color: var(--text-faint); }
.odxe-el { color: var(--accent); }
.odxe-at { color: var(--text-dim); }
.odxe-av { color: var(--ok); }
.odxe-sn {
    font-family: var(--font-ui); font-size: 11.5px; font-weight: 600; color: var(--text);
    overflow: hidden; text-overflow: ellipsis; min-width: 0;
}
.odxe-text { color: var(--text-faint); overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.odxe-badge {
    font-size: 9.5px; font-weight: 700; color: #fff; background: var(--fault);
    padding: 0 6px; border-radius: 20px; flex: none; margin-left: auto;
}
.odxe-row.has-warn .odxe-badge { background: var(--warn); color: #14100a; }

.odxe-more {
    display: block; margin: 2px 0 2px calc(20px + var(--d, 0) * 13px);
    border: 0; background: transparent; cursor: pointer;
    font-family: var(--font-mono); font-size: 11px; color: var(--accent);
    padding: 2px 6px; border-radius: 4px;
}
.odxe-more:hover { background: var(--accent-soft); }

/* Treffer der Suche – als eigene Liste über dem Baum und nicht als Filter darin:
   ein gefilterter Baum verliert seine Struktur, und genau die braucht man, um einen
   Treffer einzuordnen. */
.odxe-hits { display: flex; flex-direction: column; gap: 1px; padding: 4px; }
.odxe-hit {
    display: flex; align-items: baseline; gap: 8px; width: 100%;
    padding: 4px 8px; border: 0; border-radius: 4px; cursor: pointer;
    background: transparent; color: var(--text); text-align: left;
    font-family: var(--font-mono); font-size: 11.5px;
}
.odxe-hit:hover { background: var(--panel-raised); }
.odxe-hit.sel { background: var(--accent-soft); }
.odxe-hit-el { color: var(--accent); flex: none; }
.odxe-hit-name {
    font-family: var(--font-ui); color: var(--text-dim);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}

/* ---------- Knoten ---------- */
.odxe-detail { padding: 12px; display: flex; flex-direction: column; gap: 16px; }

.odxe-crumbs { display: flex; flex-wrap: wrap; gap: 2px; align-items: center; }
.odxe-crumb {
    border: 0; background: transparent; cursor: pointer; padding: 2px 6px;
    border-radius: 4px; font-family: var(--font-mono); font-size: 10.5px;
    color: var(--text-faint);
}
.odxe-crumb:hover { background: var(--panel-raised); color: var(--text); }
.odxe-crumb.sel { color: var(--accent); background: var(--accent-soft); }
.odxe-crumbs .odxe-crumb + .odxe-crumb::before {
    content: '›'; margin-right: 6px; color: var(--line-strong);
}

.odxe-doc {
    margin: 0; padding: 8px 11px; border-radius: var(--radius-sm);
    background: var(--panel-inset); border-left: 3px solid var(--accent);
    font-size: 11.5px; line-height: 1.65; color: var(--text-dim);
}

.odxe-sec { display: flex; flex-direction: column; gap: 8px; }

.odxe-field { display: flex; flex-direction: column; gap: 4px; }
.odxe-field > label {
    display: flex; align-items: baseline; gap: 6px;
    font-size: 11.5px; color: var(--text-dim); font-family: var(--font-mono);
}
/* Ein leeres Pflichtfeld ist der häufigste Grund, warum ein Dokument die Prüfung
   nicht besteht – es wird markiert, bevor die Prüfung es meldet. */
.odxe-field.missing > label { color: var(--fault); }
.odxe-field.missing .input,
.odxe-field.missing .select { border-color: var(--fault); }
.odxe-field.unknown > label { color: var(--warn); }
.odxe-req { color: var(--fault); font-weight: 700; }
.odxe-type { margin-left: auto; font-size: 10px; color: var(--text-faint); }
.odxe-note { font-size: 10.5px; color: var(--text-faint); }

.odxe-add { display: flex; gap: 8px; }
.odxe-add .select { flex: 1; min-width: 0; }

.odxe-kids { display: flex; flex-direction: column; gap: 2px; }
.odxe-kid {
    display: flex; align-items: center; gap: 4px;
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--panel-inset); padding: 3px 4px 3px 8px;
}
.odxe-kid-go {
    display: flex; align-items: baseline; gap: 8px; flex: 1; min-width: 0;
    border: 0; background: transparent; cursor: pointer; text-align: left;
    color: var(--text); font-size: 12px; padding: 3px 0;
}
.odxe-kid-go:hover .odxe-el { text-decoration: underline; }
.odxe-kid .btn { min-width: 26px; padding: 0 6px; height: 24px; }
.odxe-del:hover { color: #fff !important; background: var(--fault); border-color: var(--fault); }

.odxe-hint {
    padding: 10px 12px; font-size: 11.5px; color: var(--text-faint);
    line-height: 1.6;
}
.odxe-hint.warn {
    color: var(--warn); background: color-mix(in srgb, var(--warn) 8%, transparent);
    border-radius: var(--radius-sm); padding: 8px 11px;
}

/* ---------- XML ---------- */
.odxe-xml { background: var(--panel-inset); }
.odxe-code { font-family: var(--font-mono); font-size: 11.5px; padding: 6px 0; }
.odxe-code-line { display: flex; gap: 10px; padding: 0 10px; }
.odxe-code-line:hover { background: var(--panel-raised); }
.odxe-code-no {
    flex: none; width: 34px; text-align: right; color: var(--text-faint);
    font-size: 10.5px; user-select: none;
}
.odxe-code-text { white-space: pre; color: var(--text-dim); }

.odxe-raw { display: flex; flex-direction: column; gap: 8px; padding: 10px; height: 100%; }
.odxe-raw-text { flex: 1; min-height: 220px; font-size: 11.5px; line-height: 1.55; resize: none; }

/* ---------- Konsole ---------- */
.odxe-console { border-top: 1px solid var(--line-strong); }
.odxe-console-body { font-size: 12px; }
.odxe-con-row {
    display: flex; align-items: baseline; gap: 10px;
    padding: 5px 12px;
    border-bottom: 1px solid var(--line);
}
.odxe-con-row.jump { cursor: pointer; }
.odxe-con-row.jump:hover { background: var(--panel-raised); }
.odxe-con-kind {
    flex: none; width: 74px;
    font-family: var(--font-mono); font-size: 9px; font-weight: 700;
    letter-spacing: .08em; padding-top: 2px;
}
.odxe-con-row.error .odxe-con-kind { color: var(--fault); }
.odxe-con-row.warn  .odxe-con-kind { color: var(--warn); }
.odxe-con-row.edit  .odxe-con-kind { color: var(--accent); }
.odxe-con-row.info  .odxe-con-kind { color: var(--ok); }
.odxe-con-text { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
.odxe-con-title { color: var(--text); }
.odxe-con-detail {
    font-size: 10.5px; color: var(--text-faint);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.odxe-con-path {
    flex: none; max-width: 38%; font-size: 10px; color: var(--text-faint);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; direction: rtl;
}

@media (max-width: 1100px) {
    /* Schmal passen drei Spalten nicht nebeneinander. Baum und Knoten bleiben, das
       XML rückt darunter – es ist die Ansicht, die man am seltensten braucht, solange
       die Mitte schemageführt arbeitet. */
    .odxe { height: auto; min-height: 0; }
    .odxe-main { grid-template-columns: minmax(0, 1fr); }
    .odxe-split { display: none; }
    .odxe-pane { max-height: 60vh; }
    .odxe-tree-pane, .odxe-detail-pane, .odxe-xml-pane { border-bottom: 1px solid var(--line-strong); }
}

/* ============================================================================
   Zyklische Dauermessung – Skripteditor

   Drei Bereiche: links dauerhaft die ODX mit ihren Diensten, rechts die Maske oder
   das erzeugte Python, unten die Prüfung. Der Dienstkatalog steht bewusst nicht in
   einem Dialog: welche Dienste es gibt und wie ihre Parameter heissen, ist beim
   Einrichten die ganze Zeit die Frage – nicht einmal am Anfang.
   ========================================================================= */
.cme {
    display: grid;
    grid-template-columns: 360px minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr) auto;
    gap: 14px;
    height: calc(100vh - 210px);
    min-height: 560px;
}
.cme-services { min-height: 0; grid-row: 1; }
.cme-main { min-height: 0; overflow: auto; grid-row: 1; }
.cme-check { grid-column: 1 / -1; grid-row: 2; }

/* Der Name steht als Überschrift und ist zugleich das Eingabefeld – ein eigenes
   Feld daneben wäre eine zweite Stelle für dieselbe Angabe. */
.cme-name {
    font-family: var(--font-ui); font-size: 20px; font-weight: 600;
    border-color: transparent; background: transparent; padding: 2px 6px;
    height: auto; width: 100%; max-width: 460px;
}
.cme-name:hover { border-color: var(--line); }
.cme-name:focus { border-color: var(--accent); background: var(--panel-inset); }

.cme-tab-dot {
    display: inline-block; width: 6px; height: 6px; border-radius: 50%;
    background: var(--warn); margin-left: 7px; vertical-align: middle;
}

/* ---------- gewählte Dienste ----------
   Die Reihenfolge ist die Lesereihenfolge im Zyklus. Deshalb nummeriert und
   verschiebbar und nicht bloss eine Menge. */
.cme-picked { display: flex; flex-direction: column; gap: 3px; }
.cme-pick {
    display: flex; align-items: center; gap: 6px;
    padding: 4px 5px 4px 8px;
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--accent-soft);
}
.cme-pick-n {
    flex: none; width: 18px; text-align: right;
    font-size: 10px; color: var(--accent); font-weight: 700;
}
.cme-pick-main { display: flex; flex-direction: column; gap: 1px; flex: 1; min-width: 0; }
.cme-pick-name {
    font-size: 12px; font-weight: 600;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cme-pick-sub {
    font-size: 10px; color: var(--text-faint);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cme-pick .btn { min-width: 24px; padding: 0 5px; height: 22px; }
.cme-del:hover { color: #fff !important; background: var(--fault); border-color: var(--fault); }

/* ---------- Dienstkatalog ---------- */
.cme-catalog { display: flex; flex-direction: column; gap: 1px; }
.cme-svc {
    display: flex; align-items: center; gap: 8px; width: 100%;
    padding: 5px 7px; border: 0; border-radius: 4px; cursor: pointer;
    background: transparent; color: var(--text); text-align: left;
}
.cme-svc:hover { background: var(--panel-raised); }
.cme-svc.on { background: var(--accent-soft); }
.cme-box {
    width: 14px; height: 14px; flex: none; border-radius: 3px;
    border: 1px solid var(--line-strong); background: var(--panel);
    display: grid; place-items: center;
    font-size: 10px; line-height: 1; color: var(--on-accent);
}
.cme-svc.on .cme-box { background: var(--accent); border-color: var(--accent); }
.cme-svc-main { display: flex; align-items: baseline; gap: 8px; min-width: 0; flex: 1; }
.cme-svc-name {
    font-family: var(--font-mono); font-size: 11.5px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cme-svc.on .cme-svc-name { color: var(--accent); font-weight: 600; }
.cme-svc-sem {
    font-size: 9.5px; color: var(--text-faint); flex: none;
    padding: 1px 6px; border-radius: 20px; background: var(--panel-inset);
}

/* ---------- Maske ---------- */
.cme-form { display: flex; flex-direction: column; gap: 14px; }
.cme-note { font-size: 11.5px; line-height: 1.7; margin: 0 0 12px; }

/* Was selten gebraucht wird, steht zugeklappt da: Gerätepfade und GPS-Schlüssel
   ändert man einmal je Geräteflotte und danach nie wieder. */
.cme-more { margin-top: 12px; }
.cme-more > summary {
    cursor: pointer; font-family: var(--font-mono); font-size: 10px;
    letter-spacing: .1em; text-transform: uppercase; color: var(--text-dim);
    padding: 6px 0;
}
.cme-more > summary:hover { color: var(--accent); }

/* ---------- Skript ---------- */
.cme-editor { height: calc(100vh - 340px); min-height: 420px; }
.cme-expert-note { margin: 0 0 10px; line-height: 1.65; }

/* ---------- Prüfung ----------
   Immer sichtbar und nicht hinter einem Knopf: die Probleme entstehen beim
   Einstellen, und sie sollen dort auffallen und nicht erst beim Herunterladen. */
.cme-check {
    border: 1px solid var(--line-strong); border-radius: var(--radius);
    background: var(--panel); overflow: hidden;
}
.cme-check-head {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    padding: 7px 12px; border-bottom: 1px solid var(--line);
    background: var(--panel-inset);
}
.cme-check-body { max-height: 148px; overflow: auto; padding: 8px 12px; }
.cme-msg {
    display: flex; gap: 9px; align-items: baseline;
    font-size: 12px; line-height: 1.6; padding: 3px 0;
}
.cme-msg.err > span:first-child { color: var(--fault); }
.cme-msg.warn > span:first-child { color: var(--warn); }
.cme-msg.ok > span:first-child { color: var(--ok); }
.cme-msg.err > span:last-child { color: var(--text); }
.cme-msg.warn > span:last-child,
.cme-msg.ok > span:last-child { color: var(--text-dim); }

@media (max-width: 1200px) {
    /* Schmal steht die ODX über der Maske statt daneben. Sie bleibt oben, weil die
       Dienstauswahl der erste Schritt ist. */
    .cme { grid-template-columns: minmax(0, 1fr); height: auto; min-height: 0; }
    .cme-services { max-height: 420px; }
    .cme-main { grid-row: auto; }
    .cme-check { grid-column: 1; grid-row: auto; }
    .cme-editor { height: 60vh; }
}

/* ============================================================================
   Dauermessung – Nachbesserungen

   Drei Dinge: die Dienstliste rollte nicht (der Panelkörper steht auf
   overflow:hidden, also muss die Liste selbst rollen), die Fläche war zu locker
   für eine Maske, die man im Ganzen überblicken will, und die selten gebrauchten
   Abschnitte standen gleichberechtigt zwischen den täglichen.
   ========================================================================= */

/* ---------- Scrollen ----------
   .panel-fill gibt dem Körper flex:1 und overflow:hidden – wer darin rollen will,
   muss es selbst tun. Die gewählten Dienste bekommen eine eigene Obergrenze: sie
   stehen oben und dürfen den Katalog nicht aus dem Bild schieben. */
.cme-services > .panel-fill > .panel-body { gap: 8px; }
.cme-picked { flex: none; max-height: 38vh; overflow-y: auto; }
.cme-catalog { flex: 1; min-height: 0; overflow-y: auto; margin: -2px; padding: 2px; }

/* ---------- Kompakter ----------
   Die Maske wird beim Einrichten ständig überflogen. Jede Zeile Luft, die dabei
   nichts trennt, kostet einen Blick nach unten. */
.cme { gap: 10px; height: calc(100vh - 186px); }
.cme-form { gap: 10px; }
.cme-form .panel-body { padding: 12px 14px; }
.cme-form .field > label { font-size: 11px; }
.cme-form .input, .cme-form .select { height: 32px; font-size: 12.5px; }
.cme-form .lp-grid { gap: 10px; }
.cme-note { font-size: 11px; line-height: 1.6; margin: 0 0 10px; }
.cme-check-body { max-height: 104px; padding: 6px 12px; }
.cme-msg { font-size: 11.5px; padding: 2px 0; }
.cme-services > .panel-fill > .panel-head { padding: 10px 12px; }

/* Der erklärende Vorspann eines stillen Reiters: er beantwortet die Frage „muss ich
   hier überhaupt etwas tun?" – und die ist der Grund, warum diese Reiter hinten
   stehen. */
.cme-lead {
    margin: 0; padding: 9px 12px;
    border-left: 3px solid var(--accent);
    background: var(--panel-inset); border-radius: 0 4px 4px 0;
    font-size: 11.5px; line-height: 1.65; color: var(--text-dim);
}

/* ---------- Reiter: täglich vorn, selten hinten ---------- */
.cme-tabs { flex-wrap: wrap; }
.cme-tab-sep {
    width: 1px; align-self: stretch; margin: 4px 6px;
    background: var(--line-strong); flex: none;
}
/* Leiser, nicht versteckt: wer eine Adresse ändern muss, findet sie – wer eine
   Messung einrichtet, stolpert nicht zuerst darüber. */
.cme-tabs button.cme-tab-adv { font-weight: 500; opacity: .72; }
.cme-tabs button.cme-tab-adv:hover { opacity: 1; }
.cme-tabs button.cme-tab-adv.active { opacity: 1; }

/* ---------- Messwerte in der Dienstzeile ----------
   Gewählt wird ein Dienst wegen der Werte, die er zurückgibt. „ReadBatteryStatus"
   allein sagt darüber nichts – die Namen stehen deshalb in der Zeile und nicht
   hinter einem Klick. */
.cme-svc { align-items: flex-start; padding: 6px 7px; }
.cme-box { margin-top: 2px; }
.cme-svc-main { flex-direction: column; align-items: stretch; gap: 3px; }
.cme-svc-head { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.cme-svc-params { display: flex; flex-wrap: wrap; gap: 3px; }
.cme-param {
    font-family: var(--font-mono); font-size: 9.5px;
    padding: 1px 6px; border-radius: 20px;
    background: var(--panel-inset); color: var(--text-dim);
    max-width: 150px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cme-param i { color: var(--text-faint); font-style: normal; }
.cme-param.more { background: transparent; color: var(--text-faint); cursor: help; }
.cme-param.none { background: transparent; color: var(--text-faint); font-style: italic; }
.cme-svc.on .cme-param { background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent); }
.cme-svc.on .cme-param i { color: color-mix(in srgb, var(--accent) 70%, var(--text-faint)); }

/* ---------- Simulation ---------- */
.cme-sim { display: flex; flex-direction: column; gap: 10px; height: 100%; min-height: 0; }
.cme-sim-split {
    display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 10px; flex: 1; min-height: 0;
}
.cme-sim-split > .panel-fill { min-height: 260px; }

.cme-sim-stats {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(118px, 1fr)); gap: 8px;
}
.cme-stat {
    display: flex; flex-direction: column; gap: 2px;
    padding: 8px 11px; border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--panel-inset);
}
.cme-stat-n {
    font-family: var(--font-mono); font-size: 18px; font-weight: 700;
    line-height: 1; color: var(--accent);
}
.cme-stat-t { font-size: 10.5px; color: var(--text-dim); }

.cme-sig-list { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 1px; }
.cme-sig {
    display: grid; grid-template-columns: minmax(0, 1.5fr) auto 42px minmax(0, 1fr);
    gap: 8px; align-items: baseline;
    padding: 4px 7px; border-radius: 4px;
}
.cme-sig:nth-child(odd) { background: var(--panel-inset); }
.cme-sig-name { font-size: 11.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cme-sig-val {
    font-size: 12.5px; font-weight: 700; color: var(--text); text-align: right;
    font-variant-numeric: tabular-nums;
}
.cme-sig-unit { font-size: 10.5px; color: var(--text-faint); }
.cme-sig-svc {
    font-size: 10px; color: var(--text-faint);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.cme-json {
    margin: 0; flex: 1; min-height: 0; overflow: auto;
    font-family: var(--font-mono); font-size: 10.5px; line-height: 1.55;
    color: var(--text-dim); white-space: pre;
}

@media (max-width: 1200px) {
    .cme-sim-split { grid-template-columns: minmax(0, 1fr); }
    .cme-picked { max-height: 26vh; }
}

/* ============================================================================
   Dauermessung – Steuergerät direkt hier laden, Name als Eingabefeld erkennbar
   ========================================================================= */

/* ---------- Der Name ----------
   Er war ein Eingabefeld und sah nicht danach aus: Überschriftenschrift, kein
   Rahmen, kein Zeiger. Der Stift und die gepunktete Grundlinie sagen es, ohne
   aus der Überschrift ein Formularfeld zu machen – beides tritt zurück, sobald
   man hineinklickt. */
.cme-name-wrap {
    display: inline-flex; align-items: center; gap: 8px;
    max-width: 520px; cursor: text;
    border-bottom: 1px dashed var(--line-strong);
    transition: border-color .14s;
}
.cme-name-wrap:hover { border-bottom-color: var(--accent); }
.cme-name-wrap:focus-within { border-bottom-color: transparent; }
.cme-name-pen {
    width: 15px; height: 15px; flex: none; color: var(--text-faint);
    transition: color .14s, opacity .14s;
}
.cme-name-wrap:hover .cme-name-pen { color: var(--accent); }
.cme-name-wrap:focus-within .cme-name-pen { opacity: 0; }
.cme-name::placeholder { color: var(--text-faint); font-weight: 400; }

/* ---------- Steuergeräte-Auswahl ----------
   Sie steht zweimal: groß auf der leeren Seite und klein im Kopf der
   Dienstspalte. Dieselben Regeln, nur andere Höhe – deshalb nichts Festes hier. */
.cme-picker { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.cme-picker-list {
    display: flex; flex-direction: column; gap: 2px;
    max-height: 46vh; overflow-y: auto;
    margin: -2px; padding: 2px;
}
.cme-switch .cme-picker-list { max-height: 240px; }
.cme-switch {
    padding: 8px; border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
    background: var(--panel-inset);
}

.cme-ecu {
    display: flex; align-items: center; gap: 8px; width: 100%;
    padding: 7px 9px; border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--panel-raised); color: var(--text); cursor: pointer; text-align: left;
    transition: border-color .12s, background .12s;
}
.cme-ecu:hover { border-color: var(--accent); background: var(--accent-soft); }
.cme-ecu-main { display: flex; flex-direction: column; gap: 1px; flex: 1; min-width: 0; }
.cme-ecu-name {
    font-size: 12.5px; font-weight: 600;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cme-ecu-file {
    font-size: 10px; color: var(--text-faint);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cme-ecu-kind {
    flex: none; font-family: var(--font-mono); font-size: 9px; font-weight: 700;
    letter-spacing: .05em; text-transform: uppercase;
    padding: 2px 7px; border-radius: 20px;
    background: var(--panel-inset); color: var(--text-dim);
}
/* „geladen" ist die nützlichste Auskunft der Zeile: eine geladene Datei öffnet
   ohne Wartezeit, eine 40-MB-PDX braucht Sekunden. */
.cme-ecu-kind.loaded { background: var(--accent-soft); color: var(--accent); }

.cme-file { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }

/* ============================================================================
   Dauermessung – laufender Probelauf und Varianten
   ========================================================================= */

/* ---------- Laufanzeige ----------
   Ein Knopf, der von „starten" auf „läuft" wechselt, ist kein Beleg dafür, dass
   etwas passiert. Die Uhr ist einer. */
.cme-run {
    display: inline-flex; align-items: center; gap: 7px; flex: none;
    font-family: var(--font-mono); font-size: 11.5px; font-weight: 700;
    color: var(--ok); padding: 0 4px;
    font-variant-numeric: tabular-nums;
}
.cme-run-dot {
    width: 7px; height: 7px; border-radius: 50%; background: var(--ok);
    animation: cme-pulse 1.1s ease-in-out infinite;
}
@keyframes cme-pulse {
    0%, 100% { opacity: 1; transform: scale(1); }
    50% { opacity: .35; transform: scale(.78); }
}
@media (prefers-reduced-motion: reduce) { .cme-run-dot { animation: none; } }

/* ---------- Abbruchbedingung im Probelauf ----------
   Der eigentliche Grund für den Probelauf: ob eine Bedingung greift, lässt sich
   schlecht ausrechnen – und am Fahrzeug merkt man es erst, wenn die Messung nicht
   endet oder nach zehn Sekunden schon. */
.cme-stop {
    margin-top: 10px; padding: 9px 11px;
    border: 1px solid var(--line); border-left: 3px solid var(--line-strong);
    border-radius: var(--radius-sm); background: var(--panel-inset);
    transition: border-color .18s;
}
.cme-stop.holding { border-left-color: var(--warn); }
.cme-stop.done { border-left-color: var(--ok); background: color-mix(in srgb, var(--ok) 9%, transparent); }

.cme-stop-head { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; }
.cme-stop-lab {
    font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .09em;
    text-transform: uppercase; color: var(--text-dim); flex: none;
}
.cme-stop-rule { font-size: 11px; color: var(--text); }
.cme-stop-state { font-size: 11px; color: var(--text-dim); margin-left: auto; }
.cme-stop.holding .cme-stop-state { color: var(--warn); }
.cme-stop.done .cme-stop-state { color: var(--ok); font-weight: 600; }

.cme-stop-bar {
    height: 4px; margin-top: 7px; border-radius: 3px;
    background: var(--panel); overflow: hidden;
}
.cme-stop-bar > span {
    display: block; height: 100%; background: var(--warn);
    transition: width .3s linear;
}
.cme-stop.done .cme-stop-bar > span { background: var(--ok); }

/* ---------- Messwertzeile mit Verlauf ----------
   Name · Verlauf · Wert · Richtung · Einheit · Dienst. Der Verlauf sagt in einer
   Zeile, was eine Zahl allein nicht sagt: ob sich der Wert überhaupt bewegt. */
.cme-sig {
    grid-template-columns: minmax(0, 1.3fr) 58px auto 14px 40px minmax(0, 0.9fr);
}
.cme-sig-spark { display: flex; align-items: center; min-width: 0; opacity: .8; }
.cme-sig-trend { font-size: 8px; text-align: center; }
.cme-sig-trend.up { color: var(--ok); }
.cme-sig-trend.down { color: var(--fault); }
.cme-sig-trend.flat, .cme-sig-trend.none { color: var(--text-faint); }
/* Ein Wert, der sich nicht bewegen kann, soll auch nicht so aussehen, als würde er
   es nur gerade nicht tun. */
.cme-sig.static .cme-sig-val { color: var(--text-dim); font-weight: 500; }

/* ---------- Varianten ---------- */
.cme-variant { height: 26px; font-size: 11px; padding: 0 8px; flex: 1; min-width: 0; }

/* Das Zeichen an einem Dienst, der nicht in allen Varianten gleich aussieht. Klein,
   aber an der Stelle, an der man den Dienst auswählt – dort ist die Frage noch
   billig zu beantworten. */
.cme-var-flag {
    display: inline-block; margin-left: 6px; cursor: help;
    font-size: 10px; color: var(--warn);
}

/* ============================================================================
   Flottenbaum · Dashboard-Widget
   ========================================================================= */

.fleet-tree { display: flex; flex-direction: column; height: 100%; min-height: 0; gap: 8px; }

/* ---------- Kopf: Zählung und Filter ----------
   Die Zahl steht vor allem anderen. „14 von 61 online" ist die Auskunft, für die
   man die Kachel überhaupt aufs Board legt – sie darf nicht erst nach dem
   Aufklappen einer Flotte erscheinen. */
.ft-head {
    display: flex; align-items: center; justify-content: space-between;
    gap: 10px; flex-wrap: wrap; flex: none;
}
.ft-tally { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.ft-tally-n {
    font-family: var(--font-ui); font-size: 20px; font-weight: 700;
    color: var(--ok); line-height: 1; font-variant-numeric: tabular-nums;
}
.ft-tally-t { font-size: 11px; color: var(--text-dim); }
.ft-tally-gps {
    display: inline-flex; align-items: center; gap: 3px; margin-left: 6px;
    font-size: 11px; color: var(--s-cyan); font-variant-numeric: tabular-nums;
}
.ft-tally-gps svg { width: 11px; height: 11px; }

.ft-filters { display: flex; gap: 4px; flex: none; }
.ft-fchip {
    height: 22px; padding: 0 9px; border-radius: 11px;
    border: 1px solid var(--line); background: var(--panel-inset);
    color: var(--text-dim); font-size: 10.5px; font-weight: 600;
    cursor: pointer; transition: border-color .15s, color .15s, background .15s;
}
.ft-fchip:hover { border-color: var(--line-strong); color: var(--text); }
.ft-fchip.on {
    border-color: var(--accent); background: var(--accent-soft); color: var(--text);
}

/* ---------- Suche ---------- */
.ft-search { position: relative; flex: none; }
.ft-search .input { width: 100%; height: 26px; font-size: 11.5px; padding-right: 26px; }
.ft-clear {
    position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
    width: 18px; height: 18px; border: 0; background: none; cursor: pointer;
    color: var(--text-faint); font-size: 11px; line-height: 1;
}
.ft-clear:hover { color: var(--text); }

/* ---------- Der Baum ----------
   Eigener Scrollbereich: die Kachel hat eine feste Höhe aus dem Raster, und ein
   Bestand von sechzig Fahrzeugen passt in keine davon. Ohne das wächst das
   Widget über seine Zelle hinaus und schiebt das halbe Dashboard weg. */
.ft-body { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; margin: 0 -4px; padding: 0 4px; }

.ft-fleet { border-bottom: 1px solid var(--line); }
.ft-fleet:last-child { border-bottom: 0; }

.ft-fleet-head {
    display: flex; align-items: center; gap: 7px; width: 100%;
    padding: 7px 2px; border: 0; background: none; cursor: pointer; text-align: left;
    color: var(--text);
}
.ft-fleet-head:hover { color: var(--accent-hover); }
.ft-caret { color: var(--text-faint); font-size: 9px; width: 9px; flex: none; }
.ft-fleet-name {
    font-size: 12px; font-weight: 600; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Der Balken sagt in der zugeklappten Zeile, was sonst erst das Aufklappen sagt:
   wie viel dieser Flotte gerade lebt. */
.ft-bar {
    flex: 1; min-width: 24px; height: 3px; border-radius: 2px;
    background: var(--line); overflow: hidden; margin-left: auto;
}
.ft-bar > span { display: block; height: 100%; background: var(--ok); transition: width .4s; }
.ft-fleet-count {
    font-family: var(--font-mono); font-size: 10px; color: var(--text-dim);
    flex: none; font-variant-numeric: tabular-nums;
}

/* ---------- Fahrzeugzeile ---------- */
.ft-cars { padding: 0 0 6px 16px; display: flex; flex-direction: column; gap: 1px; }

.ft-car {
    display: flex; align-items: center; gap: 7px;
    padding: 4px 6px; border-radius: var(--radius-sm);
    border-left: 2px solid transparent;
    transition: background .15s;
}
.ft-car:hover { background: var(--panel-inset); }

/* Online hebt sich ab, ohne die Zeile einzufärben: eine gefärbte Zeile in einer
   Liste von sechzig Zeilen ist Rauschen, eine Kante ist eine Aussage. */
.ft-car.online { border-left-color: var(--ok); background: color-mix(in srgb, var(--ok) 6%, transparent); }
.ft-car.online .ft-car-name { color: var(--text); font-weight: 600; }
.ft-car:not(.online) .ft-car-name { color: var(--text-dim); }

.ft-led { width: 6px; height: 6px; border-radius: 50%; flex: none; background: var(--stale); }
.ft-led.on {
    background: var(--ok);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--ok) 22%, transparent);
    animation: ft-pulse 2.4s ease-in-out infinite;
}
.ft-led.idle { background: var(--stale); }
.ft-led.off { background: transparent; border: 1px solid var(--line-strong); }
@keyframes ft-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }
@media (prefers-reduced-motion: reduce) { .ft-led.on { animation: none; } }

.ft-car-main { display: flex; flex-direction: column; gap: 0; min-width: 0; flex: 1; }
.ft-car-name {
    font-size: 11.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ft-car-sub { font-size: 9.5px; color: var(--text-faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.ft-ico {
    width: 20px; height: 20px; flex: none; display: inline-flex;
    align-items: center; justify-content: center;
    border: 0; background: none; cursor: pointer; border-radius: 4px;
    color: var(--text-faint);
}
.ft-ico svg { width: 12px; height: 12px; }
.ft-ico:hover { color: var(--text); background: var(--panel-raised); }
/* Position bekannt: dieselbe Farbe wie die Zählung oben, damit beides als eine
   Aussage gelesen wird. */
.ft-car.gps .ft-ico.gps { color: var(--s-cyan); }

.ft-err {
    flex: none; min-width: 16px; height: 16px; padding: 0 4px;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 8px; background: color-mix(in srgb, var(--fault) 18%, transparent);
    color: var(--fault); font-size: 9.5px; font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.ft-age {
    flex: none; width: 40px; text-align: right;
    font-family: var(--font-mono); font-size: 9.5px; color: var(--text-faint);
    font-variant-numeric: tabular-nums;
}
.ft-car.online .ft-age { color: var(--ok); }

/* ============================================================================
   Dashboard · freies Raster
   ========================================================================= */

/* Griff und Platz im Bearbeiten-Modus. Der Griff sagt, dass man greifen darf;
   gezogen werden kann an jeder freien Stelle der Kachel. Der Platz steht daneben,
   weil bei freier Anordnung „wo" die Angabe ist, die man beim Ausrichten zweier
   Kacheln braucht – zwei auf derselben Spalte sind bündig, und das sieht man an
   der Zahl schneller als am Pixel. */
.w-size { display: inline-flex; align-items: center; gap: 5px; }
.w-grip { color: var(--text-faint); font-size: 11px; cursor: grab; line-height: 1; }
.widget.moving .w-grip { cursor: grabbing; }
.w-pos {
    padding: 0 4px; border-radius: 3px; background: var(--panel-inset);
    color: var(--text-faint); font-size: 9px; font-variant-numeric: tabular-nums;
}

/* ============================================================================
   Version in der Fußzeile
   ========================================================================= */

/* Die Nummer ist anklickbar (sie führt zu den Release Notes) und soll auch danach
   aussehen – aber leise: sie steht unter der Navigation und ist keine Aktion,
   sondern eine Auskunft. */
.nav-foot { cursor: pointer; }
.nav-ver { color: var(--text-dim); border-bottom: 1px dotted var(--line-strong); }
.nav-foot:hover .nav-ver { color: var(--accent-hover); border-bottom-color: var(--accent); }

/* ============================================================================
   Fahrtenbuch · Plausibilität
   ========================================================================= */

/* ---------- Prüfknopf in der Kopfzeile ----------
   Er steht dort, wo vorher der Reiter stand, sieht aber nicht aus wie eine zweite
   Ansicht: die Prüfung ist eine Frage an das Buch, nicht eine andere Sicht darauf. */
.fb-check-btn { display: inline-flex; align-items: center; gap: 7px; }
.fb-badge.ok { background: color-mix(in srgb, var(--ok) 14%, transparent); color: var(--ok); }
.btn-warn {
    border-color: color-mix(in srgb, var(--warn) 45%, var(--line));
    background: color-mix(in srgb, var(--warn) 10%, transparent);
    color: var(--text);
}
.btn-warn:hover { border-color: var(--warn); background: color-mix(in srgb, var(--warn) 16%, transparent); }

/* ---------- Zeile mit Befund ----------
   Die Unplausibilität färbt kräftiger als die Unvollständigkeit: eine fehlende
   Angabe sieht man am leeren Feld, eine widersprüchliche sieht aus wie eine
   gültige Fahrt. */
.fb-table tbody tr.fb-implausible > td:first-child { box-shadow: inset 2px 0 0 var(--fault); }
.fb-table tbody tr.fb-implausible { background: color-mix(in srgb, var(--fault) 6%, transparent); }
.fb-table tbody tr.fb-suspicious > td:first-child { box-shadow: inset 2px 0 0 var(--s-amber); }

.fb-flaw-dot {
    display: inline-flex; align-items: center; justify-content: center;
    width: 13px; height: 13px; margin-left: 4px; border-radius: 50%;
    background: color-mix(in srgb, var(--warn) 22%, transparent);
    color: var(--warn); font-size: 9px; font-weight: 700; cursor: help;
    vertical-align: middle;
}
.fb-flaw-dot.bad { background: color-mix(in srgb, var(--fault) 20%, transparent); color: var(--fault); }

/* ---------- Befund im Prüfdialog ----------
   Drei Spalten statt vier: Zeitpunkt, Befund, Handlung. Der Befund selbst ist
   mehrzeilig – er nennt, was nicht stimmt, womit es belegt ist und was zu tun ist. */
.fb-find.fb-find-wide {
    grid-template-columns: auto 1fr auto;
    align-items: start;
    padding: 8px 10px;
}
.fb-find.fb-find-wide.bad {
    border-left-color: var(--fault);
    background: color-mix(in srgb, var(--fault) 6%, transparent);
}
.fb-find-body { display: flex; flex-direction: column; gap: 6px; min-width: 0; }

.fb-flaw { display: flex; align-items: flex-start; gap: 8px; }
.fb-flaw > div { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.fb-flaw strong { font-size: 12px; }
/* Der Beleg in Monospace: es sind Zahlen, und Zahlen vergleicht man spaltenweise. */
.fb-flaw-detail { font-size: 11px; color: var(--text); }
.fb-flaw-fix { font-size: 11px; color: var(--text-dim); line-height: 1.5; }

.fb-find-act { display: flex; gap: 4px; flex: none; }
.fb-act-void:hover { color: var(--fault); border-color: color-mix(in srgb, var(--fault) 40%, var(--line)); }

/* ---------- Schwellen der Erkennung ---------- */
.fb-detect-opts { display: flex; gap: 14px; flex-wrap: wrap; }
.fb-detect-opts .field { gap: 4px; }
.fb-detect-opts label { font-size: 11px; color: var(--text-dim); cursor: help; }

/* ============================================================================
   Empfängerliste der Board-Alarmmail
   Adressen stehen als einzelne Marken und nicht als eine Zeile Freitext: ein
   Verteiler wächst über die Zeit, und in einer Zeile mit acht Adressen sieht
   niemand mehr, ob die eigene noch dabei ist. Jede lässt sich einzeln entfernen.
   ========================================================================= */
.mail-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    align-items: center;
    min-height: 26px;
    margin-bottom: 2px;
}

/* Die Adresse selbst in Fließschrift: die Chip-Grundform steht in Monospace, und
   ein Klammeraffe in Consolas neben normalem Text liest sich wie ein Platzhalter. */
.mail-chip { padding-right: 4px; }
.mail-chip-text { font-family: var(--font-body); font-size: 11.5px; letter-spacing: 0; }

.mail-chip-x {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: inherit;
    opacity: 0.65;
    font-size: 13px;
    line-height: 1;
    cursor: pointer;
}

.mail-chip-x:hover {
    opacity: 1;
    background: color-mix(in srgb, var(--fault) 22%, transparent);
    color: var(--fault);
}

/* ============================================================================
   Angeheftete Messwerte in der Kopfzeile
   Der Streifen steht zwischen Seitentitel und Bereichswahl und ist auf jeder
   Seite derselbe. Er nimmt sich den Platz, der übrig ist, und gibt ihn wieder
   her, sobald nichts angeheftet ist – eine leere Fläche in der Kopfzeile
   erklärt sich niemandem.
   ========================================================================= */
.pinstrip {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 1 1 auto;
    min-width: 0;
    overflow-x: auto;
    overflow-y: hidden;
    padding: 0 4px;
    scrollbar-width: none;
}

.pinstrip::-webkit-scrollbar { display: none; }

/* Die Kachel: Titel klein darüber, der Wert trägt die Zeile. Zwei Zeilen auf
   38 Pixel – mehr gibt eine 58 Pixel hohe Kopfzeile nicht her, ohne dass sie
   selbst zum Board wird. */
.pintile {
    display: flex;
    align-items: stretch;
    height: 38px;
    border: 1px solid var(--line);
    border-left-width: 3px;
    border-radius: 6px;
    background: var(--panel-raised);
    flex: none;
    max-width: 190px;
    overflow: hidden;
}

.pintile-main {
    display: flex;
    align-items: baseline;
    gap: 5px;
    flex-wrap: wrap;
    align-content: center;
    padding: 0 8px;
    border: 0;
    background: transparent;
    color: var(--text);
    cursor: pointer;
    min-width: 0;
    text-align: left;
}

.pintile-title {
    flex: 1 0 100%;
    font-size: 9.5px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-faint);
    line-height: 1.1;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 150px;
}

.pintile-value {
    font-family: var(--font-mono);
    font-size: 14px;
    font-weight: 600;
    line-height: 1.1;
    white-space: nowrap;
}

.pintile-unit { font-size: 10px; color: var(--text-dim); white-space: nowrap; }

/* Der Zustand steht in der Farbe – dieselbe Regel wie auf den Boards: nie grün,
   wenn nichts ankommt. */
.pintile.pin-ok      { border-left-color: var(--ok); }
.pintile.pin-warn    { border-left-color: var(--warn); }
.pintile.pin-alarm   { border-left-color: var(--fault); }
.pintile.pin-unknown { border-left-color: var(--line); }

.pintile.pin-warn  .pintile-value { color: var(--warn); }
.pintile.pin-alarm .pintile-value { color: var(--fault); }

.pintile.pin-unknown .pintile-value { color: var(--text-faint); }

/* Ein Alarm in der Kopfzeile pocht: sie ist die Fläche, auf die niemand
   hinsieht, solange nichts passiert. */
.pintile.pin-alarm {
    background: color-mix(in srgb, var(--fault) 10%, var(--panel-raised));
    animation: pintile-beat 1.4s ease-in-out infinite;
}

@keyframes pintile-beat {
    0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--fault) 45%, transparent); }
    55%      { box-shadow: 0 0 0 4px color-mix(in srgb, var(--fault) 0%, transparent); }
}

.pintile-x {
    display: flex;
    align-items: center;
    padding: 0 7px 0 4px;
    border: 0;
    background: transparent;
    color: var(--text-faint);
    font-size: 15px;
    line-height: 1;
    cursor: pointer;
}

.pintile-x:hover { color: var(--fault); background: color-mix(in srgb, var(--fault) 14%, transparent); }

/* Auf schmalen Fenstern gehört die Breite der Seite selbst. Die angehefteten
   Werte sind dann einen Griff entfernt – auf dem Board. */
@media (max-width: 900px) {
    .pinstrip { display: none; }
}

/* ---------- Der Anheft-Knopf auf dem Panel ----------
   Er liegt als Auflage in der oberen rechten Ecke und erscheint beim Überfahren.
   Ständig sichtbar wäre er auf einem Board mit zwölf Kacheln zwölfmal dasselbe
   Symbol; angeheftet bleibt er stehen, sonst wäre der Zustand nur durch
   Hinfahren zu erkennen. */
.lp-pin {
    position: absolute;
    top: 6px;
    right: 6px;
    z-index: 3;
    display: grid;
    place-items: center;
    width: 24px;
    height: 24px;
    padding: 0;
    border: 1px solid transparent;
    border-radius: 5px;
    background: color-mix(in srgb, var(--panel) 70%, transparent);
    color: var(--text-faint);
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}

.lp-pin svg { width: 14px; height: 14px; }

.widget:hover .lp-pin,
.lp-pin:focus-visible { opacity: 1; }

.lp-pin:hover { color: var(--accent); border-color: var(--line); }

.lp-pin.on {
    opacity: 1;
    color: var(--accent);
    border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
    background: var(--accent-soft);
}

/* Im Bearbeiten-Modus steht der Knopf in der Werkzeugleiste des Panels und
   trägt dort dieselbe Auszeichnung wie sonst als Auflage. */
.pe-btn.pe-btn-on { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, var(--line)); }

/* ============================================================================
   Fahrzeugakte und Fristen
   Ein Grundsatz wie bei den Messwerten: der Zustand steht in der Farbe, die
   Aussage daneben trotzdem in Worten. Eine überfällige HU ist rot UND sagt
   „seit 12 Tagen überfällig" – auf einem flau eingestellten Monitor bleibt
   sonst nur Grau, und genau diese Zeile darf nicht untergehen.
   ========================================================================= */

/* ---------- Kopf der Akte ---------- */
.dossier-head {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-bottom: 10px;
}

.dossier-origin { font-size: 12px; color: var(--text); }

/* Die Reiter der Akte tragen Zahlen: wie viele Fristen, Unterlagen, Schäden.
   Ohne sie muss man jeden Reiter einmal öffnen, um zu wissen, ob dort etwas steht. */
.dossier-tabs { flex-wrap: wrap; }

.tab-n {
    display: inline-block;
    margin-left: 6px;
    padding: 0 5px;
    border-radius: 8px;
    background: var(--panel-inset);
    color: var(--text-dim);
    font-size: 10.5px;
    font-weight: 600;
    vertical-align: middle;
}

.tabs button.active .tab-n { background: var(--accent-soft); color: var(--accent); }
.tab-n.hot { background: color-mix(in srgb, var(--fault) 18%, transparent); color: var(--fault); }

/* Der Inhalt der Akte scrollt in sich: ein Dialog, der mit jedem Reiter seine Höhe
   ändert, springt beim Umschalten unter dem Zeiger weg. */
.dossier-panel { max-height: 60vh; overflow: auto; }

.dossier-block { display: flex; flex-direction: column; gap: 12px; }

/* Das Eingabeblatt hebt sich vom Rest ab: darin wird etwas angelegt, darunter
   steht, was schon da ist. */
.dossier-form {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--panel-inset);
}

/* ---------- Fristen ---------- */
.dl-list { display: flex; flex-direction: column; gap: 6px; }

.dl-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    border: 1px solid var(--line);
    border-left-width: 3px;
    border-radius: var(--radius-sm);
    background: var(--panel);
}

.dl-kind {
    flex: none;
    min-width: 74px;
    padding: 2px 7px;
    border-radius: 3px;
    background: var(--panel-inset);
    font-family: var(--font-mono);
    font-size: 10.5px;
    letter-spacing: 0.04em;
    text-align: center;
    color: var(--text-dim);
}

.dl-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.dl-title { font-size: 13px; font-weight: 600; }
.dl-sub { display: flex; gap: 8px; flex-wrap: wrap; font-size: 11px; color: var(--text-faint); }

.dl-when { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; flex: none; }
.dl-date { font-family: var(--font-mono); font-size: 12.5px; }
.dl-rel { font-size: 11px; color: var(--text-dim); white-space: nowrap; }

.dl-tools { display: flex; gap: 2px; flex: none; }

/* Die vier Zustände. Erledigtes tritt zurück – es steht nur noch als Beleg da. */
.dl-far     { border-left-color: var(--line-strong); }
.dl-soon    { border-left-color: var(--accent); }
.dl-due     { border-left-color: var(--warn); background: color-mix(in srgb, var(--warn) 7%, var(--panel)); }
.dl-overdue { border-left-color: var(--fault); background: color-mix(in srgb, var(--fault) 9%, var(--panel)); }
.dl-done    { border-left-color: var(--ok); opacity: 0.6; }

.dl-due .dl-date     { color: var(--warn); }
.dl-overdue .dl-date { color: var(--fault); font-weight: 600; }

/* Dieselben Farben als Marke – auf der Fahrzeugkachel und im Kopf der Akte. */
.chip.dl-chip { font-family: var(--font-body); letter-spacing: 0; }
.chip.dl-soon    { border-color: var(--accent); color: var(--accent); }
.chip.dl-due     { border-color: var(--warn); color: var(--warn); }
.chip.dl-overdue { border-color: var(--fault); color: var(--fault);
                   background: color-mix(in srgb, var(--fault) 12%, transparent); }
.chip.dl-done    { border-color: var(--ok); color: var(--ok); }

/* ---------- Unterlagen ---------- */
.doc-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
    gap: 10px;
}

.doc-card {
    position: relative;
    display: flex;
    gap: 10px;
    padding: 8px;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--panel);
}

.doc-thumb {
    display: grid;
    place-items: center;
    width: 54px;
    height: 54px;
    flex: none;
    border-radius: 4px;
    background: var(--panel-inset);
    overflow: hidden;
}

.doc-thumb img { width: 100%; height: 100%; object-fit: cover; }

.doc-ext {
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: 0.08em;
    color: var(--text-dim);
}

.doc-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.doc-name { font-size: 12.5px; font-weight: 600; color: var(--text);
            overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.doc-name:hover { color: var(--accent); }
.doc-cat { font-size: 11px; color: var(--text-dim); }
.doc-meta { font-size: 10.5px; color: var(--text-faint); }
.doc-valid { font-size: 10.5px; color: var(--ok); }
.doc-valid.over { color: var(--fault); }

.doc-x {
    position: absolute;
    top: 4px;
    right: 4px;
    border: 0;
    background: transparent;
    color: var(--text-faint);
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.12s ease;
}

.doc-card:hover .doc-x { opacity: 1; }
.doc-x:hover { color: var(--fault); }

/* ---------- Schäden ---------- */
.dmg-list { display: flex; flex-direction: column; gap: 8px; }

.dmg {
    border: 1px solid var(--line);
    border-left-width: 3px;
    border-radius: var(--radius-sm);
    background: var(--panel);
}

.dmg.sev-minor    { border-left-color: var(--line-strong); }
.dmg.sev-major    { border-left-color: var(--warn); }
.dmg.sev-critical { border-left-color: var(--fault); }

/* Ein erledigter Schaden tritt zurück, verschwindet aber nicht: bei der Rückgabe
   eines Leasingfahrzeugs ist gerade der reparierte Schaden der Beleg. */
.dmg.closed { opacity: 0.62; }

.dmg-head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 10px;
    cursor: pointer;
    flex-wrap: wrap;
}

.dmg-head:hover { background: var(--panel-inset); }
.dmg-date { font-family: var(--font-mono); font-size: 11.5px; color: var(--text-dim); flex: none; }
.dmg-title { font-size: 13px; font-weight: 600; }
.dmg-spot { font-size: 11.5px; color: var(--text-dim); }
.dmg-cost { font-family: var(--font-mono); font-size: 12px; }
.chip.dmg-state { flex: none; }

.dmg-body {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 10px;
    border-top: 1px solid var(--line);
}

.dmg-text { margin: 0; font-size: 12.5px; line-height: 1.55; color: var(--text); white-space: pre-wrap; }
.dmg-facts { display: flex; flex-wrap: wrap; gap: 14px; font-size: 11.5px; color: var(--text-dim); }

/* ---------- Zuständige ---------- */
.hold-list { display: flex; flex-direction: column; gap: 6px; }

.hold {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--panel);
}

/* Wer nicht mehr zuständig ist, bleibt als Historie stehen – blass, nicht weg:
   die Frage „wer hatte das Fahrzeug im Mai" kommt nach jedem Schaden. */
.hold.past { opacity: 0.55; }

.hold-role {
    flex: none;
    min-width: 92px;
    padding: 2px 7px;
    border-radius: 3px;
    background: var(--panel-inset);
    font-size: 10.5px;
    letter-spacing: 0.04em;
    text-align: center;
    color: var(--text-dim);
}

.hold-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.hold-name { font-size: 13px; font-weight: 600; }
.hold-sub { display: flex; gap: 8px; flex-wrap: wrap; font-size: 11px; color: var(--text-faint); }
.hold-notify { flex: none; font-size: 11.5px; }

/* ---------- Fahrtenbuch in der Akte ---------- */
.trip-month { font-family: var(--font-ui); font-size: 13px; font-weight: 600; min-width: 130px; }

.trip-sums { display: flex; gap: 10px; flex-wrap: wrap; }

.trip-sum {
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: 8px 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--panel-inset);
    min-width: 96px;
}

.trip-sum .ts-n { font-family: var(--font-mono); font-size: 16px; font-weight: 600; }
.trip-sum .ts-l { font-size: 10.5px; color: var(--text-dim); }

.ts-table-wrap { max-height: 320px; overflow: auto; }
.trip-void { opacity: 0.5; text-decoration: line-through; }

/* ---------- Fristenübersicht der Flotte ---------- */
.fleet-due-panel { min-height: 420px; }
.due-filter { flex: none; }
.due-filter button { height: 26px; padding: 0 11px; font-size: 11.5px; }

.due-table td { vertical-align: top; }
.due-date { font-size: 12.5px; display: block; }
.due-rel { font-size: 11px; color: var(--text-dim); }

/* Die Zeile trägt ihren Zustand am linken Rand – in einer Tabelle mit sechs Spalten
   ist das die einzige Stelle, die beim Überfliegen wirklich auffällt. */
.due-table tr.due-overdue td:first-child { box-shadow: inset 3px 0 0 var(--fault); }
.due-table tr.due-due     td:first-child { box-shadow: inset 3px 0 0 var(--warn); }
.due-table tr.due-soon    td:first-child { box-shadow: inset 3px 0 0 var(--accent); }

.due-table tr.due-overdue .due-date { color: var(--fault); font-weight: 600; }
.due-table tr.due-due .due-date { color: var(--warn); font-weight: 600; }

.due-warn { font-size: 11px; color: var(--fault); margin-top: 2px; }

/* ---------- Die Akte auf der Fahrzeugkachel ---------- */
.vc-file {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
    padding: 0 12px 2px;
}

/* ============================================================================
   Flottenbereich: eine Fläche
   Links die Gliederung, rechts die Auswahl mit ihren Kennzahlen und der Tabelle.
   Vorher waren es vier Ansichten zum Umschalten – vier Antworten auf dieselbe
   Frage, zwischen denen man blättern musste, um den ganzen Bestand zu sehen.
   ========================================================================= */
.fleet-split {
    display: grid;
    grid-template-columns: 290px minmax(0, 1fr);
    gap: 16px;

    /* Kopfzeile, Reiterleiste und Seitenrand sind abgezogen. Die Leiste kam mit den
       vier Reitern dazu - ohne diese 42px stuende der Bestand unten ueber. */
    height: calc(100vh - 205px);
    min-height: 420px;
}

.fleet-split > .panel-fill > .panel-body { overflow: hidden; }
.fleet-split .fleet-list { flex: 1; min-height: 0; overflow-y: auto; margin: -2px; padding: 2px; }
.fleet-side .fi-hot { color: var(--fault); }

/* ---------- Kennzahlen der Auswahl ---------- */
.fleet-kpis {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    flex: none;
    margin-bottom: 10px;
}

.fk {
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: 7px 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--panel-inset);
    min-width: 92px;
}

.fk-n { font-family: var(--font-mono); font-size: 16px; font-weight: 600; }
.fk-l { font-size: 10.5px; color: var(--text-dim); }
.fk.hot .fk-n { color: var(--fault); }
.fk.warn .fk-n { color: var(--warn); }

/* ---------- Filterleiste ---------- */
.fleet-toolbar {
    display: flex;
    gap: 8px;
    align-items: center;
    flex-wrap: wrap;
    flex: none;
    margin-bottom: 10px;
}

.fleet-toolbar .fleet-search { flex: 1 1 240px; min-width: 180px; }
.fleet-toolbar .ft-f { width: auto; min-width: 150px; flex: none; }

/* ---------- Die Tabelle ---------- */
.cars-panel .table-wrap { flex: 1; min-height: 0; overflow: auto; }

.ft-table th { white-space: nowrap; }

/* Das Bild im Bestand traegt weder Rahmen noch eigenen Grund. Der Rahmen ist
   anderswo begruendet - ein dunkles Rendering ohne Kante sieht auf hellem Panel
   wie ein Ladefehler aus (siehe .veh-img) -, aber in einer Tabelle steht er
   gegen die Zeile: Kaesten in einer Spalte, in der die meisten leer sind, lesen
   sich als Gitter im Gitter. Ohne Kante steht dort das Fahrzeug und sonst
   nichts, und die Zeile wirkt lockerer.

   Auch beim Ueberfahren kommt keine Linie zurueck: Dass das Bild anklickbar
   ist, sagt jetzt die Aufhellung - sie braucht keinen Kasten, den die Spalte
   sonst gerade losgeworden ist. */
.ft-table td.cell-img .veh-img {
    border: 0;
    background: none;
    transition: opacity .12s, filter .12s;
}
.ft-table td.cell-img .veh-img-btn:hover .veh-img { filter: brightness(1.08); }
.ft-table td.cell-img .veh-img-btn:focus-visible .veh-img { filter: brightness(1.08); }

/* Die beiden schmalen Spalten ganz links: Sie nehmen genau ihre Breite und
   nicht mehr, damit der Name so weit wie moeglich vorn beginnt. */
.ft-table th.cell-exp, .ft-table td.cell-exp { width: 1%; padding-right: 0; }
.ft-table td.cell-img { padding-top: 6px; padding-bottom: 6px; }

/* Sortierbare Spalten zeigen es erst beim Überfahren an – ein Pfeil an jeder
   Überschrift wäre in einer Tabelle mit zehn Spalten ein Zaun. */
.ft-table th.ft-sort { cursor: pointer; user-select: none; }
.ft-table th.ft-sort:hover { color: var(--text); }
.ft-table th.ft-sort::after { content: " ⇅"; opacity: 0; font-size: 10px; }
.ft-table th.ft-sort:hover::after { opacity: 0.45; }
.ft-table th.sorted { color: var(--accent); }
.ft-table th.sorted.asc::after  { content: " ▲"; opacity: 1; font-size: 9px; }
.ft-table th.sorted.desc::after { content: " ▼"; opacity: 1; font-size: 9px; }

/* ---------- Ein Kopf, zwei Sortierungen ----------
   Wo zwei Angaben in eine Spalte gewandert sind - "Zuletzt gesehen · Geraete",
   "Pakete · Fehler" -, muss beides sortierbar bleiben. Also traegt nicht mehr die
   Zelle das Klickziel, sondern jedes Wort in ihr. Der Punkt dazwischen gehoert zu
   keinem von beiden und ist deshalb nicht anklickbar.

   Ohne das waere das Zusammenlegen ein Verlust: Eine Flotte nach der Zahl offener
   Fehler zu ordnen ist genau der Griff, mit dem man morgens anfaengt. */
.ft-table th.ft-sort2 > span { cursor: pointer; user-select: none; }
.ft-table th.ft-sort2 > span:hover { color: var(--text); }
.ft-table th.ft-sort2 > span::after { content: " ⇅"; opacity: 0; font-size: 10px; }
.ft-table th.ft-sort2 > span:hover::after { opacity: 0.45; }
.ft-table th.ft-sort2 > span.sorted { color: var(--accent); }
.ft-table th.ft-sort2 > span.sorted.asc::after  { content: " ▲"; opacity: 1; font-size: 9px; }
.ft-table th.ft-sort2 > span.sorted.desc::after { content: " ▼"; opacity: 1; font-size: 9px; }
.ft-table th.ft-sort2 > span.ft-sort2-sep {
    cursor: default; padding: 0 2px; color: var(--text-faint);
}
.ft-table th.ft-sort2 > span.ft-sort2-sep::after { content: none; }

/* ---------- Die zweite Zeile einer Zelle ----------
   Was aus einer eigenen Spalte unter eine andere Angabe gewandert ist: Alias und
   CAR-ID unter den Namen, die Geraetezahl unter die Zeitspanne, die Fehler unter
   die Pakete. Klein und gedaempft, weil es die Erlaeuterung ist und nicht die
   Aussage - wer die Zeile ueberfliegt, liest die obere. */
.ft-sub {
    font-size: 10.5px; line-height: 1.35;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}

/* Die Marken der Akte: Unterlagen, Schaeden, fehlende Zustaendigkeit. Frueher drei
   Chips nebeneinander in einer eigenen Spalte - drei Rahmen fuer drei Zahlen. Als
   Zeile unter der Frist tragen sie keinen eigenen Kasten mehr; sie stehen ohnehin
   unter einem. */
.ft-marks {
    display: inline-flex; align-items: center; gap: 9px; flex-wrap: wrap;
    font-size: 10.5px; line-height: 1.3; color: var(--text-dim);
}

.ft-table tr.ft-row.open > td { background: var(--accent-soft); }

/* Auch zehn Spalten passen nicht auf jedes Fenster – die Tabelle rollt waagerecht. Die
   Aktionsspalte bleibt dabei stehen: „Akte" und „Bearbeiten" sind die beiden
   Knöpfe, wegen derer man die Zeile überhaupt sucht, und sie sollen nicht am
   rechten Rand verschwinden. Der Schatten zeigt, dass darunter noch etwas liegt. */
.ft-table th.ft-act,
.ft-table td.ft-act {
    position: sticky;
    right: 0;
    z-index: 1;
    background: var(--panel);
    box-shadow: -9px 0 9px -9px rgba(0, 0, 0, 0.5);
}

.ft-table thead th.ft-act { z-index: 3; }
.ft-table tr.ft-row.open td.ft-act { background: color-mix(in srgb, var(--accent) 12%, var(--panel)); }

/* Der Aufklapper. Vorher achtzehn Pixel mit einem Dreieck aus der Fallback-
   Schrift - ein Ziel, das man auf einem Laptop-Trackpad zweimal anfaehrt, und
   ein Zeichen, das je nach System anders gross gesetzt wird. Jetzt ein Winkel
   als Strichzeichnung in der Groesse der uebrigen Icons, auf einer Flaeche, die
   den Finger traegt. */
.ft-exp {
    width: 30px;
    height: 30px;
    display: grid;
    place-items: center;
    flex: none;
    padding: 0;
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-dim);
    cursor: pointer;
    transition: background .12s, color .12s;
}

.ft-exp > svg { width: 18px; height: 18px; transition: transform .16s ease; }

/* Aufgeklappt zeigt derselbe Winkel nach unten. Eine Drehung statt eines zweiten
   Zeichens: Sie sagt „dasselbe Ding, anderer Zustand". */
.ft-exp.open > svg { transform: rotate(90deg); }
.ft-exp.open { color: var(--accent); }

.ft-exp:hover { background: var(--panel-inset); color: var(--text); }
.ft-exp:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

@media (prefers-reduced-motion: reduce) { .ft-exp > svg { transition: none; } }

/* ---------- Die aufgeklappte Zeile ---------- */
.ft-table tr.ft-detail > td { background: var(--panel-inset); padding: 0; }

.ftd {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 12px 14px 12px 40px;
    border-left: 3px solid var(--accent);
}

.ftd-block { display: flex; flex-direction: column; gap: 5px; }

.ftd-h {
    font-family: var(--font-ui);
    font-size: 10px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-faint);
}

.ftd-tools { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }

/* ---------- Blättern ---------- */
.ft-pager {
    display: flex;
    align-items: center;
    gap: 6px;
    flex: none;
    padding-top: 10px;
    margin-top: 10px;
    border-top: 1px solid var(--line);
}

.ft-pager .ft-size { width: auto; min-width: 120px; }
.ft-pageno { font-size: 12px; color: var(--text-dim); white-space: nowrap; padding: 0 4px; }

/* ---------- Schmale Fenster ---------- */
@media (max-width: 1100px) {
    /* Gestapelt: die Flottenliste bekommt eine feste Höhe und die Tabelle den Rest.
       Die Seite darf dann scrollen – zwei Spalten auf 900 Pixeln wären zwei zu
       schmale Spalten. */
    .fleet-split {
        grid-template-columns: 1fr;
        height: auto;
        min-height: 0;
    }

    .fleet-split > .panel-fill:first-child { max-height: 260px; }
    .fleet-split > .panel-fill:last-child { height: 620px; }
}

/* =====================================================================
   Fuhrpark 3D – der virtuelle Hof (/garage)

   Ein Experiment: die Flotte als Ort statt als Tabelle. Die Szene selbst
   zeichnet WebGL (js/fleet3d.js); hier steht der Rahmen darum – die
   Schilder über den Dächern, die Karteikarte daneben und das Gespräch
   mit dem Fahrzeug.

   Grundsatz wie überall: der Zustand steht in der Farbe, und es sind
   dieselben Farben wie auf der Live-Karte. Ein Fahrzeug, das dort grün
   ist und hier gelb, wäre ein Fehler und keine Gestaltung.
   ===================================================================== */

.garage-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 380px;
    gap: 16px;
    height: calc(100vh - 163px);
    min-height: 460px;
}

.garage-layout .garage-scene-panel > .panel-body { padding: 0; position: relative; }
.garage-layout .garage-side-panel > .panel-body { overflow-y: auto; }

/* ---------- Die Leinwand ---------- */

.g3d-host {
    position: absolute;
    inset: 0;
    border-radius: 0 0 var(--radius) var(--radius);
    overflow: hidden;
    background: var(--bg);
}

.g3d-canvas { display: block; width: 100%; height: 100%; cursor: grab; touch-action: none; }
.g3d-canvas:active { cursor: grabbing; }

/* Die Schilder liegen über der Leinwand und nehmen keine Klicks an – der Klick gehört
   der Szene darunter, sonst wäre ausgerechnet das beschriftete Fahrzeug das einzige,
   das sich nicht anklicken lässt. */
.g3d-overlay { position: absolute; inset: 0; pointer-events: none; overflow: hidden; }

.g3d-tag {
    position: absolute;
    top: 0;
    left: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1px;
    padding: 3px 7px 4px;
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--panel) 82%, transparent);
    border: 1px solid var(--line);
    box-shadow: 0 6px 16px -10px rgba(0, 0, 0, .8);
    white-space: nowrap;
    backdrop-filter: blur(3px);
    transition: border-color .12s, background .12s;
}

/* Der Strich vom Schild zum Dach: ohne ihn schwebt die Beschriftung im Bild und man
   ordnet sie beim Drehen der Kamera dem falschen Fahrzeug zu. */
.g3d-tag::after {
    content: "";
    position: absolute;
    top: 100%;
    left: 50%;
    width: 1px;
    height: 10px;
    background: linear-gradient(to bottom, var(--line-strong), transparent);
}

.g3d-tag-name {
    font-family: var(--font-ui);
    font-size: 10.5px;
    font-weight: 600;
    color: var(--text);
    max-width: 150px;
    overflow: hidden;
    text-overflow: ellipsis;
}

.g3d-tag-sub { font-family: var(--font-mono); font-size: 9px; color: var(--text-faint); }

.g3d-tag[data-state="driving"] { border-color: color-mix(in srgb, var(--ok) 55%, var(--line)); }
.g3d-tag.is-alert { border-color: color-mix(in srgb, var(--warn) 55%, var(--line)); }

.g3d-tag.is-selected {
    background: var(--panel-raised);
    border-color: var(--accent);
    box-shadow: 0 0 0 1px var(--accent-soft), 0 8px 20px -10px var(--accent-glow);
}

.g3d-tag.is-selected .g3d-tag-name { color: var(--accent); }

/* ---------- Rahmen der Szene ---------- */

.garage-hint {
    position: absolute;
    left: 12px;
    bottom: 10px;
    font-size: 10.5px;
    color: var(--text-faint);
    background: color-mix(in srgb, var(--panel) 70%, transparent);
    padding: 3px 9px;
    border-radius: 999px;
    border: 1px solid var(--line);
    pointer-events: none;
}

.garage-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
    min-height: 200px;
    padding: 24px;
    text-align: center;
    color: var(--text-dim);
    font-size: 12.5px;
}

/* ---------- Zustandsfarben (Ringe, Punkte, Marken) ---------- */

.g-dot { width: 9px; height: 9px; border-radius: 50%; display: inline-block; flex: none; background: var(--stale); }
.g-dot.g-state-driving { background: var(--ok); }
.g-dot.g-state-standing { background: var(--warn); }
.g-dot.g-state-stale, .g-dot.g-state-offline { background: var(--stale); }
.g-dot.g-dot-alert { background: var(--fault); box-shadow: 0 0 0 3px color-mix(in srgb, var(--fault) 25%, transparent); }

.chip.g-state { gap: 5px; }
.chip.g-state::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--stale); }
.chip.g-state-driving { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 40%, var(--line)); }
.chip.g-state-driving::before { background: var(--ok); }
.chip.g-state-standing::before { background: var(--warn); }

.text-ok { color: var(--ok); }
.text-warn { color: var(--warn); }
.text-fault { color: var(--fault); }

.select.select-sm { height: 27px; font-size: 11.5px; padding: 0 26px 0 9px; background-position: right 8px center; }

/* ---------- Die Übersicht in der Seitenleiste ---------- */

.gover { display: flex; flex-direction: column; gap: 12px; }

.gover-score { display: flex; align-items: center; gap: 12px; }

.gover-score-num {
    font-family: var(--font-ui);
    font-size: 34px;
    font-weight: 700;
    line-height: 1;
    color: var(--accent);
    font-variant-numeric: tabular-nums;
}

.gover-score-cap { font-size: 12px; font-weight: 600; font-family: var(--font-ui); }

.gover-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }

.gover-stat {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    padding: 7px 4px;
    background: var(--panel-inset);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
}

.gover-stat b { font-family: var(--font-mono); font-size: 15px; font-variant-numeric: tabular-nums; }
.gover-stat span { font-size: 9.5px; color: var(--text-dim); text-align: center; }

.gover-head {
    font-family: var(--font-ui);
    font-size: 10px;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--text-faint);
    margin-top: 4px;
}

.gover-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 6px 9px;
    border-radius: var(--radius-sm);
    background: var(--panel-raised);
    border: 1px solid transparent;
    cursor: pointer;
    transition: border-color .12s;
}

.gover-row:hover { border-color: var(--line-strong); }
.gover-row-name { font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gover-row-note { display: flex; gap: 4px; flex: none; font-size: 11px; color: var(--text-dim); }

.gover-legend { display: flex; flex-direction: column; gap: 5px; font-size: 11px; color: var(--text-dim); }
.gover-legend span { display: flex; align-items: center; gap: 7px; }

/* ---------- Die Karteikarte ---------- */

.vcard { display: flex; flex-direction: column; gap: 10px; height: 100%; min-height: 0; }

.vcard-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.vcard-name { font-size: 15px; margin: 0; overflow-wrap: anywhere; }

.vcard-state { display: flex; flex-wrap: wrap; gap: 6px; }

.vcard-score { display: flex; align-items: center; gap: 9px; }

.vcard-score-bar {
    flex: 1;
    height: 6px;
    border-radius: 999px;
    background: var(--panel-inset);
    border: 1px solid var(--line);
    overflow: hidden;
}

.vcard-score-bar > span { display: block; height: 100%; background: var(--accent); transition: width .3s ease; }
.vcard-score-bar > span.lvl-1 { background: var(--fault); }
.vcard-score-bar > span.lvl-2 { background: var(--warn); }
.vcard-score-bar > span.lvl-5 { background: var(--ok); }

.vcard-score-text { font-family: var(--font-mono); font-size: 11.5px; flex: none; }

.vcard-badges { display: flex; flex-wrap: wrap; gap: 5px; }

/* Ein Abzeichen ohne Begründung wäre Zierrat – jedes trägt seine im Titel, und der
   gestrichelte Unterstrich sagt, dass dort etwas zu lesen ist. */
.vbadge {
    display: inline-flex;
    align-items: center;
    height: 19px;
    padding: 0 8px;
    border-radius: 999px;
    font-size: 10px;
    font-family: var(--font-ui);
    letter-spacing: .02em;
    background: var(--panel-inset);
    border: 1px solid var(--line);
    color: var(--text-dim);
    cursor: help;
    border-bottom-style: dashed;
}

.vbadge-clean { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 35%, var(--line)); }
.vbadge-onroad { color: var(--ok); }
.vbadge-probe { color: var(--info); }
.vbadge-overdue { color: var(--fault); border-color: color-mix(in srgb, var(--fault) 35%, var(--line)); }
.vbadge-scars { color: var(--warn); }

/* Fünf Reiter auf 380 Pixeln: Die gemeinsame Reiterleiste ist inline-flex und
   schneidet ab, was nicht passt – ausgerechnet den letzten, und das ist das
   Gespräch. Hier darf sie umbrechen und die Beschriftung enger stehen. */
.vcard-tabs {
    flex: none;
    display: flex;
    flex-wrap: wrap;
    row-gap: 3px;
}

.vcard-tabs button { height: 27px; padding: 0 9px; font-size: 11.5px; }
.vcard-body { flex: 1; min-height: 0; overflow-y: auto; }

.vcard-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }

.vcard-gap {
    padding: 7px 9px;
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--warn) 10%, var(--panel-inset));
    border: 1px solid color-mix(in srgb, var(--warn) 30%, var(--line));
    font-size: 11.5px;
    color: var(--text-dim);
    margin-bottom: 8px;
}

/* ---------- Schlüssel-Wert-Listen in der Karteikarte ---------- */

.vlist { display: flex; flex-direction: column; }

.vlist-head {
    font-family: var(--font-ui);
    font-size: 10px;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--text-faint);
    margin: 12px 0 4px;
    padding-bottom: 3px;
    border-bottom: 1px solid var(--line);
}

.vrow {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    padding: 4px 0;
    border-bottom: 1px dotted color-mix(in srgb, var(--line) 70%, transparent);
    font-size: 12px;
}

.vrow:last-child { border-bottom: none; }
.vrow-stack { flex-direction: column; align-items: flex-start; gap: 1px; }
.vrow-k { color: var(--text-dim); flex: none; }
.vrow-v { text-align: right; overflow-wrap: anywhere; display: inline-flex; align-items: center; gap: 6px; }
.vrow-stack .vrow-v { text-align: left; font-size: 11px; }

.vdot { width: 7px; height: 7px; border-radius: 50%; background: var(--stale); flex: none; }
.vdot.is-on { background: var(--ok); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ok) 20%, transparent); }

/* ---------- Das Gespräch ---------- */

.vtalk { display: flex; flex-direction: column; gap: 8px; height: 100%; min-height: 320px; }

.vtalk-log {
    flex: 1;
    min-height: 140px;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding-right: 2px;
}

.vtalk-intro { display: flex; flex-direction: column; gap: 4px; padding: 14px 2px; }
.vtalk-intro-head { font-family: var(--font-ui); font-size: 13px; font-weight: 600; }
.vtalk-intro-sub { font-size: 11.5px; color: var(--text-dim); line-height: 1.5; }

.vtalk-row { display: flex; flex-direction: column; gap: 2px; max-width: 90%; }
.vtalk-row.is-me { align-self: flex-end; align-items: flex-end; }

.vtalk-who {
    font-size: 9.5px;
    color: var(--text-faint);
    font-family: var(--font-mono);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 100%;
}

.vtalk-bubble {
    padding: 8px 11px;
    border-radius: var(--radius);
    background: var(--panel-inset);
    border: 1px solid var(--line);
    font-size: 12.5px;
    line-height: 1.55;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.is-me .vtalk-bubble {
    background: var(--accent-soft);
    border-color: color-mix(in srgb, var(--accent) 35%, var(--line));
}

/* Solange der Text wächst, bleibt die Kante des Kastens sichtbar – man soll sehen,
   dass die Antwort noch nicht fertig ist, ohne dass ein Balken darüber steht. */
.vtalk-live { border-color: color-mix(in srgb, var(--accent) 30%, var(--line)); }

.vtalk-dots { display: inline-flex; gap: 4px; padding: 2px 0; }
.vtalk-dots i {
    width: 5px; height: 5px; border-radius: 50%;
    background: var(--text-faint);
    animation: vtalk-blink 1.2s infinite ease-in-out;
}
.vtalk-dots i:nth-child(2) { animation-delay: .18s; }
.vtalk-dots i:nth-child(3) { animation-delay: .36s; }

@keyframes vtalk-blink {
    0%, 80%, 100% { opacity: .25; }
    40% { opacity: 1; }
}

.vtalk-error {
    padding: 7px 10px;
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--fault) 12%, var(--panel-inset));
    border: 1px solid color-mix(in srgb, var(--fault) 35%, var(--line));
    font-size: 11.5px;
    color: var(--text);

    /* Fehlertexte eines Sprachmodell-Servers sind roher JSON-Text ohne ein einziges
       Leerzeichen. Ohne diese Zeile schiebt er das Protokoll in die Breite, statt
       umzubrechen – und die Meldung ist dann gerade dort abgeschnitten, wo steht, was
       eigentlich los war. */
    overflow-wrap: anywhere;
}

.vtalk-chips { display: flex; flex-wrap: wrap; gap: 5px; }
.vtalk-chip { cursor: pointer; height: 22px; font-size: 10.5px; }
.vtalk-chip:hover { border-color: var(--accent); color: var(--accent); }

.vtalk-input { display: flex; gap: 6px; align-items: center; }
.vtalk-input .input { flex: 1; height: 30px; font-size: 12px; }

.vtalk-foot { font-size: 10px; line-height: 1.4; }

/* ---------- Schmale Fenster ----------
   Gestapelt, und die Szene bekommt eine feste Höhe: Ein Hof, der sich die Höhe vom
   Inhalt nimmt, ist auf einem Telefon entweder 80 Pixel hoch oder zwei Bildschirme. */
@media (max-width: 1180px) {
    .garage-layout {
        grid-template-columns: 1fr;
        grid-template-rows: auto auto;
        height: auto;
        min-height: 0;
    }

    .garage-layout .garage-scene-panel { height: 440px; }
    .garage-layout .garage-side-panel { max-height: 620px; }
}

/* ---------- Das Fahrzeugfoto in der Karteikarte ----------
   Dieselbe Aufnahme, aus der im Hof die Silhouette abgeleitet wird. Sie steht über den
   Stammdaten, weil ein Bild die Frage „welches Fahrzeug ist das" in einem Augenblick
   beantwortet, für den man sonst vier Zeilen liest. */
.vcard-photo {
    position: relative;
    margin-bottom: 10px;
    border-radius: var(--radius-sm);
    overflow: hidden;
    background: var(--panel-inset);
    border: 1px solid var(--line);
}

.vcard-photo img { display: block; width: 100%; height: auto; }

.vcard-photo-cap {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    padding: 4px 8px;
    font-size: 10.5px;
    color: var(--text);
    background: linear-gradient(to top, color-mix(in srgb, var(--panel) 92%, transparent), transparent);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* =====================================================================
   Fahrtenbuch – Kopfleiste und Kennzahlen, kompakt

   Die Seite beginnt mit zwei Blöcken, die zusammen ein Drittel des
   Bildschirms füllten, bevor die erste Fahrt zu sehen war: die
   Auswahlleiste und acht Kennzahlkacheln. Beides trägt – aber keines
   davon ist das, weswegen jemand das Fahrtenbuch öffnet. Sie sind
   deshalb auf das Maß gebracht, in dem sie nebenbei gelesen werden.
   ===================================================================== */

/* ---------- Auswahlleiste ---------- */

.fb-bar .toolbar {
    align-items: flex-end;
    gap: 10px;
    row-gap: 8px;
}

/* Labels dichter an ihrem Feld: Die sechs Pixel Luft der allgemeinen Form summieren
   sich über vier Felder zu einer Zeile, die nichts enthält. */
.fb-bar .field { gap: 3px; }
.fb-bar .field > label { font-size: 10.5px; letter-spacing: .01em; }
.fb-bar .input, .fb-bar .select { height: 32px; font-size: 12.5px; padding: 0 10px; }
.fb-bar .select { padding-right: 28px; background-position: right 9px center; }

.fb-f-dongle { min-width: 220px; flex: 1 1 260px; max-width: 400px; }
.fb-f-period { width: auto; flex: none; }
.fb-f-driver { width: 170px; flex: none; }

/* ---------- Zeitraum als ein Bedienelement ----------
   Vorher waren es vier einzelne Teile mit Lücken dazwischen – zwei Pfeilknöpfe, ein
   Monatsfeld mit eigenem Rahmen und ein Jahr-Schalter. Das liest sich als Sammlung und
   nicht als Auswahl. Jetzt ist es eine Gruppe mit einem Rahmen: Pfeil, Zeitraum, Pfeil,
   und rechts der Umschalter auf das ganze Jahr. */
.fb-period {
    display: flex;
    align-items: stretch;
    gap: 0;
    height: 32px;
    background: var(--panel-inset);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-sm);
    overflow: hidden;
}

.fb-period > .btn {
    height: 100%;
    border: none;
    border-radius: 0;
    background: transparent;
    padding: 0 9px;
    color: var(--text-dim);
    font-size: 11px;
}

.fb-period > .btn:hover { background: var(--panel-raised); color: var(--text); }

.fb-period > .btn.btn-toggled {
    background: var(--accent-soft);
    color: var(--accent);
    border-left: 1px solid var(--line);
}

.fb-period > .btn:last-child { border-left: 1px solid var(--line); }

.fb-period .input {
    height: 100%;

    /* Breit genug für „September 2026" samt Kalendersymbol – bei 128 px schnitt das
       Feld die Jahreszahl ab, und im Zeitraumfeld stand „September 202". */
    width: 152px;
    border: none;
    border-radius: 0;
    background: transparent;
    text-align: center;
    font-size: 12.5px;
    padding: 0 4px;
}

.fb-period .input:focus { outline: none; box-shadow: inset 0 0 0 1px var(--accent); }

/* Das native Kalendersymbol des Monatsfelds sitzt am rechten Rand und zieht den Blick
   auf sich, ohne etwas zu können, was die Pfeile daneben nicht besser könnten. */
.fb-period input[type="month"]::-webkit-calendar-picker-indicator {
    opacity: .35;
    cursor: pointer;
    margin-left: 2px;
}

.fb-period input[type="month"]::-webkit-calendar-picker-indicator:hover { opacity: .8; }

/* ---------- Aktualisieren ----------
   Der Knopf stand am rechten Rand für sich allein, während die Felder links zusammen
   standen – ein Bedienelement ohne erkennbare Zugehörigkeit. Jetzt schließt er an das
   letzte Feld an und steht auf derselben Grundlinie. */
.fb-f-load {
    display: flex;
    align-items: flex-end;
    flex: none;
    margin-left: 2px;
}

.fb-f-load .btn { height: 32px; font-size: 12.5px; padding: 0 14px; gap: 7px; }

/* ---------- Kennzahlen, kleiner ---------- */

.fb-kpis { grid-template-columns: repeat(auto-fit, minmax(118px, 1fr)); gap: 6px; }

.fb-kpi {
    padding: 6px 9px;
    border-left-width: 2px;
    gap: 0;
}

.fb-kpi-k { font-size: 8.5px; letter-spacing: .09em; }
.fb-kpi-v { font-size: 14.5px; line-height: 1.25; }

/* Die dritte Zeile ist Erläuterung und nicht Zahl: eine Zeile reicht, und wo sie nicht
   reicht, steht der ganze Text im Titel. */
.fb-kpi-n {
    font-size: 9.5px;
    line-height: 1.25;
    -webkit-line-clamp: 1;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ---------- Stornierte Fahrten ---------- */

.fb-voided-line {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    flex-wrap: wrap;
    margin-top: 8px;
    padding: 6px 10px;
    background: var(--panel-inset);
    border: 1px dashed var(--line-strong);
    border-radius: var(--radius-sm);
    font-size: 11.5px;
}

@media (max-width: 900px) {
    .fb-f-period, .fb-f-driver, .fb-f-load { width: 100%; }
    .fb-period { width: 100%; }
    .fb-period .input { flex: 1; width: auto; }
    .fb-f-load .btn { width: 100%; }
}

/* =====================================================================
   Zeitreihen – kurze oder invalide Messungen

   Ein Fahrzeug wacht auf, meldet sich zwanzig Sekunden und schläft
   wieder ein: beim Entriegeln, beim Laden, beim Werkstattkontakt. In
   der Messungsliste standen diese Aufzeichnungen zwischen den Fahrten
   und waren dort in der Überzahl. Sie stehen jetzt in einem eigenen
   Reiter oben im Panel – nicht versteckt, sondern getrennt.
   ===================================================================== */

/* Die Reiter teilen sich die Zeile zu gleichen Teilen und behalten ihre Breite beim
   Wechsel. Reicht sie nicht, wird die Beschriftung gekürzt und nicht über den Rand
   geschoben – die Zahl bleibt stehen. */
.ms-pool-row { display: flex; align-items: center; min-width: 0; }
.ms-pool { display: flex; flex: 1 1 auto; min-width: 0; }
.ms-pool button { flex: 1 1 0; justify-content: center; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.ms-pool .tab-n { flex: none; }
.ms-top-anchor { height: 0; }
.ms-side-tools { display: flex; gap: 6px; padding: 0 1px 4px; }

/* ---------- Messungsliste: suchen, sortieren, filtern (Timeseries.razor) ----------
   Klebt oben in der Rollfläche des Panels: Wer dreihundert Messungen herunterrollt,
   soll den Suchtext ändern können, ohne wieder nach oben zu müssen. */
.ms-tools {
    position: sticky; top: -8px; z-index: 2;
    display: flex; flex-direction: column; gap: 6px;
    margin: -8px -9px 6px; padding: 8px 9px 7px;
    background: var(--panel); border-bottom: 1px solid var(--line);
}
.ms-tools-row { display: flex; gap: 5px; align-items: center; }
.ms-tools .input, .ms-tools .select { height: 28px; font-size: 12px; }
.ms-tools .ms-q { flex: 1 1 auto; min-width: 0; padding: 0 9px; }
.ms-tools .ms-sort { flex: 0 1 132px; width: auto; min-width: 0; padding: 0 24px 0 8px; background-position: right 8px center; }
.ms-filter-btn { position: relative; flex: none; height: 28px; width: 28px !important; }
.ms-filter-btn svg { width: 14px; height: 14px; }
.ms-filter-count {
    position: absolute; top: -5px; right: -5px; min-width: 15px; height: 15px; padding: 0 3px;
    border-radius: 8px; display: grid; place-items: center;
    background: var(--accent); color: var(--on-accent);
    font-family: var(--font-mono); font-size: 9px; font-weight: 700; line-height: 1;
}

.ms-chips { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; }
.ms-chips .chip { height: 24px; font-size: 10.5px; }
.ms-chips .chip:hover { border-color: var(--line-strong); color: var(--text); }
.ms-chips .chip.accent:hover { border-color: var(--accent); color: var(--accent); }
.ms-tools .ms-chip-select {
    width: auto; height: 24px; padding: 0 22px 0 9px; border-radius: 20px;
    font-family: var(--font-mono); font-size: 10.5px; color: var(--text-dim);
    background-color: var(--panel-raised); border-color: var(--line); background-position: right 7px center;
}
.ms-tools .ms-chip-select.on { color: var(--accent); border-color: var(--accent); background-color: var(--accent-soft); }
.ms-reset {
    margin-left: auto; padding: 2px 4px; border: 0; background: none; cursor: pointer;
    font-family: var(--font-ui); font-size: 11px; font-weight: 600; color: var(--accent);
}
.ms-reset:hover { text-decoration: underline; }

.ms-scope {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px;
    font-size: 10.5px; color: var(--text-faint);
}
.ms-scope .mono { font-family: var(--font-mono); }
.ms-scope-warn { color: var(--warn); }

/* Die Liste stößt an ihre Grenze, oder ein Gerät ließ sich nicht lesen. */
.ms-more {
    display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px;
    padding: 6px 8px; border-radius: var(--radius-sm);
    border: 1px solid color-mix(in srgb, var(--warn) 45%, transparent);
    background: color-mix(in srgb, var(--warn) 10%, transparent);
    font-size: 11px; line-height: 1.35; color: var(--text);
}
.ms-more-ico { color: var(--warn); flex: none; }
.ms-more-text { flex: 1 1 160px; min-width: 0; }
.ms-more-hint { font-size: 10.5px; color: var(--text-dim); }
.ms-more .btn-sm { height: 24px; padding: 0 9px; font-size: 11px; }
.ms-older { width: 100%; margin-top: 8px; }

.ms-empty-hint { margin-top: 6px; font-size: 11px; color: var(--text-faint); }
.ms-failed .btn { margin-top: 10px; }

/* Warum eine Messung im Nebenbereich steht: kurz, Kilometerstand oder Geschwindigkeit
   unverändert. In der Warnfarbe, aber blass – ein Hinweis, kein Fehler. */
.ms-why {
    margin-left: 6px;
    font-size: 9.5px;
    color: var(--warn);
    opacity: .85;
}

/* Das Verschieben zwischen Liste und Nebenbereich: dasselbe stille Symbol wie das
   Benennen, beim Überfahren in der Akzentfarbe. */
.ms-item .ms-keep:hover { color: var(--accent); }

/* Der Löschknopf trägt jetzt die Zahl der gewählten Messungen statt nur ein Symbol. */
.ts-del-many { gap: 5px; font-variant-numeric: tabular-nums; }

/* ============================================================================
   Fahrzeugbildkatalog verwalten (Seite /vehicle-catalog)
   Die zweite Quelle des Bildkatalogs: Modelle, die ein Administrator im
   Betrieb nachtraegt und die in jedem Bereich zur Auswahl stehen.
   ========================================================================= */

/* Der Satz ueber der Tabelle. Er steht dort, weil diese Seite eine Frage
   beantworten muss, bevor jemand das erste Feld ausfuellt: wozu die Merkmale
   gut sind, die unter dem Bild abgefragt werden. */
.vca-intro {
    font-size: 12px;
    line-height: 1.5;
    color: var(--text-dim);
    padding: 0 2px 10px;
    max-width: 82ch;
}

/* Die Vorschau in der Liste traegt keinen Rahmen - wie in der Fahrzeugtabelle
   der Flotten und aus demselben Grund: in einer Spalte, in der jede Zeile ein
   Bild hat, ist die Kante ein Gitter im Gitter. */
.vca-thumb { border-color: transparent; background: none; }

.vca-form { display: flex; flex-direction: column; gap: 16px; }

/* Bild und Erlaeuterung nebeneinander: das Bild ist die Hauptsache dieses
   Dialogs, die Felder darunter beschreiben nur, wann es gezeigt wird. */
.vca-pic { display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap; }
.vca-pic .veh-img { flex: none; }

/* Zwei Spalten, solange Platz ist. Die Bezeichnung nimmt beide - sie ist der
   laengste Text im Formular und der einzige, der in einer halben Zeile
   abgeschnitten aussaehe. */
.vca-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px 16px;
}

.vca-wide { grid-column: 1 / -1; }

@media (max-width: 720px) {
    .vca-grid { grid-template-columns: minmax(0, 1fr); }
}

/* ============================================================================
   Geraetebild (Komponente DeviceImage)
   Die Aufnahme einer Bauart - heute der R.Dongle -, freigestellt und ohne
   eigenen Grund. Anders als beim Fahrzeugbild traegt sie deshalb keinen
   Rahmen: das Geraet steht auf der Flaeche des Panels, und ein Kasten darum
   waere eine Kante um nichts.
   ========================================================================= */
.dev-img {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    color: var(--text-faint);
}

/* Die Hoehe fuehrt, die Breite folgt aus dem Bild. Das Geraet ist hochkant -
   eine feste Breite schnitte es an oder stauchte es. */
.dev-img > img { height: 100%; width: auto; display: block; object-fit: contain; }

.dev-img-sm { height: 34px; }
.dev-img-md { height: 96px; }
.dev-img-lg { height: 180px; }

/* Die Silhouette fuer Bauarten ohne Aufnahme: zurueckhaltend und quadratisch,
   sie soll sagen „hier gehoert ein Geraet hin" und nicht um Aufmerksamkeit
   bitten. */
.dev-img-ph { height: 62%; width: auto; aspect-ratio: 1; opacity: .5; }

/* In der Geraeteliste steht das Bild links vom Namen. Ein Abstand nach rechts,
   sonst nichts: die Zeile traegt zwei Textzeilen und ist damit hoch genug, das
   Bild waechst sie nicht. */
.dev-cell { display: flex; align-items: center; gap: 10px; min-width: 0; }

/* Kopf des Bearbeitungsdialogs: das Geraet links, seine Felder rechts. Bei
   schmalem Fenster rutscht das Bild ueber die Felder statt sie auf eine
   Spaltenbreite zu druecken, in der kein Feld mehr lesbar waere. */
.dev-edit-head { display: flex; gap: 18px; align-items: flex-start; margin-top: 8px; }

@media (max-width: 640px) {
    .dev-edit-head { flex-direction: column; align-items: center; }
    .dev-edit-head > .grid { width: 100%; }
}

/* ============================================================================
   R.DiagX Assist – Knopf, Fenster, Andockspalte
   ============================================================================

   Warum es aussieht, wie es aussieht:

   Der Assistent ist das einzige Bauteil dieser Anwendung, das um Aufmerksamkeit
   bitten darf. Alles andere – Panels, Tabellen, Diagramme – hält sich zurück,
   weil es die Arbeit ist; er ist das Angebot daneben, und ein Angebot, das
   niemand sieht, ist keines. Deshalb bekommt er als einziges Element einen
   Farbverlauf, eine plastische Kante und einen langsamen Puls.

   Der Verlauf entsteht aus dem Akzent des Themes plus einem kleinen Anteil
   Violett und Cyan. Kleiner Anteil mit Absicht: Es gibt acht Themes, und ein
   fest eingetragener Verlauf säße in fünfen davon neben der Hausfarbe. So
   bleibt es in jedem Theme die Hausfarbe – nur eine, die schillert.

   Danach wird das Ganze eine Stufe abgedunkelt (--asx-shade). Das ist ein
   eigener Schritt und keine andere Farbe: Der Farbton folgt weiterhin dem
   Theme, er sitzt nur tiefer. Abgedunkelt wird gegen ein dunkles Marineblau
   und nicht gegen Schwarz – Schwarz nimmt dem Verlauf die Sättigung, und aus
   dem Blau würde Grau.

   Die Geometrie steht in vier CSS-Variablen, die JS setzt (js/assist.js).
   Blazor darf beliebig neu zeichnen, ohne dass das Fenster springt.
   ------------------------------------------------------------------------- */

.asx-fab, .asx-panel {
    /* Erst die drei Farbtöne des Verlaufs. Sie heißen `tone` und nicht `h`, weil
       --asx-h schon die Höhe des Fensters ist (gesetzt von js/assist.js) – zwei
       Größen mit ähnlichem Namen und völlig verschiedener Bedeutung sind eine
       Falle für den Nächsten, der hier etwas ändert. */
    --asx-tone1: color-mix(in srgb, var(--accent) 78%, #22D3EE 22%);
    --asx-tone2: var(--accent);
    --asx-tone3: color-mix(in srgb, var(--accent) 76%, #8B5CF6 24%);

    /* … dann eine Stufe tiefer. In zwei Schritten und nicht in einem, damit
       man den Farbton und die Tiefe getrennt verstellen kann. */
    --asx-g1: color-mix(in srgb, var(--asx-tone1) 82%, #0A1526 18%);
    --asx-g2: color-mix(in srgb, var(--asx-tone2) 82%, #0A1526 18%);
    --asx-g3: color-mix(in srgb, var(--asx-tone3) 82%, #0A1526 18%);

    --asx-grad: linear-gradient(135deg, var(--asx-g1) 0%, var(--asx-g2) 48%, var(--asx-g3) 100%);
}

/* ---------- Der Knopf, solange das Fenster zu ist ---------- */

.asx-fab {
    position: fixed; right: 22px; bottom: 22px; z-index: 90;
    display: flex; align-items: center; gap: 8px;
    height: 42px; padding: 0 16px 0 14px;
    border: 0;
    border-radius: 999px;
    background: var(--asx-grad);
    color: var(--on-accent, #fff);
    font-family: var(--font-ui); font-size: 12.5px; font-weight: 700; letter-spacing: .02em;
    cursor: pointer;
    overflow: hidden;
    isolation: isolate;

    /* Die Plastik: eine helle Innenkante oben, eine dunkle unten, darunter zwei
       Schatten – einer eng am Körper für die Kante, einer weit für die Höhe.
       Der dritte Eintrag ist der Pulsring; er wird animiert. */
    box-shadow:
        inset 0 1px 0 rgba(255,255,255,.34),
        inset 0 -1px 0 rgba(0,0,0,.22),
        0 2px 4px rgba(0,0,0,.18),
        0 12px 26px -10px var(--accent-glow),
        0 0 0 0 color-mix(in srgb, var(--accent) 50%, transparent);

    animation: asx-pulsering 3.2s cubic-bezier(.3,0,.2,1) infinite;
    transition: transform .16s cubic-bezier(.2,.9,.3,1.3), filter .16s;
}

/* Der Puls. Ein Ring, der aus dem Knopf herauswächst und verblasst – er liegt
   außerhalb des Körpers und deckt deshalb nichts zu. Drei Sekunden Abstand: oft
   genug, dass man ihn im Augenwinkel bemerkt, selten genug, dass er nicht
   nervt, während jemand eine Kurve liest. */
@keyframes asx-pulsering {
    0% {
        box-shadow:
            inset 0 1px 0 rgba(255,255,255,.34), inset 0 -1px 0 rgba(0,0,0,.22),
            0 2px 4px rgba(0,0,0,.18), 0 12px 26px -10px var(--accent-glow),
            0 0 0 0 color-mix(in srgb, var(--accent) 50%, transparent);
    }
    60%, 100% {
        box-shadow:
            inset 0 1px 0 rgba(255,255,255,.34), inset 0 -1px 0 rgba(0,0,0,.22),
            0 2px 4px rgba(0,0,0,.18), 0 12px 26px -10px var(--accent-glow),
            0 0 0 18px transparent;
    }
}

.asx-fab:hover {
    transform: translateY(-2px) scale(1.03);
    filter: brightness(1.08) saturate(1.06);
}

.asx-fab:active { transform: translateY(0) scale(.99); }

/* Der Lichtstreifen, der alle paar Sekunden über den Knopf läuft. Er macht aus
   einer farbigen Fläche eine Oberfläche – dasselbe, was ein Glanzlicht auf
   einem Knopf aus Kunststoff tut. */
.asx-fab-sheen {
    position: absolute; top: 0; bottom: 0; left: -60%; width: 42%;
    background: linear-gradient(100deg, transparent, rgba(255,255,255,.4), transparent);
    transform: skewX(-18deg);
    pointer-events: none;
    animation: asx-sheen 5.5s ease-in-out infinite;
}

@keyframes asx-sheen {
    0%, 64%  { left: -60%; }
    90%, 100% { left: 130%; }
}

.asx-fab .asx-roki { width: 21px; height: 21px; }

.asx-fab-seal {
    display: inline-flex; align-items: center;
    padding-left: 7px; margin-left: 1px;
    border-left: 1px solid rgba(255,255,255,.3);
    opacity: .92;
}
.asx-fab-seal .asx-shield { width: 14px; height: 14px; }

/* ---------- Das Fenster ---------- */

.asx-panel {
    position: fixed; z-index: 90;
    left: var(--asx-x, 40px); top: var(--asx-y, 40px);
    width: var(--asx-w, 420px); height: var(--asx-h, 560px);

    /* Die eingestellte Höhe gilt nur, solange sie ins Sichtfenster passt.
       Sonst steht das Eingabefeld unterhalb des unteren Randes – und ein
       fliegendes Fenster kann man nicht herunterrollen, um es zu erreichen. Was
       vom Platz zwischen Oberkante des Fensters und Bildschirmrand übrig
       bleibt, ist die Obergrenze; der Verlauf rollt dann innen. */
    max-height: calc(100dvh - var(--asx-y, 40px) - 12px);

    display: flex; flex-direction: column; min-height: 0;
    background: var(--panel);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius);

    /* Dieselbe Plastik wie am Knopf, nur größer gedacht: eine helle Oberkante,
       ein enger Kantenschatten, ein weiter Höhenschatten und ein Hauch
       Akzentfarbe im Streulicht. */
    box-shadow:
        inset 0 1px 0 color-mix(in srgb, var(--text) 8%, transparent),
        0 2px 6px rgba(0,0,0,.2),
        0 26px 60px -22px rgba(0,0,0,.6),
        0 0 42px -18px var(--accent-glow);
    overflow: hidden;

    /* Das Fenster kommt aus der Ecke, in der der Knopf steht, und nicht aus dem Nichts:
       Es wächst aus der Geste, die es geöffnet hat. Deshalb der Ursprung unten rechts
       und die Verschiebung dorthin. Die Unschärfe am Anfang macht aus dem Einblenden
       eine Bewegung nach vorn statt eines Aufhellens. */
    transform-origin: 100% 100%;
    animation: asx-in .34s cubic-bezier(.16,.84,.3,1.02);
}

@keyframes asx-in {
    0%   { opacity: 0; transform: translate(26px, 38px) scale(.9); filter: blur(7px); }
    60%  { opacity: 1; }
    100% { opacity: 1; transform: none; filter: blur(0); }
}

/* Und denselben Weg zurück, nur kürzer: Beim Öffnen wartet man auf das Fenster, beim
   Schließen hat man es schon hinter sich. Ein gleich langes Ausblenden fühlt sich zäh
   an. `forwards` hält den Endzustand, bis der Server das Element entfernt – sonst
   blitzte es im letzten Bild wieder auf. */
.asx-panel.is-closing {
    animation: asx-out .2s cubic-bezier(.4,0,.85,.45) forwards;
    pointer-events: none;
}

@keyframes asx-out {
    from { opacity: 1; transform: none; filter: blur(0); }
    to   { opacity: 0; transform: translate(22px, 32px) scale(.92); filter: blur(6px); }
}

/* Während des Ziehens: keine Übergänge, sonst läuft das Fenster dem Zeiger
   hinterher, und keine Textauswahl, sonst markiert die Geste die halbe Seite. */
.asx-panel.is-dragging { transition: none; user-select: none; }
.asx-panel.is-dragging * { user-select: none; }

/* Eingeklappt bleibt nur die Titelleiste stehen – als Merkzettel, dass da ein
   Gespräch offen ist, und als Griff, es wieder aufzumachen. */
.asx-panel.is-min { height: auto !important; }

/* ---------- Angedockt ----------
   Dasselbe Fenster, nur als Spalte der Seite: kein Schatten, keine runden
   Ecken, keine eigene Breite. Es sitzt im Raster der Hülle (MainLayout) und
   bekommt dort seine Maße – hier wird abgeräumt, was ein fliegendes Fenster
   ausmacht.

   Die Höhe ist das Sichtfenster und NICHT der Inhalt, und das ist der ganze
   Punkt: Mit `height: auto` wuchs die Spalte mit dem Gespräch. Bei einem langen
   Verlauf und einem niedrigen Browserfenster stand das Eingabefeld dann irgendwo
   weit unten auf der Seite – man musste die ganze Seite herunterrollen, um eine
   Frage zu stellen, und beim Lesen der Antwort wieder hinauf.

   `sticky` mit `top: 0` und voller Sichtfensterhöhe löst das so, wie es die
   Navigationsleiste links schon tut: Die Spalte bleibt stehen, während die Seite
   darunter rollt. Der Verlauf rollt dann INNEN (.asx-log ist flex:1 mit
   overflow-y:auto), und Titelleiste wie Eingabefeld sind immer zu sehen.

   `dvh` statt `vh` wegen der Adressleiste auf Telefonen, die beim Rollen
   ein- und ausfährt; `vh` steht als Rückfall davor. */
.asx-panel.is-docked {
    position: sticky; top: 0; z-index: 1;
    align-self: start;
    left: auto;
    width: auto;
    height: 100vh;
    height: 100dvh;

    /* Die Obergrenze des fliegenden Fensters rechnet mit --asx-y, und die steht
       angedockt noch auf dem letzten Ort vor dem Andocken. Hier zählt allein die
       Sichtfensterhöhe. */
    max-height: none;
    border-radius: 0;
    border-width: 0 0 0 1px;
    box-shadow: none;
    animation: none;
}
/* Eingeklappt bleibt nur die Titelleiste – sie darf die Spalte nicht auf volle
   Höhe aufspannen. Die allgemeine Regel oben setzt dafür schon `height: auto`
   mit !important; hier bleibt nur die Ausrichtung nach oben. */
.asx-panel.is-docked.is-min { align-self: start; height: auto !important; }
.asx-panel.is-docked .asx-head { cursor: default; }

.app-shell.assist-docked { grid-template-columns: var(--rail-w) 1fr var(--asx-dock-w, 380px); }
.app-shell.assist-docked.nav-collapsed { grid-template-columns: var(--rail-w-min) 1fr var(--asx-dock-w, 380px); }

/* Eingeklappt bleibt die Spalte bestehen – sie wird nur schmal.

   Dass sie bestehen bleibt, ist der Punkt: Ein angedocktes Fenster sitzt IM Raster
   der Hülle. Nimmt man ihm beim Einklappen die Spalte, steht es in einer, die es
   nicht mehr gibt, und rutscht unter den Inhalt – der Assistent wäre verschwunden,
   ohne geschlossen zu sein, und der Knopf käme trotzdem nicht zurück.

   Schmal statt voller Dockbreite, weil Einklappen Platz zurückgeben soll. Die
   eingestellte Breite ist die Obergrenze: Wer die Spalte enger gezogen hat, bekommt
   eingeklappt keine breitere. Was dann nicht mehr hinpasst, kürzt die Titelleiste
   selbst (.asx-peek-text). */
.app-shell.assist-docked.assist-min { grid-template-columns: var(--rail-w) 1fr min(var(--asx-dock-w, 380px), 252px); }
.app-shell.assist-docked.assist-min.nav-collapsed { grid-template-columns: var(--rail-w-min) 1fr min(var(--asx-dock-w, 380px), 252px); }

/* ---------- Titelleiste ---------- */

/* Die Kopfzeile trägt die CI-Fläche und nicht mehr den schillernden Akzentverlauf.
   Sie steht damit in derselben Farbe wie das Logofeld oben links – das Fenster liest
   sich als Teil des Portals und nicht als Fremdkörper darüber. Der Akzentverlauf
   (--asx-grad) bleibt dem Knopf und den Fragenblasen: Dort ist er das Angebot, hier
   wäre er eine zweite Hausfarbe. */
.asx-head {
    display: flex; align-items: center; gap: 9px;
    padding: 9px 9px 9px 12px;
    background: var(--brand-grad);
    color: #fff;
    cursor: grab;
    flex: none;
    position: relative;
}

/* Die Glanzkante oben – dieselbe Linie wie am Knopf, damit beide erkennbar
   dasselbe Ding sind. */
.asx-head::after {
    content: ""; position: absolute; left: 0; right: 0; top: 0; height: 1px;
    background: rgba(255,255,255,.35);
}

.asx-panel.is-dragging .asx-head { cursor: grabbing; }

/* ---------- Das Wappen ----------
   Der Schlagschatten ist der billigste Teil der Raeumlichkeit und der wirksamste: Er
   hebt den Helm von seinem Untergrund ab, ohne dass die Zeichnung selbst etwas dafuer
   tun muss. Er ist bewusst eng und dunkel – ein weicher grosser Schatten sieht auf
   zwanzig Pixeln nach Unschaerfe aus.

   `overflow: visible`, weil die Hoerner bis an den Rand der Zeichenflaeche reichen und
   der Schatten darueber hinausgeht. */
.asx-roki {
    overflow: visible;
    filter: drop-shadow(0 1px 1.5px rgba(0, 0, 0, .55));
}

/* ---------- Der Blick folgt dem Zeiger ----------
   Zwei Ebenen, die sich verschieden weit bewegen: die Maske dreimal so weit wie der Helm
   darunter. Diese Verschiebung GEGENEINANDER erzeugt die Tiefe – zwei gleich bewegte
   Ebenen waeren nur ein wackelndes Bild, und eine allein sahe aus wie ein verrutschter
   Aufkleber.

   Die Auslenkung ist klein: eine gute Einheit von vierundzwanzig, also gut vier Prozent
   der Zeichenflaeche. Mehr, und die Maske faellt aus dem Helm.

   `px` sind in einer SVG mit viewBox keine Bildschirmpunkte, sondern Einheiten des
   Koordinatensystems – deshalb bleibt die Auslenkung in jeder Groesse dieselbe Geste.

   --roki-dx/--roki-dy setzt js/assist.js. Kommt nichts, steht 0 da und alles still. */
.roki-face {
    transform: translate(calc(var(--roki-dx, 0) * 1.05px), calc(var(--roki-dy, 0) * 0.8px));
    transition: transform .22s cubic-bezier(.2, .8, .3, 1);
}

.roki-shell {
    transform: translate(calc(var(--roki-dx, 0) * 0.35px), calc(var(--roki-dy, 0) * 0.28px));
    transition: transform .3s cubic-bezier(.2, .8, .3, 1);
}

/* Die Augen atmen. Sehr langsam und mit kleinem Hub: Was in einer Kopfzeile blinkt,
   zieht den Blick von der Arbeit ab – und genau das soll es nicht. */
.roki-glow {
    animation: roki-breathe 3.6s ease-in-out infinite;
    transform-origin: center;
}

@keyframes roki-breathe {
    0%, 100% { opacity: .62; }
    50%      { opacity: 1; }
}

/* Der Glanz wandert ueber die Hoerner. Die zweite Seite laeuft versetzt, sonst blitzen
   beide im Gleichtakt und es sieht nach Blinklicht aus statt nach Lichteinfall. */
.roki-glint {
    opacity: .12;
    animation: roki-glint 5.2s ease-in-out infinite;
}

.roki-glint.is-late { animation-delay: 2.6s; }

@keyframes roki-glint {
    0%, 62%, 100% { opacity: .12; }
    18%           { opacity: .78; }
}

/* ---------- Das Winken ----------
   Im Ruhezustand gibt es die Hand nicht: keine Deckkraft, also auch kein
   halbdurchsichtiger Schemen hinter dem Kopf. Erst der Klick holt sie hervor.

   Gedreht wird um das Handgelenk und nicht um die Mitte eines Kastens: Eine Hand winkt
   aus dem Gelenk. Dafür `view-box` mit Ursprung 0 0 – dann rechnet jede Verschiebung im
   Koordinatensystem der Zeichnung, und der Drehpunkt ist genau der Punkt, den das
   translate im Schlüsselbild setzt.

   (Hier stand einmal ein Schwerthieb. Wer einen Assistenten anklickt, erwartet keinen
   Hieb – und ein Zeichen, das lächelt und dann zuschlägt, ist unheimlicher als eines,
   das von vornherein finster dreinsieht.) */
.roki-arm { opacity: 0; }
.roki-hand { transform-box: view-box; transform-origin: 0 0; }

.asx-mark, .asx-hello-mark { cursor: pointer; }

/* Beim Winken schaut es den an, der geklickt hat, und nicht mehr dem Zeiger hinterher. */
.asx-roki.is-waving .roki-face,
.asx-roki.is-waving .roki-shell { transition: none; }

.asx-roki.is-waving .roki-arm  { animation: roki-arm 2s linear; }
.asx-roki.is-waving .roki-hand { animation: roki-wave 2s; }

/* Der ganze Kopf nickt einmal mit – ein Winken aus einem starren Gesicht sieht aus wie
   ein Mechanismus und nicht wie ein Gruß. */
.asx-roki.is-waving {
    transform-origin: 50% 70%;
    animation: roki-nod 2s ease-in-out;
}

/* Die Augen gehen dabei an: heller und ohne das langsame Atmen, das sonst laeuft. */
.asx-roki.is-waving .roki-glow { animation: roki-hello 2s ease-in-out; }

@keyframes roki-arm {
    0%, 4%     { opacity: 0; }
    9%, 92%    { opacity: 1; }
    100%       { opacity: 0; }
}

@keyframes roki-wave {
    /* Hinter dem Kopf: klein, weggedreht, nicht zu sehen. */
    0%   { transform: translate(19px, 15px) rotate(40deg) scale(.5);
           animation-timing-function: cubic-bezier(.2,.9,.3,1.1); }

    /* Sie kommt an der rechten Wange hervor und steht aufrecht. */
    14%  { transform: translate(20.5px, 12.5px) rotate(6deg) scale(1); }

    /* Dreimal hin und her, aus dem Gelenk. Der Ausschlag wird kleiner, wie bei jedem
       Winken, das aufhoert. */
    28%  { transform: translate(20.5px, 12.5px) rotate(-22deg) scale(1); }
    42%  { transform: translate(20.5px, 12.5px) rotate(20deg) scale(1); }
    56%  { transform: translate(20.5px, 12.5px) rotate(-16deg) scale(1); }
    70%  { transform: translate(20.5px, 12.5px) rotate(14deg) scale(1); }
    82%  { transform: translate(20.5px, 12.5px) rotate(-6deg) scale(1);
           animation-timing-function: cubic-bezier(.4,0,.8,.6); }

    /* Und wieder weg. */
    100% { transform: translate(19px, 15px) rotate(40deg) scale(.5); }
}

/* Ein Nicken, kein Sprung: Der Kopf neigt sich kurz zur Seite und kommt zurueck. */
@keyframes roki-nod {
    0%, 100% { transform: none; }
    16%      { transform: rotate(-5deg) scale(1.04); }
    46%      { transform: rotate(3deg) scale(1.02); }
    76%      { transform: rotate(-2deg) scale(1.02); }
}

/* Die Augen an, ohne Farbwechsel: Es ist derselbe Blick, nur wacher. */
@keyframes roki-hello {
    0%, 100% { opacity: .62; }
    14%, 86% { opacity: 1; filter: brightness(1.25); }
}

.asx-mark { display: inline-flex; opacity: .95; }
.asx-mark .asx-roki { width: 20px; height: 20px; }

.asx-titles { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }

.asx-title {
    display: flex; align-items: center; gap: 7px;
    font-family: var(--font-ui); font-size: 12.5px; font-weight: 700; letter-spacing: .02em;
}

/* ---------- Das Datenschutzsiegel ----------
   Auf dem farbigen Band steht es hell auf durchscheinendem Weiß statt grün auf
   dunkel: Grün auf Blau ist bei elf Pixeln Schrift keine Farbe mehr, sondern
   ein Fleck. Die Aussage trägt das Schild und das Wort. */

.asx-seal {
    display: inline-flex; align-items: center; gap: 3px;
    height: 17px; padding: 0 6px 0 4px;
    border-radius: 999px;
    border: 1px solid rgba(255,255,255,.34);
    background: rgba(255,255,255,.16);
    color: inherit;
    font-size: 9.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
    cursor: help;
}
.asx-seal .asx-shield { width: 11px; height: 11px; }

.asx-seal.is-warn {
    border-color: color-mix(in srgb, var(--warn) 70%, white);
    background: color-mix(in srgb, var(--warn) 55%, transparent);
}

/* ---------- Die Kontextzeile ---------- */

.asx-ctx {
    display: flex; align-items: center; gap: 5px;
    padding: 0; border: 0; background: none;
    color: rgba(255,255,255,.78); font-size: 10.5px; text-align: left;
    cursor: pointer; min-width: 0;
}
/* Weiß und nicht --on-accent: Die Kopfzeile trägt jetzt in JEDEM Theme die dunkle
   CI-Fläche. In zwei Themes ist --on-accent dunkel (dort ist der Akzent hell) – die
   Zeile wäre beim Überfahren unsichtbar geworden. */
.asx-ctx:hover { color: #fff; }

.asx-ctx-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.asx-ctx-dot {
    width: 5px; height: 5px; border-radius: 50%; flex: none;
    background: currentColor;
    box-shadow: 0 0 0 3px rgba(255,255,255,.18);
}

/* ---------- Der Blick durchs eingeklappte Fenster ---------- */

.asx-peek {
    display: flex; align-items: center; gap: 6px; min-width: 0;
    font-size: 10.5px; color: rgba(255,255,255,.8); font-style: italic;
}
.asx-peek-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.asx-peek-live { display: inline-flex; gap: 3px; flex: none; }
.asx-peek-live i {
    width: 4px; height: 4px; border-radius: 50%; background: currentColor;
    animation: asx-bounce 1.2s infinite ease-in-out;
}
.asx-peek-live i:nth-child(2) { animation-delay: .18s; }
.asx-peek-live i:nth-child(3) { animation-delay: .36s; }

.asx-icon {
    width: 24px; height: 24px; flex: none;
    display: grid; place-items: center;
    border: 0; border-radius: var(--radius-sm);
    background: rgba(255,255,255,.1); color: inherit;
    font-size: 12px; line-height: 1; cursor: pointer;
    transition: background .12s;
}
.asx-icon:hover { background: rgba(255,255,255,.26); }

/* ---------- Werkzeugleiste ---------- */

.asx-tools {
    display: flex; align-items: center; gap: 6px; flex: none;
    padding: 6px 9px;
    background: var(--panel-raised);
    border-bottom: 1px solid var(--line);
}
.asx-tools-gap { flex: 1; }

.asx-tool {
    display: inline-flex; align-items: center; gap: 5px;
    height: 23px; padding: 0 8px;
    border: 1px solid var(--line-strong); border-radius: 999px;
    background: var(--panel); color: var(--text-dim);
    font-size: 10.5px; font-weight: 600; cursor: pointer;
    transition: border-color .13s, color .13s, background .13s;
}
.asx-tool:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.asx-tool.is-on { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.asx-tool:disabled { opacity: .4; cursor: default; }
.asx-tool .asx-ico { width: 12px; height: 12px; }

/* Die Kugel vor der Denktiefe – dasselbe Zeichen wie am Gedankengang, damit man
   die Einstellung und ihre Wirkung als dasselbe erkennt. */
.asx-tool-orb {
    width: 7px; height: 7px; border-radius: 50%; flex: none;
    background: var(--asx-grad);
    box-shadow: 0 0 0 2px var(--accent-soft);
}

.asx-caretdown {
    width: 0; height: 0; margin-left: 1px;
    border-left: 3px solid transparent; border-right: 3px solid transparent;
    border-top: 4px solid currentColor; opacity: .7;
}

.asx-levelwrap { position: relative; }

.asx-menu {
    position: absolute; top: calc(100% + 5px); left: 0; z-index: 5;
    min-width: 236px;
    display: flex; flex-direction: column;
    background: var(--panel-raised);
    border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
    box-shadow: var(--shadow);
    overflow: hidden;
    animation: asx-in .13s ease-out;
}

.asx-menu-item {
    display: flex; flex-direction: column; gap: 1px;
    padding: 7px 10px; text-align: left;
    border: 0; border-bottom: 1px solid var(--line);
    background: none; cursor: pointer;
}
.asx-menu-item:last-child { border-bottom: 0; }
.asx-menu-item:hover { background: var(--panel-inset); }
.asx-menu-item.is-sel { background: var(--accent-soft); }

.asx-menu-name { font-size: 11.5px; font-weight: 600; color: var(--text); }
.asx-menu-item.is-sel .asx-menu-name { color: var(--accent); }
.asx-menu-hint { font-size: 10px; color: var(--text-dim); line-height: 1.4; }

/* ---------- Der Verlauf ---------- */

.asx-log {
    flex: 1; min-height: 0; overflow-y: auto;
    padding: 12px 13px;
    display: flex; flex-direction: column; gap: 12px;
    scrollbar-width: thin;
}

/* ---------- Wenn der Assistent nichts sieht ----------
   Im Warnton und nicht im Grau des Hinweises: Das ist der Zustand, in dem eine Frage
   nichts bringt, und er muss auffallen, bevor jemand tippt. Er steht an der Stelle der
   Begruessung, damit man ihn nicht uebersehen kann. */
.asx-blind {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 14px;
    border: 1px solid var(--warn);
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--warn) 9%, transparent);
}

.asx-blind-mark { color: var(--warn); }
.asx-blind-mark .asx-ico { width: 22px; height: 22px; }
.asx-blind-head { font-size: 13.5px; font-weight: 600; color: var(--warn); line-height: 1.35; }
.asx-blind-sub { font-size: 12px; line-height: 1.55; color: var(--text-dim); }

.asx-blind-list { display: flex; flex-direction: column; gap: 4px; margin-top: 2px; }
.asx-blind-list-head { font-size: 11px; font-weight: 600; color: var(--text-dim); }
.asx-blind-list ul { margin: 0; padding-left: 16px; display: flex; flex-direction: column; gap: 2px; }
.asx-blind-list li { font-size: 11.5px; line-height: 1.45; color: var(--text-dim); }

/* Der Streifen ueber dem Eingabefeld. Er bleibt auch mitten im Gespraech stehen: Wer die
   Seite wechselt, nimmt das Fenster mit und die Auskunft nicht. */
.asx-blind-bar {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 10px;
    margin: 0 10px 6px;
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--warn) 12%, transparent);
    color: var(--warn);
    font-size: 11.5px;
    line-height: 1.4;
}

.asx-blind-bar .asx-ico { width: 14px; height: 14px; flex: none; }

/* Ein gesperrtes Eingabefeld muss gesperrt aussehen, sonst tippt man hinein und wundert
   sich, dass nichts erscheint. */
.asx-text:disabled { opacity: .55; cursor: not-allowed; }

/* ---------- Die Begrüßung ----------
   Solange nichts gefragt wurde, ist dieser Bereich kein Text mit einem Symbol davor,
   sondern eine Startseite: Das Wappen steht mittig und groß, darunter die Frage und
   der Satz dazu. Sobald das Gespräch läuft, verschwindet das Ganze ohnehin. */
.asx-hello {
    display: flex; flex-direction: column; align-items: center;
    gap: 9px; padding: 18px 0 8px;
}

.asx-hello-head, .asx-hello-sub { text-align: center; }

/* Das Warnfeld bleibt ein Absatz und keine Mitte: Fließtext, der zentriert steht,
   liest sich schlechter, und hier steht der Grund, warum man vielleicht gar nicht
   fragen sollte. */
.asx-hello .asx-priv { text-align: left; align-self: stretch; }
.asx-hello-mark { color: var(--accent); }
/* Mittig und groß: Auf dieser einen Fläche darf das Wappen wirken – hier ist Platz,
   und hier stellt sich der Assistent vor. In der Kopfzeile bleibt es bei zwanzig
   Pixeln, dort beschriftet es nur. */
.asx-hello-mark .asx-roki { width: 54px; height: 54px; }
.asx-hello-head { font-family: var(--font-ui); font-size: 13.5px; font-weight: 600; }
.asx-hello-sub { font-size: 11.5px; line-height: 1.55; color: var(--text-dim); }

/* ---------- Die Zusage im leeren Fenster ---------- */

.asx-priv {
    display: flex; gap: 9px; align-items: flex-start;
    margin-top: 4px; padding: 10px 11px;
    border: 1px solid color-mix(in srgb, var(--ok) 30%, var(--line));
    border-left: 2px solid var(--ok);
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--ok) 7%, var(--panel-inset));
}

.asx-priv.is-warn {
    border-color: color-mix(in srgb, var(--warn) 35%, var(--line));
    border-left-color: var(--warn);
    background: color-mix(in srgb, var(--warn) 8%, var(--panel-inset));
}

.asx-priv-mark { display: inline-flex; color: var(--ok); padding-top: 1px; }
.asx-priv.is-warn .asx-priv-mark { color: var(--warn); }
.asx-priv-mark .asx-shield { width: 16px; height: 16px; }

.asx-priv-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.asx-priv-head { font-size: 11.5px; font-weight: 600; line-height: 1.4; }
.asx-priv-sub { font-size: 11px; line-height: 1.5; color: var(--text-dim); }
.asx-priv-host { font-size: 10px; color: var(--text-faint); margin-top: 1px; }

/* ---------- Wortwechsel ---------- */

.asx-turn {
    display: flex; flex-direction: column; gap: 6px;
    animation: asx-rise .22s ease-out;
}

@keyframes asx-rise {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: none; }
}

.asx-turn.is-me { align-items: flex-end; }

.asx-bubble {
    max-width: 88%;
    padding: 7px 11px;
    border-radius: 12px 12px 3px 12px;
    background: var(--asx-grad, var(--accent));
    color: var(--on-accent, #fff);
    font-size: 12px; line-height: 1.5;
    white-space: pre-wrap; word-break: break-word;
    box-shadow: inset 0 1px 0 rgba(255,255,255,.22), 0 2px 8px -4px var(--accent-glow);
}
.asx-panel { --asx-grad-bubble: var(--asx-grad); }

/* ---------- Der Gedankengang ----------
   Voreingestellt zugeklappt. Sichtbar ist nur, DASS gedacht wird – der
   Gedankengang ist die Vorarbeit und nicht das Ergebnis. Offen schöbe er die
   Antwort aus dem Bild und ließe sie, während sie entsteht, nach unten wandern:
   Man liest dem Modell beim Denken zu, statt seine Auskunft zu bekommen. Wer
   ihn sehen will, klappt ihn über das Augensymbol auf. */

.asx-think {
    align-self: flex-start;
    border: 1px solid color-mix(in srgb, var(--accent) 22%, var(--line));
    border-radius: 999px;
    background: color-mix(in srgb, var(--accent) 5%, var(--panel-inset));
    overflow: hidden;
    transition: border-radius .15s;
}
.asx-think.is-open { border-radius: var(--radius-sm); align-self: stretch; }

.asx-think-head {
    display: flex; align-items: center; gap: 7px; width: 100%;
    padding: 4px 9px 4px 7px;
    border: 0; background: none; cursor: pointer;
    color: var(--text-dim); font-size: 10.5px; letter-spacing: .02em;
}
.asx-think-head:hover { color: var(--text); }

/* Die Kugel: im Ruhezustand ein Punkt in Akzentfarbe, beim Denken ein
   atmender Kern mit Hof. */
.asx-think-orb {
    width: 9px; height: 9px; border-radius: 50%; flex: none;
    background: var(--asx-grad, var(--accent));
}
.asx-think.is-live .asx-think-orb {
    animation: asx-orb 1.4s ease-in-out infinite;
}

@keyframes asx-orb {
    0%, 100% { transform: scale(.82); box-shadow: 0 0 0 0 var(--accent-soft); }
    50%      { transform: scale(1);   box-shadow: 0 0 0 5px transparent; }
}

.asx-think-label { white-space: nowrap; }
.asx-think.is-live .asx-think-label { color: var(--accent); font-weight: 600; }

/* Drei Balken wie an einer Aussteuerungsanzeige. Sie sagen „es läuft" ohne zu
   behaupten, wie weit es noch ist – ein Fortschrittsbalken wäre hier eine
   Lüge, niemand weiß, wie lange ein Modell nachdenkt. */
.asx-think-wave { display: inline-flex; align-items: flex-end; gap: 2px; height: 10px; }
.asx-think-wave i {
    width: 2px; border-radius: 1px; background: var(--accent);
    animation: asx-wave 1s ease-in-out infinite;
}
.asx-think-wave i:nth-child(1) { animation-delay: 0s; }
.asx-think-wave i:nth-child(2) { animation-delay: .15s; }
.asx-think-wave i:nth-child(3) { animation-delay: .3s; }

@keyframes asx-wave {
    0%, 100% { height: 3px; opacity: .45; }
    50%      { height: 10px; opacity: 1; }
}

.asx-think-ms { font-size: 9.5px; color: var(--text-faint); }

.asx-think-eye { display: inline-flex; margin-left: auto; opacity: .55; }
.asx-think-eye svg { width: 13px; height: 13px; }
.asx-think-head:hover .asx-think-eye { opacity: 1; }
.asx-think.is-open .asx-think-eye { color: var(--accent); opacity: 1; }

.asx-think-body {
    padding: 2px 10px 9px; margin: 0;
    max-height: 200px; overflow-y: auto;
    color: var(--text-faint);
    font-family: var(--font-mono); font-size: 10px; line-height: 1.55;
    white-space: pre-wrap; word-break: break-word;
    border-top: 1px dashed color-mix(in srgb, var(--accent) 20%, var(--line));
    animation: asx-unfold .16s ease-out;
}

@keyframes asx-unfold {
    from { opacity: 0; }
    to   { opacity: 1; }
}

/* ---------- Die Antwort ---------- */

.asx-answer { font-size: 12px; line-height: 1.6; color: var(--text); word-break: break-word; }

.asx-answer > :first-child { margin-top: 0; }
.asx-answer > :last-child { margin-bottom: 0; }
.asx-answer p { margin: 0 0 7px; }

.asx-answer .asx-h {
    margin: 12px 0 5px;
    font-family: var(--font-ui); font-size: 11px; font-weight: 700;
    letter-spacing: .07em; text-transform: uppercase;
    color: var(--text-dim);
}

.asx-answer .asx-ul,
.asx-answer .asx-ol { margin: 0 0 7px; padding-left: 17px; }
.asx-answer li { margin-bottom: 3px; }
.asx-answer .asx-ul { list-style: none; padding-left: 13px; }
.asx-answer .asx-ul > li { position: relative; }
.asx-answer .asx-ul > li::before {
    content: ""; position: absolute; left: -11px; top: .58em;
    width: 4px; height: 4px; border-radius: 50%;
    background: color-mix(in srgb, var(--accent) 70%, transparent);
}

.asx-answer strong { font-weight: 700; color: var(--text); }

.asx-answer code {
    padding: 1px 4px; border-radius: 4px;
    background: var(--panel-inset); border: 1px solid var(--line);
    font-family: var(--font-mono); font-size: 10.5px;
}

.asx-answer .asx-code {
    margin: 0 0 7px; padding: 8px 10px;
    background: var(--panel-inset); border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    overflow-x: auto;
}
.asx-answer .asx-code code { padding: 0; border: 0; background: none; }

/* Tabellen sind der Grund, warum in dieser Antwort überhaupt Markdown erlaubt
   ist: Vier Messungen nebeneinander sind hier in zwei Sekunden erfasst. */
.asx-tablewrap { overflow-x: auto; margin: 0 0 8px; }

.asx-table {
    width: 100%; border-collapse: collapse;
    font-size: 11px; font-variant-numeric: tabular-nums;
}
.asx-table th, .asx-table td {
    padding: 4px 8px; text-align: left;
    border-bottom: 1px solid var(--line);
    white-space: nowrap;
}
.asx-table th {
    color: var(--text-dim); font-weight: 600;
    font-size: 9.5px; letter-spacing: .05em; text-transform: uppercase;
    border-bottom-color: var(--line-strong);
}
.asx-table tr:last-child td { border-bottom: 0; }

/* Der Schreibbalken am Ende des Textes, während er entsteht. */
.asx-caret {
    display: inline-block; width: 6px; height: 12px;
    margin-left: 2px; vertical-align: -1px;
    background: var(--accent); border-radius: 1px;
    animation: asx-blink 1s steps(2, start) infinite;
}

@keyframes asx-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }

/* ---------- Warten, bevor das erste Zeichen da ist ---------- */

.asx-dots { display: inline-flex; gap: 4px; padding: 3px 0; }
.asx-dots i {
    width: 5px; height: 5px; border-radius: 50%;
    background: var(--accent);
    animation: asx-bounce 1.2s infinite ease-in-out;
}
.asx-dots i:nth-child(2) { animation-delay: .18s; }
.asx-dots i:nth-child(3) { animation-delay: .36s; }

@keyframes asx-bounce {
    0%, 80%, 100% { opacity: .25; transform: translateY(0); }
    40%           { opacity: 1;   transform: translateY(-3px); }
}

.asx-error {
    display: flex; gap: 7px; align-items: flex-start;
    padding: 8px 10px; border-radius: var(--radius-sm);
    border: 1px solid color-mix(in srgb, var(--fault) 40%, var(--line));
    background: color-mix(in srgb, var(--fault) 9%, transparent);
    color: var(--text); font-size: 11px; line-height: 1.5;
}

/* ---------- Vorschläge ---------- */

.asx-chips { display: flex; flex-wrap: wrap; gap: 5px; padding: 0 13px 4px; flex: none; }

.asx-chip {
    height: 23px; padding: 0 9px;
    border: 1px solid var(--line-strong); border-radius: 999px;
    background: var(--panel-inset); color: var(--text-dim);
    font-size: 10.5px; cursor: pointer;
    transition: border-color .13s, color .13s, background .13s, transform .13s;
}
.asx-chip:hover {
    border-color: var(--accent); color: var(--accent);
    background: var(--accent-soft);
    transform: translateY(-1px);
}

/* ---------- Eingabe ---------- */

.asx-input {
    display: flex; gap: 7px; align-items: flex-end; flex: none;
    padding: 9px 11px;
    border-top: 1px solid var(--line);
    background: var(--panel-raised);
}

.asx-text {
    flex: 1; min-width: 0;
    height: 32px; max-height: 140px; padding: 7px 10px;
    border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
    background: var(--panel); color: var(--text);
    font-family: inherit; font-size: 12px; line-height: 1.4;
    resize: none; overflow-y: auto;
    transition: border-color .13s, box-shadow .13s;
}
.asx-text:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}

.asx-send {
    width: 32px; height: 32px; flex: none;
    display: grid; place-items: center;
    border: 0; border-radius: var(--radius-sm);
    background: var(--asx-grad, var(--accent)); color: var(--on-accent, #fff);
    cursor: pointer;
    box-shadow: inset 0 1px 0 rgba(255,255,255,.28), 0 2px 8px -3px var(--accent-glow);
    transition: transform .13s, opacity .13s, filter .13s;
}
.asx-send svg { width: 16px; height: 16px; }
.asx-send:hover:not(:disabled) { filter: brightness(1.1); transform: translateX(1px); }
.asx-send:disabled { opacity: .35; cursor: default; box-shadow: none; }

.asx-send.is-stop {
    background: var(--panel-inset); color: var(--text-dim);
    border: 1px solid var(--line-strong);
    box-shadow: none;
}
.asx-send.is-stop:hover { border-color: var(--fault); color: var(--fault); transform: none; filter: none; }
.asx-stop { width: 10px; height: 10px; border-radius: 2px; background: currentColor; }

/* ---------- Fußzeile ---------- */

.asx-foot {
    display: flex; align-items: flex-start; gap: 8px; flex: none;
    padding: 7px 11px 9px;
    border-top: 1px solid var(--line);
    background: var(--panel-raised);
    font-size: 9.5px; line-height: 1.45;
}

.asx-foot-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }

.asx-foot-seal {
    display: flex; align-items: center; gap: 4px;
    color: var(--ok); font-weight: 600;
}
.asx-foot-seal.is-warn { color: var(--warn); }
.asx-foot-seal .asx-shield { width: 11px; height: 11px; flex: none; }

/* ---------- Die acht Griffe ----------
   Vier Kanten und vier Ecken. Ein Fenster, das sich nur unten rechts fassen
   lässt, muss man erst verschieben, um es oben größer zu machen – zwei Gesten
   für eine Absicht.

   Die Kanten sind sechs Pixel breit und liegen halb außerhalb, die Ecken
   vierzehn: Eine Ecke ist das, was man zuerst greift, und sie muss die Kanten
   überlagern, damit sie am Schnittpunkt gewinnt. */

.asx-h { position: absolute; z-index: 3; }

.asx-h-n { top: -3px; left: 12px; right: 12px; height: 7px; cursor: ns-resize; }
.asx-h-s { bottom: -3px; left: 12px; right: 12px; height: 7px; cursor: ns-resize; }
.asx-h-w { left: -3px; top: 12px; bottom: 12px; width: 7px; cursor: ew-resize; }
.asx-h-e { right: -3px; top: 12px; bottom: 12px; width: 7px; cursor: ew-resize; }

.asx-h-nw, .asx-h-ne, .asx-h-sw, .asx-h-se { width: 15px; height: 15px; z-index: 4; }
.asx-h-nw { top: -3px; left: -3px; cursor: nwse-resize; }
.asx-h-ne { top: -3px; right: -3px; cursor: nesw-resize; }
.asx-h-sw { bottom: -3px; left: -3px; cursor: nesw-resize; }
.asx-h-se { bottom: -3px; right: -3px; cursor: nwse-resize; }

/* Nur die untere rechte Ecke bekommt ein sichtbares Zeichen – sie ist die, an
   der jeder zuerst sucht. Die übrigen sieben findet die Hand über den Zeiger. */
.asx-h-se::after {
    content: ""; position: absolute; right: 3px; bottom: 3px;
    width: 9px; height: 9px;
    background:
        linear-gradient(135deg, transparent 52%, var(--line-strong) 52% 66%, transparent 66% 78%,
                        var(--line-strong) 78% 92%, transparent 92%);
}

/* Angedockt bleibt nur der Griff an der linken Kante – er verstellt die Breite
   der Spalte und läuft über die volle Höhe. */
.asx-panel.is-docked .asx-h-w { top: 0; bottom: 0; left: -3px; width: 7px; cursor: ew-resize; }
.asx-panel.is-docked .asx-h-w:hover { background: var(--accent-soft); }

/* ---------- Schmale Fenster ----------
   Unter 900 px Breite gibt es keine Andockspalte: Sie nähme dem Inhalt mehr,
   als sie ihm gibt. Unter 600 px ist auch ein frei schiebbares Fenster keine
   Hilfe – es deckt ohnehin alles ab. Dann liegt es als Blatt über der Seite,
   und die Griffe verschwinden: Es gibt nichts zu verstellen. */

@media (max-width: 900px) {
    .app-shell.assist-docked,
    .app-shell.assist-docked.nav-collapsed,
    .app-shell.assist-docked.assist-min,
    .app-shell.assist-docked.assist-min.nav-collapsed { grid-template-columns: var(--rail-w) 1fr; }
    .app-shell.assist-docked .asx-panel.is-docked {
        position: fixed; right: 8px; bottom: 8px; left: 8px;
        border-width: 1px; border-radius: var(--radius);
        height: min(72vh, 560px);
    }
}

@media (max-width: 600px) {
    .asx-panel {
        left: 8px !important; right: 8px;
        top: auto !important; bottom: 8px;
        width: auto !important; height: min(72vh, 560px) !important;

        /* Hier hängt das Fenster unten und nicht an --asx-y – die Obergrenze von
           oben würde mit einem stehengebliebenen Wert rechnen. */
        max-height: calc(100dvh - 16px);
    }
    .asx-panel.is-min { height: auto !important; }
    .asx-h { display: none; }
    .asx-head { cursor: default; }
}

/* ---------- Ruhige Darstellung ----------
   Wer Bewegung abbestellt hat, bekommt sie nicht. Die Kugel und die Balken
   bleiben stehen – sie sagen dann durch ihr Dasein, was sie sonst durch ihre
   Bewegung sagen. */

@media (prefers-reduced-motion: reduce) {
    .asx-fab, .asx-fab-sheen, .asx-think-orb, .asx-think-wave i,
    .asx-caret, .asx-dots i, .asx-peek-live i,
    .roki-glow, .roki-glint { animation: none; }

    /* Steht der Glanz still, darf er nicht als blasser Strich liegen bleiben – dann
       sieht das Wappen aus, als fehle ihm etwas. */
    .roki-glint { opacity: .45; }
    .roki-glow { opacity: 1; }

    /* Auch der Blick bleibt stehen. Er ist keine Animation im Sinne einer Schleife,
       aber es ist Bewegung, und abbestellt ist abbestellt. */
    .roki-face, .roki-shell { transform: none; transition: none; }

    /* Auch die Klinge bleibt stecken. Der Klick auf die Maske tut dann nichts, und das
       ist richtig: Die Einlage ist Zierde, und Zierde entfällt als Erstes. js/assist.js
       setzt die Klasse ohnehin nicht – das hier fängt den Fall ab, dass sie doch
       irgendwo gesetzt wird. */
    .asx-roki.is-waving,
    .asx-roki.is-waving .roki-arm,
    .asx-roki.is-waving .roki-hand,
    .asx-roki.is-waving .roki-glow { animation: none; }
    .asx-roki.is-waving { transform: none; }
    .asx-roki.is-waving .roki-arm { opacity: 0; }

    /* Die Klasse ist spezifischer als .asx-panel und gewänne sonst gegen die Zeile
       darunter – das Ausblenden liefe trotz abbestellter Bewegung. */
    .asx-panel.is-closing { animation: none; }
    .asx-panel, .asx-turn, .asx-menu, .asx-think-body { animation: none; }
    .asx-fab:hover, .asx-chip:hover, .asx-send:hover:not(:disabled) { transform: none; }
}

/* =========================================================================
   BENACHRICHTIGUNG - die Reiter sitzen auf dem Panel
   ========================================================================= */

/* Die Reiter hingen in der Luft: Die Leiste trug ihre Linie (wie im Flottenbereich)
   und darunter kam mit 14 Pixeln Abstand ein Panel mit eigener Oberkante. Zwei Linien
   mit einem Streifen Hintergrund dazwischen – die aktive Lasche war nach unten offen,
   aber es war nichts da, in das sie sich öffnete.

   Hier geht der Weg der Zeitreihenseite (.ts-tabs), und zwar aus einem handfesten
   Grund: Alle vier Reiter dieses Bereichs beginnen mit einem Panel. Es gibt also immer
   eine Oberkante zum Zudecken – anders als im Flottenbereich, wo nur einer von vier
   Reitern mit einem Panel beginnt und die Zeile deshalb die Linie tragen muss.

   Die Reiter liegen mit -1px auf dieser Oberkante, die aktive Lasche übermalt sie in
   der Farbe des Panels und ist damit nach unten offen. Die Linie unter den Reitern ist
   ab jetzt die Oberkante des Panels selbst; eine eigene braucht die Zeile nicht mehr. */
.nt-tabrow {
    display: flex;
    /* flex-end: Reiter und Schaltflächen stehen auf derselben Linie. */
    align-items: flex-end;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;

    /* Kein Abstand nach unten: Der Abstand WAR der Fehler. */
    margin-bottom: 0;

    /* Die Laschen malen über die Kante des Panels; ohne eigene Ebene liegt das Panel
       darauf und der Deckstrich ist wieder weg. */
    position: relative;
    z-index: 1;
}

.nt-tabs {
    flex-wrap: wrap;

    /* Der Streifen nimmt die Breite der Zeile: Nur dann hat das abgesetzte Archiv
       ueberhaupt einen Platz, an den es sich schieben kann (margin-left: auto). Als
       inline-flex war der Streifen so breit wie seine Reiter, und "ganz rechts" war
       dort, wo der letzte Reiter endete. */
    flex: 1 1 auto;
    min-width: 0;

    /* Die Lasche deckt die Oberkante des Panels zu. */
    margin-bottom: -1px;

    /* Die Linie liegt am Panel und nicht am Reiterstreifen: Am Streifen wäre sie nur so
       breit wie die Reiter und endete mitten im Fenster. */
    border-bottom: 0;
}

/* Nach unten offen – in der Farbe des Panels und nicht der Seite: Das Panel liegt
   darunter, und --bg wäre an dieser Stelle ein heller Strich quer durch die Lasche. */
.nt-tabs button.active { border-bottom-color: var(--panel); }

/* Die Schaltflächen etwas über der Linie: Direkt darauf sähen sie aus, als stünden sie
   im Panel. */
.nt-tabrow > .row { padding-bottom: 8px; }

/* Das Panel unter den Reitern gibt seine linke obere Rundung auf: Die erste Lasche
   sitzt genau dort, und eine Rundung unter einer geraden Laschenkante ist eine Kerbe. */
.nt-panel { border-top-left-radius: 0; }

/* ---------- Regeln links, Editor rechts ---------- */
/* Vorher legte sich der Editor als Dialog ueber die Liste. Fuer einen einmaligen Vorgang
   ist das richtig; an Regeln arbeitet man aber reihenweise - wer die Sperrfrist von fuenf
   Regeln angleicht, oeffnete fuenfmal einen Dialog und suchte dazwischen jedes Mal die
   naechste Zeile in einer Liste, die er gerade erst verdeckt hatte.

   Links schmal, rechts breit, und zwar mit festen Grenzen statt eines Verhaeltnisses: Die
   linke Spalte traegt Karten mit kurzen Zeilen und braucht etwa 340 Pixel - darunter
   bricht der Anlassname um, darueber ist es verschenkte Breite, denn der Editor arbeitet
   innen selbst zweispaltig und nimmt jeden Pixel. */
.nt-split {
    display: grid;
    grid-template-columns: minmax(300px, 360px) minmax(0, 1fr);
    gap: 12px;
    align-items: start;
}

/* ---------- Zwei Spalten, und zwar zwei ----------
   Hier stand einmal das Gegenteil: Sobald eine Regel offen war, fiel der Spalt weg, die
   zugewandten Kanten gaben ihre Rundung auf und teilten sich einen Rahmen - die Liste
   sollte an der Stelle der gewaehlten Regel in den Editor uebergehen.

   Der Gedanke war, dass beides zusammengehoert. Auf dem Bildschirm sah es nicht so aus:
   Zwei Kaesten mit eigenem Kopf, eigener Fusszeile und eigener Bildlaufleiste kleben
   dann aneinander, ohne eins zu werden - und weil beide fuer sich rollen, wandert die
   gewaehlte Karte beim Rollen von der Naht weg, an der sie andocken sollte. Uebrig blieb
   ein geschlossener Spalt, und der liest sich als Versehen.

   Also wieder ein Abstand, und zwar derselbe wie im geschlossenen Zustand: Zusammen
   gehoeren die beiden ueber die Markierung der gewaehlten Karte, nicht ueber Geometrie. */
.nt-split-open { gap: 12px; }

/* ---------- Eingeklappte Liste ----------
   Eine Spalte statt zweier: Der Editor bekommt die Breite, die er fuer die Karte des
   Geofence, die Kanalliste und einen langen Auftrag an Ro.Ki braucht.

   display:none und keine Breite von null: Eine Spalte von null Pixeln traegt weiter ihren
   Inhalt, und ein Panel, das fuer sich rollt, rollt dann unsichtbar weiter - samt seiner
   Bildlaufleiste, die als Strich stehen bleibt. */
.nt-split.nt-list-off { grid-template-columns: minmax(0, 1fr); }
.nt-split.nt-list-off > .nt-split-list { display: none; }

/* Beide Spalten reichen bis zum unteren Fensterrand, und jede rollt fuer sich.

   --nt-top ist, was ueber ihnen liegt: Seitenkopf, Reiterleiste, Abstand. Als Variable und
   nicht dreimal als Zahl - sie steht in zwei Hoehenangaben und in der Klebemarke, und drei
   Stellen fuer denselben Wert heisst frueher oder spaeter drei verschiedene Werte.

   Warum ueberhaupt volle Hoehe: Eine Liste, die nur so hoch ist wie ihr Inhalt, laesst
   unter sich Leere stehen, waehrend rechts ein Formular ueber zwei Bildschirme geht - und
   dann rollt die ganze SEITE, also auch die Liste aus dem Bild. Wer in einer langen Liste
   arbeitet, verliert damit genau das, was der Umbau bringen sollte. */
/* Hoehe = Viewport - Topbar(58) - .content-Polster oben+unten(2x22) - Reiterleiste(~40).
   Gerechnet und nicht geschaetzt, nach dem Muster von .prof-layout: Ein zu kleiner Abzug
   erzeugt genau den Seiten-Scroll, den dieses Layout vermeiden soll. */
.nt-split { --nt-top: 150px; }

.nt-split-list {
    min-width: 0;
    position: sticky; top: 12px;
    height: calc(100vh - var(--nt-top));
    display: flex; flex-direction: column;
}

/* Das Panel fuellt die Spalte, und nur sein Koerper rollt: Kopfzeile und Suchfeld bleiben
   stehen. Panel und Panel-Koerper sind fremde Bauteile - deshalb hier und nicht dort. */
.nt-split-list > .panel {
    flex: 1; min-height: 0;
    display: flex; flex-direction: column;
}
.nt-split-list > .panel > .panel-body {
    flex: 1; min-height: 0;
    /* Acht statt zehn Pixel unter dem Suchfeld: Der Abstand trennt hier zwei Dinge, die
       ohnehin zusammengehoeren - man tippt oben und liest unten. */
    display: flex; flex-direction: column; gap: 8px;
    overflow: hidden;
}
.nt-split-list .nr-list {
    flex: 1; min-height: 0;
    overflow-y: auto;
    /* Platz fuer den Rand der gewaehlten Karte und fuer die Rollleiste. */
    padding-right: 4px;
}

.nt-split-edit {
    min-width: 0; display: flex; flex-direction: column; gap: 10px;
    position: sticky; top: 12px;
}
.nt-split-edit > .edock { max-height: calc(100vh - var(--nt-top)); }

/* Die Dialoge des Editors (Anlass, Ro.Ki, Fahrzeuge, Kanäle …) stehen im Markup IN der
   Spalte, und die Spalte ist sticky - damit ein eigener Stapelkontext. Das Overlay mit
   seinem z-index 100 zählte nur darin, und die Reiterleiste (z-index 1) und die Topbar
   malten über den abgedunkelten Grund. Solange ein Dialog offen ist, hebt sich die Spalte
   deshalb auf die Ebene des Overlays. */
.nt-split-list:has(.overlay),
.nt-split-edit:has(.overlay) { z-index: 100; }

/* Bei offener Regel die volle Hoehe und nicht nur hoechstens sie: Links steht ein Panel
   fester Hoehe, rechts haenge es am Inhalt - bei einer kurzen Regel endete der Editor
   auf halber Strecke, und der gemeinsame Rahmen hoerte mittendrin auf. Zu sehen ist das
   erst, seit beide zusammengewachsen sind; vorher waren es zwei Karten verschiedener
   Laenge, was nichts Besonderes ist. */
.nt-split-open .nt-split-edit > .edock { height: calc(100vh - var(--nt-top)); }

/* Ein Fenster, in dem 360 Pixel Liste und ein zweispaltiger Editor nicht nebeneinander
   passen: Dann untereinander - und sobald eine Regel geoeffnet ist, zaehlt nur noch sie.
   Eine Liste ueber einem Editor waere kein Master-Detail mehr, sondern zweimal Rollen. */
@media (max-width: 1150px) {
    .nt-split { grid-template-columns: 1fr; }

    .nt-split-list { position: static; height: auto; }
    .nt-split-list > .panel > .panel-body { overflow: visible; }
    .nt-split-list .nr-list { overflow-y: visible; padding-right: 0; }
    .nt-split-edit { position: static; }
    .nt-split-edit > .edock { max-height: none; }
    .nt-split-open .nt-split-list { display: none; }
}

/* ---------- Der angedockte Editor ---------- */
/* Dieselbe Huelle wie der Dialog - Kopf, Koerper, Fusszeile -, nur ohne Verdunklung
   dahinter: Er liegt nicht ueber der Seite, sondern daneben. Siehe
   Components/Shared/EditorDock.razor. */
.edock {
    display: flex; flex-direction: column; min-width: 0;
    border: 1px solid var(--line-strong);
    border-radius: var(--radius);
    background: var(--panel);
    box-shadow: var(--shadow);
}

.edock-head {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    padding: 14px 18px;
    border-bottom: 1px solid var(--line);
}

/* Der Titelblock nimmt die Zeile: Steht dort ein Feld statt einer Ueberschrift - der
   Name der Regel -, soll es die Breite haben, die der Name braucht, und nicht die, die
   der eingetippte Text gerade einnimmt. */
.edock-head > .panel-title { flex: 1; min-width: 0; }

/* Eigenes Rollen: Sonst wandert die Fusszeile mit dem Inhalt aus dem Bild, und
   "Speichern" steht am Ende eines langen Formulars dort, wo man es nicht sieht. */
.edock-body {
    flex: 1; min-height: 0; overflow-y: auto;
    padding: 16px 18px;
}

.edock-foot {
    display: flex; justify-content: flex-end; gap: 10px;
    padding: 14px 18px;
    border-top: 1px solid var(--line);
}

/* "ungespeichert" im Kopf und nicht in der Fusszeile: Der Klick auf die naechste Regel
   geschieht links oben, und dort muss die Warnung sein, nicht am unteren Rand eines
   Formulars, das man gerade nach unten gerollt hat. */
/* Der Name der Regel als Feld in der Kopfzeile. Er soll dort wie die Ueberschrift
   aussehen, die er ersetzt - aber sichtbar bleiben, DASS er ein Feld ist: Die erste
   Fassung zeigte den Kasten erst beim Hineinfahren, und wer die Maus nicht zufaellig
   darueberfuehrte, hielt den Namen fuer eine Anzeige und suchte das Feld unten im
   Formular. Jetzt traegt er eine gepunktete Grundlinie und den Stift daneben. */
.edock-title-edit { display: flex; align-items: center; gap: 4px; min-width: 0; }

/* Der Stift: dauerhaft sichtbar, aber leise. Er ist kein zweiter Weg zum Aendern,
   sondern die Auskunft, DASS man aendern kann - und ein Klick darauf setzt den
   Schreibzeiger ins Feld. */
.edock-title-pen {
    display: inline-flex; align-items: center; justify-content: center; flex: none;
    width: 22px; height: 22px; padding: 0;
    border: 0; border-radius: var(--radius-sm);
    background: transparent; color: var(--text-faint); cursor: pointer;
    transition: color .12s, background .12s;
}
.edock-title-pen svg { width: 13px; height: 13px; }
.edock-title-pen:hover { color: var(--accent); background: var(--panel-inset); }

/* Waehrend man schreibt, tritt der Stift zurueck: Seine Aufgabe ist erledigt. */
.edock-title-edit:focus-within .edock-title-pen { color: var(--line-strong); }
.edock-title-edit:focus-within .edock-title-pen:hover { color: var(--accent); }
.edock-title-input {
    width: 100%; min-width: 0;
    margin: -2px -7px; padding: 1px 6px;
    border: 1px solid transparent; border-radius: var(--radius-sm);
    background: transparent; color: var(--text);

    /* 14px wie .panel-title h3 und nicht groesser: Es steht an der Stelle einer
       Ueberschrift und soll aussehen wie eine. Nebenbei ist es der Unterschied, der die
       beiden Kopfzeilen auf dieselbe Hoehe bringt. */
    font-family: var(--font-ui); font-size: 14px; font-weight: 600; line-height: 1.35;
    transition: border-color .12s, background .12s;
}
/* Die gepunktete Grundlinie sagt "hier steht Text, den man aendern kann", ohne einen
   Kasten um eine Ueberschrift zu ziehen. Beim Hineinfahren wird daraus der volle
   Rahmen. */
.edock-title-input { border-bottom-color: var(--line-strong); border-bottom-style: dotted; }
.edock-title-input:hover, .edock-title-input:focus { border-bottom-style: solid; }

.edock-title-input::placeholder { color: var(--text-faint); font-weight: 500; }
.edock-title-input:hover { border-color: var(--line); background: var(--panel-raised); }
.edock-title-input:focus {
    outline: none;
    border-color: var(--accent); background: var(--panel);
}

/* Die knappe Sichtbarkeitswahl in der Rubrikzeile. Zwei Zeichen, so hoch wie die Zeile
   selbst - sie soll dort nichts auseinanderdruecken. */
/* Flacher als der gewoehnliche Umschalter: Er sitzt in einer Rubrikzeile, und jeder
   Pixel, den er hoeher ist als die Zeile, macht den ganzen Kopf hoeher - und damit den
   rechten Kopf hoeher als den linken, mit dem er sich einen Rahmen teilt. */
.vis-pick-mini { flex: none; }
.vis-pick-mini .seg { align-self: center; padding: 2px; }

/* Groesser als die 18 Pixel von frueher: Die Wahl steht jetzt rechts in der Kopfzeile
   zwischen Knoepfen von 25 Pixeln, und ein Umschalter, der dort sieben Pixel flacher ist
   als seine Nachbarn, sieht aus wie eine Anzeige und nicht wie ein Bedienelement.
   Hoeher als die Knoepfe daneben darf er trotzdem nicht werden - er teilt sich die Zeile
   mit ihnen, und jeder Pixel macht den ganzen Kopf hoeher. */
.vis-pick-mini .seg button { height: 23px; padding: 0 9px; gap: 5px; }
.vis-pick-mini .seg button svg { width: 14px; height: 14px; }

/* Die Woerter stehen jetzt auch in der knappen Form - klein und ohne Umbruch: "Privat"
   und "Im Bereich" sind zusammen keine sechzig Pixel, und ein Zeichen allein liess offen,
   welcher der beiden Zustaende gerade gilt, wenn man die Farben nicht kennt. */
.vis-pick-mini .seg button > span { font-size: 11px; white-space: nowrap; }

/* Das „i" steht neben der Wahl und nicht darunter: In der Kopfzeile gibt es kein
   Darunter. */
/* Eine Zeile, nicht zwei. Die Regel traegt beide Klassen, weil die allgemeine
   (.vis-pick, weiter unten im Blatt) sonst gewinnt: gleiche Spezifitaet, spaetere Stelle.
   Ohne das stand das "i" unter den Knoepfen und machte die Kopfzeile zwei Zeilen hoch. */
.vis-pick.vis-pick-mini { flex-direction: row; align-items: center; gap: 6px; }

/* Das "i" sitzt auf der Grundlinie der Knoepfe und nicht auf der des Textes darin. */
.vis-pick.vis-pick-mini > .ihint { align-self: center; }

.edock-dirty {
    flex: none;
    padding: 2px 9px;
    border: 1px solid var(--warn); border-radius: 999px;
    color: var(--warn);
    font-size: 10px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase;
    white-space: nowrap;
}

/* Die Rueckfrage vor dem Verwerfen. Ueber dem Editor und nicht als Dialog: Ein Dialog
   ueber einem Editor ist genau das, was dieser Umbau abschaffen sollte. */
.nt-discard {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    padding: 10px 14px;
    border: 1px solid var(--warn);
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--warn) 10%, var(--panel));
}
.nt-discard-text { flex: 1; min-width: 200px; font-size: 12px; color: var(--text); }

/* ---------- Der Kopf der Regelliste ---------- */
/* Regeln und Archiv als Umschalter statt als zwei Reiter der Seite: Es ist derselbe
   Bestand in einem anderen Zustand, und der Editor daneben bleibt beim Wechsel stehen.
   Der Umschalter traegt die Zahlen gleich mit - so beantwortet der Kopf die Frage
   "wie viele liegen drueben" ohne einen Klick. */
.nt-listhead { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; min-width: 0; }
.nt-listhead-note {
    font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.08em;
    text-transform: uppercase; color: var(--text-faint); white-space: nowrap;
}

/* Der kleine Umschalter - dieselbe Bauart wie .seg, nur flacher: Er sitzt in einer
   Kopfzeile und nicht in einem Formular. */
.seg-xs { display: inline-flex; }
.seg-xs button {
    display: inline-flex; align-items: center; gap: 5px;
    height: 24px; padding: 0 9px;
    font-size: 11.5px;
}
.seg-xs .tab-n {
    padding: 0 4px; border-radius: 999px;
    background: var(--panel-inset); color: var(--text-faint);
    font-family: var(--font-mono); font-size: 9.5px; line-height: 14px;
}
.seg-xs button.active .tab-n { background: color-mix(in srgb, var(--accent) 22%, transparent); color: inherit; }

/* Das Panel der Liste traegt seinen Kopf selbst und braucht darunter keinen zweiten
   Abstand: Suchfeld und Liste beginnen direkt. */
.nt-list-panel > .panel-body { padding-top: 10px; }

/* ---------- Die Schnellsuche ueber der Liste ---------- */
/* Sie bleibt stehen, waehrend die Liste darunter rollt: Bei dreissig Regeln sucht man
   nicht am Anfang, sondern dann, wenn man merkt, dass man sucht - und das ist irgendwo
   in der Mitte. */
.nr-find { position: relative; flex: none; }
.nr-find > .input { height: 32px; padding-right: 30px; font-size: 12px; }

/* Das Kreuz im Feld: Der Weg zurueck zur vollen Liste ist sonst das Markieren und
   Loeschen von acht Zeichen. */
.nr-find-x {
    position: absolute; top: 50%; right: 8px; transform: translateY(-50%);
    display: inline-flex; align-items: center; justify-content: center;
    width: 18px; height: 18px; padding: 0;
    border: 0; border-radius: 50%;
    background: transparent; color: var(--text-faint);
    font-size: 10px; cursor: pointer;
    transition: color .12s, background .12s;
}
.nr-find-x:hover { color: var(--text); background: var(--panel-raised); }

/* ---------- Die Karte in der schmalen Spalte ---------- */
/* Kompakter als frueher: Die Karte stand einmal allein auf der ganzen Seitenbreite, jetzt
   stehen zwanzig davon in einer Spalte von 340 Pixeln. Jede eingesparte Zeile ist eine
   Regel mehr, die ohne Rollen sichtbar ist. */
/* Rechts mehr Polster als links: Dort sitzt der Zeiger der gewaehlten Karte (siehe
   .nr.sel::after). An ALLEN Karten und nicht nur an der gewaehlten - sonst ruecke der Text
   bei jedem Wechsel der Auswahl um neun Pixel. */
.nt-split-list .nr { padding: 8px 10px 8px 12px; }

/* Sechs statt zehn Pixel zwischen den Karten: Bei zwanzig Regeln sind die vier Pixel je
   Fuge zusammen eine halbe Karte - und jede Karte, die ohne Rollen sichtbar ist, ist eine
   Regel, die man nicht sucht. Die Karten tragen ihren eigenen Rahmen; sie brauchen den
   Abstand nicht, um sich voneinander zu loesen. */
.nt-split-list .nr-list { gap: 6px; }


/* ---------- Die Handgriffe ---------- */
/* Vier Knoepfe je Karte waren in einer schmalen Spalte zwei zusaetzliche Zeilen - an jeder
   der zwanzig Karten. Sie erscheinen jetzt an der Karte, an der man arbeitet, und beim
   Ueberfahren; wer eine Regel anfasst, hat sie ohnehin zuerst gewaehlt. */
.nr-acts {
    grid-column: 1 / -1;
    display: flex; align-items: center; gap: 2px; flex-wrap: wrap;
    margin-top: 6px; padding-top: 6px;
    border-top: 1px dashed var(--line);
}
.nr-acts .btn {
    display: inline-flex; align-items: center; gap: 5px;
    height: 24px; padding: 0 7px; font-size: 11px; color: var(--text-dim);
}
.nr-acts .btn:hover { color: var(--text); }
.nr-acts .btn svg { width: 13px; height: 13px; flex: none; }
.nr-acts .nr-act-del { margin-left: auto; }
.nr-acts .nr-act-del:hover { color: var(--fault); }

@media (hover: hover) and (pointer: fine) {
    /* Nicht display:none, sondern Hoehe null: Ein Wechsel von none auf flex laesst die
       Karte springen und die darunter mitspringen - beim Ueberfahren einer Liste ist das
       ein Zittern von oben nach unten. */
    .nt-split-list .nr-acts {
        margin: 0; padding: 0; border-top: 0;
        max-height: 0; overflow: hidden; opacity: 0;
        transition: max-height .12s, opacity .12s, margin .12s, padding .12s;
    }
    .nt-split-list .nr:hover .nr-acts,
    .nt-split-list .nr:focus-within .nr-acts,
    .nt-split-list .nr.sel .nr-acts {
        max-height: 44px; opacity: 1;
        margin-top: 5px; padding-top: 5px; border-top: 1px dashed var(--line);
    }
}



/* =========================================================================
   FLOTTENBEREICH - die Reiter und was darunter liegt
   ========================================================================= */

/* ---------- Die Reiterleiste geht in den Bereich darunter ueber ----------
   Vorher sass die Leiste mit 12px Abstand ueber dem Inhalt: ein Streifen Knoepfe, der
   mit dem, was darunter stand, sichtbar nichts zu tun hatte.

   Der Weg der Zeitreihenseite - eine Lasche, die die Oberkante des Panels darunter
   zudeckt (.ts-tabs) - geht hier NICHT. Die vier Reiter beginnen verschieden: Rollout
   und Ziellaufwerke mit einer Aktionsleiste, die Statistik mit Kennzahlenkacheln, nur
   der Bestand mit einem Panel. Eine Lasche haette also in einem von vier Faellen etwas
   zum Ueberlappen und in dreien nicht.

   Stattdessen traegt die ZEILE die Linie, und zwar auf ganzer Breite: Sie ist damit die
   Unterkante des Reiterbereichs und gleichzeitig die Oberkante dessen, was folgt. Der
   aktive Reiter unterbricht sie - seine Unterkante ist in der Farbe des Grundes
   gezeichnet und nicht in der der Linie. Er ist damit nach unten offen und geht in den
   Bereich darunter ueber, ganz gleich, was dort steht. */
.fleet-tabs {
    flex-wrap: wrap;

    /* Die Linie liegt an der Zeile, nicht am Streifen: Am Streifen waere sie nur so
       breit wie die Reiter selbst und endete mitten im Fenster. */
    border-bottom: 0;
}

/* Reiter links, Schaltflächen rechts – in einer Zeile. Vorher standen sie
   uebereinander, und die Panels begannen eine Zeile tiefer als noetig. Umbrechen darf
   die Zeile: auf schmalen Fenstern rutschen die Knoepfe unter die Reiter, statt sie
   zusammenzuquetschen. */
.fleet-tabrow {
    display: flex;
    /* flex-end statt center: Reiter und Schaltflaechen stehen auf derselben Linie.
       Mittig ausgerichtet schwebten die Knoepfe ueber ihr. */
    align-items: flex-end;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 14px;
    border-bottom: 1px solid var(--line);
}

/* -1px: Die Reiter stehen mit ihrer Unterkante auf der Linie der Zeile, statt einen
   Punkt darueber zu enden. Nur so kann der aktive sie unterbrechen. */
.fleet-tabrow > .fleet-tabs { margin-bottom: -1px; }

/* Der aktive Reiter unterbricht die Linie - das ist die ganze Verbindung. --bg und
   nicht transparent: Eine durchsichtige Kante laesst die Linie der Zeile durch, sie muss
   also uebermalt werden. */
.fleet-tabs button.active { border-bottom-color: var(--bg); }

/* Die Schaltflaechen etwas ueber der Linie: Direkt darauf sahen sie aus, als stuenden
   sie im Reiterbereich, und sie gehoeren zum Inhalt. */
.fleet-tabrow > .row { padding-bottom: 7px; }

/* Die Zeile ueber einem Reiter: links sagt sie, was der Reiter ist, rechts stehen
   seine Schaltflaechen. Sie ersetzt die Kopfzeile, die Rollout und Weiterleitungen
   als eigene Seiten hatten - dort war sie eine section-head, hier waere eine zweite
   Ueberschrift unter der ersten eine Dopplung. */
.fleet-actionbar {
    display: flex;
    align-items: center;
    gap: 9px;
    flex-wrap: wrap;
    margin-bottom: 12px;
}

/* ---------- Statistik ---------- */

.fleet-stats { display: flex; flex-direction: column; gap: 14px; }

/* Die Kopfzahlen. Stat-Kacheln und kein Diagramm: Es sind einzelne Werte ohne
   Verlauf, und ein Balken je Zahl wuerde nur die Zahl noch einmal zeichnen. */
.fs-kpis {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
    gap: 10px;
}

.fs-kpi {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 12px 14px;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--panel);
}

.fs-kpi-n { font-family: var(--font-mono); font-size: 23px; font-weight: 600; line-height: 1.1; }
.fs-kpi-l { font-size: 11px; color: var(--text-dim); line-height: 1.35; }
.fs-kpi.hot .fs-kpi-n { color: var(--fault); }
.fs-kpi.warn .fs-kpi-n { color: var(--warn); }

/* ---------- Die Tafeln der Statistik ----------
   Zwoelf Spalten mit fester Breite je Tafel statt auto-fit.

   Mit auto-fit entschied die Fensterbreite, wie viele Tafeln nebeneinander stehen. Weil
   sie verschieden hoch sind - eine Verteilung aus vier Zeilen neben einer Tabelle mit
   zwoelf -, rissen zwischen den Reihen Luecken auf: Bei vier Spalten war die halbe Seite
   Luft, und die Tafeln standen in keinem erkennbaren Verhaeltnis zueinander.

   Jetzt hat jede Tafel die Breite, die ihr Inhalt braucht, und die Reihen gehen auf:
   4+8, 5+7, 6+6, 12. Zusammen mit stretch stehen die Unterkanten einer Reihe auf einer
   Linie - der Rest des Whitespace, den man nicht dem Inhalt ansieht. */
.fs-grid {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: 14px;
    align-items: stretch;
}

/* Die Tafel fuellt ihre Reihenhoehe; ihr Inhalt bleibt trotzdem oben. */
.fs-grid > .panel { display: flex; flex-direction: column; min-width: 0; }
.fs-grid > .panel > .panel-body { flex: 1; }

.fs-c4  { grid-column: span 4; }
.fs-c5  { grid-column: span 5; }
.fs-c6  { grid-column: span 6; }
.fs-c7  { grid-column: span 7; }
.fs-c8  { grid-column: span 8; }
.fs-c12, .fs-wide { grid-column: 1 / -1; }

/* Mittlere Fenster: zwei Tafeln je Reihe, egal welche Spannweite sie haben. Die
   Feinaufteilung 5+7 braucht Breite, um besser zu sein als 6+6 - darunter ist sie nur
   ungleich. */
@media (max-width: 1240px) {
    .fs-c4, .fs-c5, .fs-c6, .fs-c7, .fs-c8 { grid-column: span 6; }
}

/* Eine Verteilungszeile: Punkt, Beschriftung, Messbalken, Zahl.

   Bewusst je Zeile ein eigener Balken statt eines gestapelten. Im hellen Thema
   liegen --warn und --fault nur dE 14,6 auseinander (Normalsicht; Schwelle 15,
   unter Deuteranopie 7,8) - als benachbarte Segmente eines Stapels waeren
   "ueberfaellig" und "faellig diese Woche" also nicht sicher zu trennen. Jede Zeile
   traegt ausserdem Beschriftung und Zahl, die Farbe steht damit nie allein. */
.fs-meters { display: flex; flex-direction: column; gap: 9px; }

.fs-meter { display: flex; align-items: center; gap: 9px; }

.fs-dot {
    width: 8px; height: 8px; border-radius: 50%; flex: none;
    /* Der Ring in Flaechenfarbe haelt den Punkt lesbar, wo er an eine Kante stoesst. */
    box-shadow: 0 0 0 2px var(--panel);
}

.fs-meter-l { font-size: 12px; width: 118px; flex: none; color: var(--text); }

.fs-track {
    flex: 1;
    min-width: 40px;
    height: 6px;
    border-radius: 3px;
    background: var(--panel-inset);
    border: 1px solid var(--line);
    overflow: hidden;
}

/* Am Nullpunkt eckig, am Datenende rund - so liest man, wo der Balken beginnt. */
.fs-fill {
    height: 100%;
    border-radius: 0 3px 3px 0;
    transition: width .25s ease;
}

.fs-meter-n {
    font-family: var(--font-mono);
    font-size: 12.5px;
    width: 46px;
    flex: none;
    text-align: right;
    color: var(--text);
}

.fs-barline { display: flex; align-items: center; gap: 9px; }

.fs-note {
    margin-top: 12px;
    font-size: 11.5px;
    line-height: 1.5;
    color: var(--text-dim);
}

.fs-empty { padding: 22px 4px; font-size: 12.5px; color: var(--text-dim); text-align: center; }

.fs-table th, .fs-table td { font-size: 12.5px; }
.fs-table tr.fs-row-sel > td { background: var(--accent-soft); }

@media (max-width: 760px) {
    .fs-meter-l { width: 96px; font-size: 11.5px; }
    .fs-grid > .panel { grid-column: 1 / -1; }
}


/* ==========================================================================
   Saeulen fuer gezaehlte Dinge - siehe Components/Charts/ColumnChart.razor
   ========================================================================== */

/* Die Zeichenflaeche. Die Saeulen wachsen von unten, deshalb liegt die Grundlinie am
   unteren Rand und nicht als gezeichnete Linie darunter: Der Rand IST die Null. */
.cch { display: flex; flex-direction: column; min-width: 0; }

.cch-plot {
    display: flex; align-items: stretch; gap: 4px;
    min-width: 0;
}

.cch-col {
    flex: 1 1 0; min-width: 0;
    display: flex; flex-direction: column; align-items: center; gap: 3px;
}

/* Die Zahl ueber der Saeule. Sie steht auch bei null da - "kein Termin in diesem Monat"
   ist eine Aussage und keine fehlende Angabe. */
.cch-v {
    font-family: var(--font-mono); font-size: 11px; line-height: 1;
    color: var(--text-dim); white-space: nowrap;
}

/* Der Platz, in dem die Saeule steht. Ohne ihn waere die Hoehe in Prozent auf die
   Zeile bezogen, in der auch Zahl und Beschriftung stehen - eine Saeule von hundert
   Prozent reichte dann bis unter die Beschriftung. */
.cch-bar-slot {
    flex: 1; min-height: 0; width: 100%;
    display: flex; align-items: flex-end; justify-content: center;
}

.cch-bar {
    width: 100%; max-width: 46px;
    min-height: 2px;
    border-radius: 3px 3px 0 0;
    background: var(--accent);
    transition: height .25s ease;
}

/* Die betonte Saeule - ueberfaellige Fristen, lange stille Fahrzeuge. Eine Kontur und
   kein zweiter Farbton: Die Farbe traegt hier schon die Art, und zwei Abstufungen
   derselben Farbe unterscheidet niemand ueber die Breite einer Saeule. */
.cch-col.hot .cch-v { color: var(--text); font-weight: 700; }
.cch-col.hot .cch-bar { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--text) 35%, transparent); }

/* Die Beschriftung. Waagerecht und kurz - gedrehte Beschriftungen liest man einzeln,
   und hier geht es um die Gestalt der Reihe. */
.cch-l {
    font-size: 10px; line-height: 1.2; color: var(--text-faint);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}

/* Der Hinweis unter dem Bild steht schon als .fs-note im Stil der Statistik; hier nur
   der Abstand, weil das Bild direkt darueber endet. */
.cch > .fs-note { margin-top: 10px; }

/* Schmale Fenster: Zwoelf Monate nebeneinander sind dort zwoelf Striche mit
   unleserlicher Beschriftung. Die Reihe rollt dann waagerecht und behaelt ihre
   Saeulenbreite - lieber schieben als raten. */
@media (max-width: 620px) {
    .cch { overflow-x: auto; }
    .cch-plot { min-width: 420px; }
}

/* =====================================================================
   Meldungen (Seite /notifications)

   Vier Reiter statt drei Panels nebeneinander: Regeln, Archiv, Kanaele,
   Verlauf. Der Grund ist nicht Platz, sondern Haeufigkeit - ein Kanal
   wird einmal eingerichtet und danach jahrelang nicht angesehen, eine
   Regel legt man an, schaltet sie ab, holt sie wieder, dupliziert sie
   fuer das naechste Fahrzeug. Drei gleichrangige Panels gaben der
   seltenen Arbeit denselben Platz wie der haeufigen.
   ===================================================================== */

.nt-list { display: flex; flex-direction: column; gap: 8px; }

/* ---------- Eine Regelkarte ---------- */
/* Vier Zeilen und nicht eine: Anlass und Zustand oben, Name gross, Angaben klein,
   Schaltflaechen unten. Vorher stand alles in einer Zeile, und die Schaltflaechen
   drueckten Name und Angaben so weit zusammen, dass beide abgeschnitten waren. */
.nr-list { display: flex; flex-direction: column; gap: 10px; }

/* Drei Spalten: das Zeichen des Anlasses, Name und Angaben, Schalter und letzte Meldung.
   Die Handgriffe laufen darunter ueber alle drei (siehe .nr-acts). */
.nr {
    display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center; column-gap: 10px;
    padding: 9px 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--panel-raised);
}

/* Stillgelegt heisst blass und nicht versteckt: Eine Regel, die man ueber den Urlaub
   abgeschaltet hat, soll man wiederfinden - und sehen, dass sie gerade nichts tut. */
.nr.off { opacity: .6; }
.nr.off .nr-ico { filter: grayscale(1); }

/* Die ganze Karte waehlt aus. In einer schmalen Spalte ist sie das groesste Ziel, und wer
   eine Regel ansehen will, klickt sie an und sucht nicht den kleinen Knopf. Nur die
   eigenen: An einer fremden gibt es nichts zu oeffnen. */
.nr.pick { cursor: pointer; transition: border-color .12s, background .12s; }
.nr.pick:hover { border-color: var(--line-strong); background: var(--panel); }

/* ---------- Die gewaehlte Regel ---------- */
/* Das eigentliche Problem war nicht, DASS die Auswahl markiert war, sondern dass links und
   rechts wie zwei getrennte Dinge aussahen. Drei Mittel dagegen, und sie wirken zusammen:

     1. Die Karte traegt die Akzentfarbe - Rahmen, ein voller Streifen links und ein
        Schein ringsum. Sie ist damit das einzige farbige Element der Liste.
     2. Ein Zeiger an ihrer rechten Kante. Er sitzt AUSSERHALB der Karte, im Abstand
        zwischen den Spalten, und zeigt auf den Editor. Das ist die Aussage, die eine
        Umrandung allein nicht macht: nicht "diese hier", sondern "diese hier gehoert zu
        dem da".
     3. Der Editor traegt ihren Namen in der Ueberschrift (siehe NotifyRuleEditor.Title).
        Zwei gleiche Woerter nebeneinander sind der belastbarste Faden von allen.

   Der Zeiger nur in der Spaltenansicht: Untereinander zeigte er auf nichts. */
.nr.sel {
    border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 7%, var(--panel));
    box-shadow: inset 4px 0 0 0 var(--accent),
                0 0 0 1px var(--accent),
                0 4px 16px -8px color-mix(in srgb, var(--accent) 60%, transparent);
}
.nr.sel.off { opacity: 1; }
.nr.sel .nr-name { color: var(--text); }

.nt-split-list .nr.sel { position: relative; }

/* Der Zeiger. Als ::after und nicht als eigenes Element, weil er zur Markierung gehoert
   und nicht zum Inhalt - er darf in keiner Vorlesung und in keiner Suche vorkommen.

   INNERHALB der Karte und nicht daraus hervorstehend: Die Liste rollt senkrecht, und ein
   Kasten mit overflow-y:auto schneidet waagerecht ebenso ab (overflow-x wird dann
   ebenfalls zu auto) - ein Zeiger, der aus der Karte ragt, haette eine waagerechte
   Rollleiste unter der Liste erzeugt. Er sitzt deshalb im rechten Polster, das alle
   Karten der Spalte tragen (siehe oben): So springt der Text nicht, wenn die Auswahl
   wechselt. */
/* Die gewaehlte Karte bleibt eine Karte mit vier Kanten. Sie lief hier einmal bis an
   die Naht und gab dort Rundung und Rahmen auf - das gehoerte zum Uebergang von links
   nach rechts, den es nicht mehr gibt (siehe ".nt-split-open" weiter oben). Ohne Naht
   waere eine Karte mit fehlender rechter Kante nur eine Karte mit einem Fehler.

   Kenntlich ist sie ueber den Streifen links und den Schein ringsum, und der Editor
   daneben traegt dieselbe Farbe. */
.nt-split-open .nt-split-list .nr.sel {
    box-shadow: inset 4px 0 0 0 var(--accent), 0 0 0 1px var(--accent);
}

/* Der Editor greift die Farbe auf: Ein Panel in Akzentfarbe neben einer Karte in
   Akzentfarbe liest sich als ein Ding in zwei Teilen. Nur der Rahmen und der Kopf, nicht
   die Flaeche - darin steht ein Formular, und das soll ruhig bleiben. */
.nt-split-edit > .edock { border-color: var(--accent); }
.nt-split-edit > .edock > .edock-head {
    background: color-mix(in srgb, var(--accent) 7%, var(--panel));
    border-bottom-color: color-mix(in srgb, var(--accent) 35%, var(--line));
}

/* Beide Koepfe auf eine Linie. Sie sahen verschieden aus, weil sie es waren: links ein
   Panelkopf mit 12px Polster, rechts ein Editorkopf mit 14px und einer eigenen Farbe.
   Nebeneinander liest man das als zwei Bauteile, die zufaellig nebeneinanderstehen.
   Gleiche Hoehe, gleiche Farbe, gleiche Trennlinie - dann ist es eine Kopfzeile ueber
   zwei Spalten. */
.nt-split-open .nt-split-list > .panel > .panel-head,
.nt-split-edit > .edock > .edock-head {
    /* Eine gemeinsame Hoehe und nicht zweimal dieselbe Mindesthoehe: Der rechte Kopf
       traegt ein Eingabefeld und war damit neun Pixel hoeher als der linke. Bei zwei
       Panels mit einem Spalt dazwischen faellt das niemandem auf; seit sie sich einen
       Rahmen teilen, ist es ein Versatz mitten in der Naht. */
    min-height: 65px;
    padding: 10px 14px;
}
.nt-split-open .nt-split-list > .panel > .panel-head {
    background: color-mix(in srgb, var(--accent) 7%, var(--panel));
    border-bottom: 1px solid color-mix(in srgb, var(--accent) 35%, var(--line));
}

/* Die stillgelegte gewaehlte Regel bleibt kenntlich, ohne blass zu sein: Wer sie
   bearbeitet, soll ihren Text lesen koennen. Der Name traegt die Aussage stattdessen. */
.nr.sel.off .nr-name { opacity: .7; }


/* ---- Das Zeichen des Anlasses ----
   Eine Kachel in der Akzentfarbe, dasselbe Zeichen wie im Editor. Sie ist das Erste,
   wonach das Auge in der Liste sucht - "welche Regeln melden Signalwerte" - und sie
   macht aus zwanzig gleich grauen Karten eine Liste, die man ueberfliegen kann. */
.nr-ico {
    position: relative;
    display: flex; align-items: center; justify-content: center;
    width: 32px; height: 32px; border-radius: 9px;
    background: var(--accent-soft); color: var(--accent);
    border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent);
}
.nr-ico svg { width: 17px; height: 17px; }

/* Der Live-Ausloeser in der Warnfarbe und mit einem Punkt an der Ecke: Er greift,
   WAEHREND gemessen wird. Der Punkt pulsiert leise - er sagt "hier wird mitgelesen",
   nicht "Alarm". */
.nr-ico.live {
    background: color-mix(in srgb, var(--warn) 14%, transparent); color: var(--warn);
    border-color: color-mix(in srgb, var(--warn) 34%, transparent);
}
.nr-ico.live::after {
    content: ""; position: absolute; top: -3px; right: -3px;
    width: 8px; height: 8px; border-radius: 50%;
    background: var(--warn); box-shadow: 0 0 0 2px var(--panel-raised);
    animation: nr-live-pulse 2.4s ease-in-out infinite;
}
@keyframes nr-live-pulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: .35; }
}
@media (prefers-reduced-motion: reduce) {
    .nr-ico.live::after { animation: none; }
}

.nr-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }

.nr-head { display: flex; align-items: center; gap: 6px; min-width: 0; }
.nr-name {
    min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    font-family: var(--font-ui); font-weight: 600; font-size: 13px; line-height: 1.3;
    color: var(--text);
}

/* Die Zeichen neben dem Namen: Ro.Ki, privat, geteilt. Klein und gedaempft - sie sind
   Auskunft auf Nachfrage (der Satz steht im Tooltip), und der Name soll fuehren. */
.nr-mark { display: inline-flex; flex: none; color: var(--text-faint); }
.nr-mark svg { width: 12px; height: 12px; }
.nr-mark-shared { color: var(--accent); }
.nr-mark-roki .asx-roki { width: 15px; height: 15px; }

/* Die Angaben in EINER Zeile, abgeschnitten statt umgebrochen. Der Anlass fuehrt sie an
   und traegt die Akzentfarbe; der Rest ist gedaempft. */
.nr-meta {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    font-size: 11px; line-height: 1.35; color: var(--text-dim);
}
.nr-meta > span + span::before { content: "·"; margin: 0 6px; color: var(--text-faint); }
.nr-trig { color: var(--accent); font-weight: 600; }
.nr-trig.live { color: var(--warn); }

/* Rechts: oben der Schalter, darunter die letzte Meldung - rechtsbuendig, damit die
   Zeiten der Karten untereinander eine Spalte bilden. */
.nr-side {
    display: flex; flex-direction: column; align-items: flex-end; gap: 4px;
    align-self: stretch; justify-content: center;
}
.nr-fired { font-size: 10.5px; color: var(--text-faint); white-space: nowrap; }

/* Der kleine Schalter: In einer Karte von vierzig Pixeln Hoehe ist der grosse der
   lauteste Teil, und er soll nur sagen, ob die Regel an ist. */
.toggle.toggle-sm { width: 30px; height: 17px; }
.toggle.toggle-sm .track::before { width: 11px; height: 11px; }
.toggle.toggle-sm input:checked + .track::before { transform: translateX(13px); }
.toggle input:focus-visible + .track { outline: 2px solid var(--accent); outline-offset: 2px; }

.nr-badge {
    display: inline-flex; align-items: center; gap: 4px; flex: none;
    font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .06em;
    text-transform: uppercase;
    padding: 1px 7px; border-radius: 999px;
}
.nr-live {
    background: color-mix(in srgb, var(--warn) 16%, transparent);
    color: var(--warn);
    border: 1px solid color-mix(in srgb, var(--warn) 40%, transparent);
}


/* ---------- Der Wortlaut im Verlauf ---------- */
/* Vorformatiert, weil die Meldung zeilenweise aufgebaut ist: Ein umgebrochener Block
   aus "Schluessel: Wert"-Zeilen ist nicht mehr zu lesen. Er rollt waagerecht, statt
   die Zeile zu brechen - eine Webhook-Adresse oder ein Kartenlink gehoert in eine
   Zeile. */
.nt-log-body {
    margin: 6px 0 2px; padding: 9px 11px;
    max-height: 340px; overflow: auto;
    background: var(--panel-inset); border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    font-family: var(--font-mono); font-size: 11px; line-height: 1.6;
    color: var(--text-dim); white-space: pre;
}

/* =====================================================================
   Der Regeleditor

   Eine Flaeche statt vier Schritte. Der Assistent fuehrte durch den Satz
   "Sag mir Bescheid, WENN dieses Fahrzeug DORT - und zwar so", und als
   Einfuehrung war das richtig. Beim zweiten Mal ist es im Weg: Wer die
   Sperrfrist aendern will, klickt sich durch drei Bildschirme, auf denen
   er nichts aendern moechte.
   ===================================================================== */

/* 10px und nicht 16: Bei vier Bloecken sind sechs Pixel je Fuge zusammen eine
   Formularzeile - und die entscheidet hier darueber, ob gerollt werden muss. */
.nre { display: flex; flex-direction: column; gap: 10px; }


/* ---------- Die Regel als Ablauf ---------- */
/* Ein UML-Aktivitaetsdiagramm: Startpunkt, das empfangene Ereignis (Kerbe links), die
   Aktionen (abgerundet), die Pruefung (Raute), das gesendete Signal (Spitze rechts) und
   der Endpunkt. Warum die Formen und nicht fuenf gleiche Kaesten: Sie sagen, WAS an der
   Station geschieht, bevor man ein Wort gelesen hat - hinein, pruefen, hinaus. Siehe
   Components/Notify/NotifyRuleEditor.razor.

   Die Flaeche mit dem Punktraster ist die Zeichenflaeche. Sie grenzt das Diagramm vom
   Kopf des Editors ab, ohne einen weiteren Rahmen um jeden Knoten zu brauchen. */
.nrf {
    --nrf-line: var(--line-strong);
    --nrf-flow: var(--accent);
    /* Tiefe von Kerbe und Spitze. Eine Zahl, weil Kerbe, Spitze und das Polster
       daneben zusammenpassen muessen. */
    --nrf-cut: 16px;
    display: flex; flex-direction: column; align-items: center;
    padding: 22px 18px 20px;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background-color: var(--panel-inset);
    background-image: radial-gradient(circle,
                      color-mix(in srgb, var(--text-faint) 30%, transparent) 1px,
                      transparent 1.4px);
    background-size: 16px 16px;
    background-position: center 6px;
}

/* Die Knoten auf eine Breite: Ein Diagramm, dessen Kaesten mit ihrem Text wachsen, hat
   eine Mittellinie, die hin und her springt. 560 Pixel, weil darin die laengste
   Kurzform - drei Kanalnamen, drei Fahrzeuge - in einer Zeile steht, und weil ein
   Kasten ueber die volle Breite eines eingeklappten Editors kein Knoten mehr ist,
   sondern ein Balken. */
.nrf > .nrf-node,
.nrf > .nrf-guard,
.nrf > .nrf-end { width: 100%; max-width: 560px; }

/* Der Startpunkt: in UML ein gefuellter Kreis. In der Textfarbe und nicht im Akzent -
   er ist ein Zeichen, kein Bedienelement. */
.nrf-start {
    flex: none; width: 14px; height: 14px; border-radius: 50%;
    background: var(--text);
}

/* ---- Die Kante zwischen zwei Knoten ----
   Gestrichelt, solange der Fluss hier nicht ankommt; durchgezogen im Akzent, sobald
   alles darueber steht. Wo er abreisst, fehlt etwas - siehe das Markup. */
.nrf-edge { position: relative; flex: none; width: 12px; height: 26px; }
.nrf-edge::before {
    content: ""; position: absolute; left: 50%; top: 0; bottom: 5px;
    border-left: 2px dashed var(--nrf-line);
    transform: translateX(-50%);
}
.nrf-edge::after {
    content: ""; position: absolute; left: 50%; bottom: 0;
    border: 5px solid transparent; border-top: 6px solid var(--nrf-line); border-bottom: 0;
    transform: translateX(-50%);
}
.nrf-edge.is-on::before { border-left-style: solid; border-left-color: var(--nrf-flow); }
.nrf-edge.is-on::after { border-top-color: var(--nrf-flow); }

/* ---- Ein Knoten ----
   Der Knopf selbst ist unsichtbar; gezeichnet wird .nrf-shape darin. Die Trennung
   braucht es fuer die beiden beschnittenen Formen unten: Dort ist der Knopf der Rand
   und die Form darin die Flaeche. */
.nrf-node {
    display: block; margin: 0; padding: 0;
    border: 0; background: none; color: inherit; font: inherit; text-align: left;
    cursor: pointer;
}
.nrf-shape {
    display: flex; align-items: center; gap: 12px; min-width: 0;
    min-height: 58px; padding: 10px 14px;
    border: 1px solid var(--nrf-line);
    border-radius: 14px;
    background: var(--panel-raised);
    transition: border-color .12s, background .12s;
}
.nrf-node:hover .nrf-shape { border-color: var(--accent); }
.nrf-node:focus-visible { outline: none; }
.nrf-node:focus-visible .nrf-shape {
    border-color: var(--accent);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 40%, transparent);
}

/* ---- Empfangen und Senden: die beschnittenen Formen ----
   clip-path schneidet auch den Rahmen ab. Deshalb traegt der Knopf die Randfarbe als
   Flaeche und die Form darin, einen Pixel kleiner und gleich beschnitten, die Fuellung -
   was dazwischen stehen bleibt, ist der Rand, auch an der Schraege. Eckig und nicht
   abgerundet: So zeichnet UML beide, und die Rundung der Aktionen dazwischen ist genau
   der Unterschied, den man sehen soll. */
.nrf-accept, .nrf-send {
    padding: 1px;
    background: var(--nrf-line);
    transition: background .12s;
}
.nrf-accept { clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, var(--nrf-cut) 50%); }
.nrf-send {
    clip-path: polygon(0 0, calc(100% - var(--nrf-cut)) 0, 100% 50%,
                       calc(100% - var(--nrf-cut)) 100%, 0 100%);
}
.nrf-accept > .nrf-shape, .nrf-send > .nrf-shape {
    border: 0; border-radius: 0;
    clip-path: inherit;
}
.nrf-accept > .nrf-shape { padding-left: calc(var(--nrf-cut) + 12px); }
.nrf-send > .nrf-shape { padding-right: calc(var(--nrf-cut) + 10px); }
.nrf-accept:hover, .nrf-send:hover,
.nrf-accept:focus-visible, .nrf-send:focus-visible { background: var(--accent); }
.nrf-accept:focus-visible > .nrf-shape, .nrf-send:focus-visible > .nrf-shape { box-shadow: none; }

/* Die Luecke: Der Knoten, an dem der Fluss abreisst, traegt die Warnfarbe - Rand, Zeichen
   und ein Hauch in der Flaeche. Nicht rot: Es ist nichts kaputt, es fehlt noch etwas. */
.nrf-node.is-gap { --nrf-line: var(--warn); }
.nrf-node.is-gap .nrf-shape { background: color-mix(in srgb, var(--warn) 7%, var(--panel-raised)); }
.nrf-node.is-gap .nrf-ico { background: color-mix(in srgb, var(--warn) 16%, transparent); color: var(--warn); }

/* Das Angebot: Ro.Ki, solange nichts beigelegt wird. Gestrichelt und gedaempft - eine
   Station, an der die Meldung vorbeigeht, und keine, an der sie wartet. */
.nrf-node.is-optional .nrf-shape {
    border-style: dashed;
    background: color-mix(in srgb, var(--panel-raised) 55%, transparent);
}
.nrf-node.is-optional .nrf-title { color: var(--text-dim); font-weight: 500; }
.nrf-node.is-optional .nrf-ico { opacity: .6; filter: grayscale(1); }
.nrf-node.is-optional:hover .nrf-ico { opacity: 1; filter: none; }

/* ---- Der Inhalt eines Knotens ---- */
.nrf-ico {
    display: flex; align-items: center; justify-content: center; flex: none;
    min-width: 34px; height: 34px; padding: 0 7px; border-radius: 10px;
    background: var(--accent-soft); color: var(--accent);
}
.nrf-ico svg { width: 18px; height: 18px; }
.nrf-ico-roki { width: 34px; padding: 5px; background: var(--panel-inset); }
.nrf-ico-roki .asx-roki { width: 100%; height: 100%; }

/* Die Zeichen der gewaehlten Kanaele, leicht uebereinander - wie Teilnehmer an einem
   Gespraech: Man sieht, dass es mehrere sind, ohne dass sie den Knoten breiter machen. */
.nrf-logos { display: flex; align-items: center; }
.nrf-logos { gap: 3px; }

.nrf-text { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
.nrf-kicker {
    font-family: var(--font-mono); font-size: 9.5px; letter-spacing: 0.12em;
    text-transform: uppercase; color: var(--text-faint); line-height: 1.2;
}
.nrf-title {
    display: flex; align-items: center; gap: 6px; flex-wrap: wrap; min-width: 0;
    font-size: 13.5px; font-weight: 600; line-height: 1.3; color: var(--text);
}
/* Die Kurzform in einer Zeile: Der Knoten ist eine Auskunft und kein Absatz. Wer alles
   lesen will, oeffnet ihn - und der Satz unter dem Diagramm sagt es ohnehin ganz. */
.nrf-sub {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    font-size: 11.5px; line-height: 1.4; color: var(--text-dim);
}
.nrf-sub-warn { color: var(--warn); white-space: normal; }

.nrf-go { display: flex; flex: none; color: var(--text-faint); transition: transform .12s, color .12s; }
.nrf-go svg { width: 16px; height: 16px; }
.nrf-node:hover .nrf-go { color: var(--accent); transform: translateX(2px); }

/* ---- Die Pruefung: eine Raute auf der Mittellinie ----
   Drei Spalten, die aeusseren gleich breit: So steht die Raute genau auf der Linie der
   Kanten, gleich wie lang der Waechter rechts und der Zweig links sind. */
.nrf-guard {
    display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    align-items: center;
    margin: 0; padding: 0;
    border: 0; background: none; color: inherit; font: inherit; text-align: left;
    cursor: pointer;
}
.nrf-guard:focus-visible { outline: none; }

/* Gedreht wird die Raute groesser als ihr Kasten; der Rand oben und unten faengt das auf,
   damit die Pfeilspitze an ihrer Ecke endet und nicht in ihr. */
.nrf-diamond {
    width: 24px; height: 24px; margin: 6px 14px;
    border: 1.5px solid var(--nrf-line); border-radius: 3px;
    background: var(--panel-raised);
    transform: rotate(45deg);
    transition: border-color .12s, background .12s, box-shadow .12s;
}
.nrf-guard.is-set .nrf-diamond {
    border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 18%, var(--panel-raised));
}
.nrf-guard:hover .nrf-diamond { border-color: var(--accent); }
.nrf-guard:focus-visible .nrf-diamond {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 35%, transparent);
}

/* Der Waechter in eckigen Klammern - so schreibt UML die Bedingung an einer Kante. Die
   Klammern stehen im Textfluss und nicht als eigene Spalten: Bricht der Waechter um,
   bleibt die schliessende Klammer am Ende des Textes und nicht allein am rechten Rand.
   Proportional und nicht in Festbreite, weil drei Schranken sonst schon in der halben
   Breite des Diagramms umbrechen. */
.nrf-guard-text {
    display: flex; align-items: center; gap: 6px; justify-self: start; min-width: 0;
    font-size: 12px; font-weight: 500; line-height: 1.45; color: var(--text);
}
.nrf-guard-b { color: var(--accent); font-family: var(--font-mono); font-weight: 700; }
.nrf-guard-none { font-family: var(--font-ui); font-style: italic; color: var(--text-faint); }
.nrf-guard-edit {
    display: flex; flex: none; align-self: center;
    color: var(--accent); opacity: 0; transition: opacity .12s;
}
.nrf-guard-edit svg { width: 14px; height: 14px; }
.nrf-guard:hover .nrf-guard-edit,
.nrf-guard:focus-visible .nrf-guard-edit { opacity: 1; }

/* Der Zweig nach links: Was die Pruefung nicht passiert, endet im Flussende (der Kreis
   mit dem Kreuz) - still, und nicht als Fehler. */
.nrf-else { display: flex; align-items: center; justify-content: flex-end; gap: 6px; min-width: 0; }
.nrf-else-text {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    font-size: 11px; font-style: italic; color: var(--text-faint);
}
.nrf-final {
    position: relative; flex: none; width: 14px; height: 14px;
    border: 1.5px solid var(--text-faint); border-radius: 50%;
}
.nrf-final::before, .nrf-final::after {
    content: ""; position: absolute; left: 50%; top: 50%;
    width: 9px; height: 1.5px; background: var(--text-faint);
    transform: translate(-50%, -50%) rotate(45deg);
}
.nrf-final::after { transform: translate(-50%, -50%) rotate(-45deg); }
.nrf-else-line {
    position: relative; flex: none; width: 30px;
    border-top: 1.5px dashed var(--text-faint);
}
.nrf-else-line::before {
    content: ""; position: absolute; left: -1px; top: -5.25px;
    border: 4.5px solid transparent; border-right: 5px solid var(--text-faint); border-left: 0;
}

/* ---- Das Ende ----
   Der Endpunkt: ein Ring mit gefuelltem Kern. Gruen, sobald die Regel speicherbar ist;
   bis dahin gedaempft, und daneben steht, was noch fehlt. Dasselbe Raster wie die Raute,
   damit er auf der Mittellinie sitzt. */
.nrf-end {
    display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    align-items: center;
}
.nrf-end::before { content: ""; }
.nrf-final-node {
    position: relative; flex: none; width: 22px; height: 22px; margin: 0 12px;
    border: 2px solid var(--text-faint); border-radius: 50%;
}
.nrf-final-node::after {
    content: ""; position: absolute; inset: 3px; border-radius: 50%;
    background: var(--text-faint);
}
.nrf-end.is-ready .nrf-final-node { border-color: var(--ok); }
.nrf-end.is-ready .nrf-final-node::after { background: var(--ok); }
.nrf-end-text { font-size: 12px; line-height: 1.45; color: var(--warn); }
.nrf-end.is-ready .nrf-end-text { color: var(--ok); }

/* ---- Der Satz unter dem Diagramm ----
   Auf die Breite der Knoten und nicht des Editors: Er liest sich als Unterschrift unter
   dem Bild, und eine Zeile ueber 1100 Pixel liest niemand bis zum Ende. */
.nrf-sentence {
    display: flex; flex-direction: column; gap: 3px;
    width: 100%; max-width: 560px; margin: 0 auto;
    font-size: 12px; line-height: 1.55; color: var(--text-dim);
}

@media (max-width: 560px) {
    .nrf { padding: 16px 10px; --nrf-cut: 12px; }
    .nrf-shape { gap: 10px; padding: 9px 11px; }
    .nrf-else-text { display: none; }
}

@media (prefers-reduced-motion: reduce) {
    .nrf-go { transition: none; }
    .nrf-node:hover .nrf-go { transform: none; }
}

/* ---------- Ro.Ki im Regeleditor ---------- */
/* Ein Dialog, in dem jemand spricht: oben Ro.Ki mit Wappen und Sprechblase, darunter zwei
   Beispielmeldungen zur Wahl und die Vorlagen mit ihrem Ergebnis. Die Farbe ist die der
   Marke (--brand-*, wie im Wappen) und nicht die Warnfarbe oder das Gruen: Ro.Ki ist
   kein Zustand, sondern ein Mitarbeiter. Siehe Components/Notify/NotifyRuleEditor.razor. */
.rk {
    --rk: var(--accent);
    --rk-soft: color-mix(in srgb, var(--rk) 10%, var(--panel));
    display: flex; flex-direction: column; gap: 10px;
}

/* ---- Die Vorstellung ---- */
.rk-hello { display: flex; align-items: flex-start; gap: 11px; }
.rk-avatar {
    flex: none; width: 38px; height: 38px; padding: 5px;
    border-radius: 12px;
    background: radial-gradient(circle at 30% 25%,
                color-mix(in srgb, var(--rk) 30%, var(--panel-raised)) 0%,
                var(--panel-inset) 75%);
    border: 1px solid color-mix(in srgb, var(--rk) 40%, var(--line));
    box-shadow: 0 6px 18px -10px color-mix(in srgb, var(--rk) 80%, transparent);
}
.rk-avatar .asx-roki { width: 100%; height: 100%; }

/* Die Sprechblase mit der Spitze zum Wappen: Der Satz darin ist Ro.Kis und nicht der
   des Formulars - deshalb in der ersten Person. */
.rk-bubble {
    position: relative; flex: 1; min-width: 0;
    padding: 7px 12px 8px;
    border: 1px solid color-mix(in srgb, var(--rk) 35%, var(--line));
    border-radius: 4px 12px 12px 12px;
    background: var(--rk-soft);
}
.rk-bubble::before {
    content: ""; position: absolute; left: -7px; top: 10px;
    width: 12px; height: 12px; transform: rotate(45deg);
    background: var(--rk-soft);
    border-left: 1px solid color-mix(in srgb, var(--rk) 35%, var(--line));
    border-bottom: 1px solid color-mix(in srgb, var(--rk) 35%, var(--line));
}
.rk-bubble p { margin: 2px 0 0; font-size: 12px; line-height: 1.45; color: var(--text); }
.rk-who { display: flex; align-items: center; gap: 7px; font-weight: 700; font-size: 12.5px; color: var(--text); }
.rk-ki {
    padding: 1px 7px; border-radius: 999px;
    background: color-mix(in srgb, var(--rk) 18%, transparent); color: var(--rk);
    font-family: var(--font-mono); font-size: 9px; font-weight: 700;
    letter-spacing: .1em; text-transform: uppercase;
}

/* ---- Die Schritte ---- */
.rk-step {
    display: flex; align-items: center; gap: 7px; margin-top: 2px;
    font-size: 12.5px; font-weight: 600; color: var(--text);
}
.rk-step-n {
    display: inline-flex; align-items: center; justify-content: center; flex: none;
    width: 18px; height: 18px; border-radius: 50%;
    background: var(--rk); color: var(--on-accent, #fff);
    font-family: var(--font-mono); font-size: 11px; font-weight: 700;
}
.rk-step-note { margin-left: auto; font-size: 11px; font-weight: 400; color: var(--text-faint); }
.rk-sub { margin-top: -2px; font-size: 11.5px; font-weight: 600; color: var(--text-dim); }

/* ---- Die Wahl: zwei Beispielmeldungen ----
   Zwei Karten mit Radio-Kreis statt eines Umschalters: Man waehlt, wie die Meldung
   aussieht, und sieht es in der Karte. */
.rk-choice { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr); gap: 8px; }
@media (max-width: 640px) {
    .rk-choice { grid-template-columns: 1fr; }
}
.rk-opt {
    display: flex; flex-direction: column; gap: 6px; min-width: 0;
    padding: 9px 10px; text-align: left; cursor: pointer;
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--panel-raised); color: var(--text); font: inherit;
    transition: border-color .12s, background .12s, box-shadow .12s;
}
.rk-opt:hover { border-color: var(--line-strong); }
.rk-opt.active {
    border-color: var(--rk);
    background: var(--rk-soft);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--rk) 22%, transparent);
}
.rk-opt:focus-visible { outline: 2px solid var(--rk); outline-offset: 2px; }
.rk-opt-head { display: flex; align-items: center; gap: 8px; }
.rk-opt-title { font-size: 12.5px; font-weight: 600; }
.rk-radio {
    flex: none; width: 16px; height: 16px; border-radius: 50%;
    border: 1.5px solid var(--line-strong); background: var(--panel);
    transition: border-color .12s, box-shadow .12s;
}
.rk-opt.active .rk-radio { border-color: var(--rk); box-shadow: inset 0 0 0 3.5px var(--panel), inset 0 0 0 8px var(--rk); }
.rk-opt-note { font-size: 11px; line-height: 1.4; color: var(--text-dim); }

/* Die Meldung im Kleinen: ein Kaertchen, wie es im Postfach oder im Chat erscheint. */
.rk-msg {
    display: flex; flex-direction: column; gap: 1px;
    padding: 7px 9px;
    border: 1px solid var(--line); border-left: 3px solid var(--line-strong);
    border-radius: 6px;
    background: var(--panel);
    font-size: 11px; line-height: 1.45; color: var(--text-dim);
}
.rk-msg-title { font-size: 11.5px; font-weight: 700; color: var(--text); }
.rk-msg-line { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Ro.Kis Absatz in der Meldung: abgesetzt, mit Wappen und Namen - so erkennt man ihn
   auch spaeter in der echten Meldung wieder. */
.rk-msg-roki {
    display: flex; flex-direction: column; gap: 2px;
    margin-top: 5px; padding: 6px 8px;
    border-radius: 6px;
    background: color-mix(in srgb, var(--rk) 12%, var(--panel));
    border: 1px dashed color-mix(in srgb, var(--rk) 45%, transparent);
}
.rk-msg-roki-who {
    display: inline-flex; align-items: center; gap: 5px;
    font-size: 10px; font-weight: 700; color: var(--rk);
    letter-spacing: .04em;
}
.rk-msg-roki-who .asx-roki { width: 14px; height: 14px; }
.rk-msg-roki-text { font-size: 11px; line-height: 1.5; color: var(--text); }
.rk-opt:not(.active) .rk-msg-roki { opacity: .75; }

/* ---- Die Vorlagen: Karten mit Beispiel ---- */
.rk-tpls {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 6px;
}
.rk-tpl {
    display: flex; flex-direction: column; gap: 3px; min-width: 0;
    padding: 7px 9px 8px; text-align: left; cursor: pointer;
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--panel-raised); color: var(--text); font: inherit;
    transition: border-color .12s, background .12s;
}
.rk-tpl:hover { border-color: color-mix(in srgb, var(--rk) 55%, var(--line)); }
.rk-tpl.active { border-color: var(--rk); background: var(--rk-soft); }
.rk-tpl:focus-visible { outline: 2px solid var(--rk); outline-offset: 2px; }
.rk-tpl-head { display: flex; align-items: center; gap: 7px; }
.rk-tpl-check {
    display: inline-flex; align-items: center; justify-content: center; flex: none;
    width: 17px; height: 17px; border-radius: 5px;
    border: 1px solid var(--line-strong); color: var(--text-faint);
    font-size: 11px; font-weight: 700; line-height: 1;
}
.rk-tpl.active .rk-tpl-check { border-color: var(--rk); background: var(--rk); color: var(--on-accent, #fff); }
.rk-tpl-title { font-size: 12px; font-weight: 600; }

/* Das Beispiel: kursiv und in Anfuehrungszeichen, zwei Zeilen - es ist ein Zitat aus einer
   Meldung, die es noch nicht gibt. */
.rk-tpl-ex {
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
    font-size: 11px; line-height: 1.45; font-style: italic; color: var(--text-dim);
}
.rk-tpl.active .rk-tpl-ex { color: var(--text); }

/* ---- Der eigene Auftrag ---- */
.rk-own {
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--panel-raised);
}
.rk-own > summary {
    display: flex; align-items: center; gap: 8px; cursor: pointer;
    padding: 6px 10px; font-size: 12px; font-weight: 600; color: var(--text);
    list-style: none;
}
.rk-own > summary::-webkit-details-marker { display: none; }
.rk-own > summary::before {
    content: "▸"; color: var(--text-faint); font-size: 10px; transition: transform .12s;
}
.rk-own[open] > summary::before { transform: rotate(90deg); }
.rk-own-note { margin-left: auto; font-size: 11px; font-weight: 400; color: var(--text-faint); }
.rk-own > textarea { display: block; width: calc(100% - 20px); margin: 0 10px; }
.rk-own-hint { display: block; padding: 5px 10px 8px; font-size: 11px; line-height: 1.45; color: var(--text-dim); }

/* ---- Der Messbericht ---- */
.rk-pdf {
    display: flex; align-items: flex-start; gap: 10px;
    padding: 8px 10px;
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--panel-raised);
}
.rk-pdf-ico {
    flex: none; display: flex; align-items: center; justify-content: center;
    width: 26px; height: 32px; border-radius: 4px 10px 4px 4px;
    background: color-mix(in srgb, var(--fault) 14%, var(--panel));
    border: 1px solid color-mix(in srgb, var(--fault) 40%, var(--line));
    color: var(--fault); font-family: var(--font-mono); font-size: 9px; font-weight: 700;
}
.rk-pdf-text { display: flex; flex-direction: column; gap: 4px; flex: 1; min-width: 0; }
.rk-pdf-title { display: flex; align-items: center; gap: 5px; font-size: 12.5px; font-weight: 600; }
.rk-pdf-note { font-size: 11px; line-height: 1.45; color: var(--text-dim); }
.rk-pdf > .toggle { margin-top: 2px; }

@media (max-width: 560px) {
    .rk-hello { gap: 9px; }
    .rk-avatar { width: 32px; height: 32px; padding: 4px; }
    .rk-step-note { display: none; }
}

.nre-card {
    display: flex; flex-direction: column; min-width: 0;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--panel-raised);
}

/* Der Kopf in der Schrift der Kanalzeilen (mono, gesperrt, klein): Er benennt die Karte
   und konkurriert nicht mit den Beschriftungen darin. */
.nre-card-head {
    display: flex; align-items: center; gap: 6px;
    padding: 7px 11px;
    border-bottom: 1px solid var(--line);
    font-family: var(--font-mono); font-size: 10px;
    letter-spacing: 0.1em; text-transform: uppercase;
    color: var(--text-dim);
}

.nre-card-body {
    display: flex; flex-direction: column; gap: 10px;
    padding: 10px 11px; min-width: 0;
}

/* Ein Teil innerhalb einer Karte - "Fahrzeuge" und "Kanäle", oder der Anlass und seine
   Bedingungen. Die gestrichelte Linie trennt sie, ohne eine zweite Karte aufzumachen:
   Sie gehören zusammen, sind aber nicht dasselbe. */
.nre-part { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.nre-part + .nre-part,
.nre-trigs + .nre-part { padding-top: 9px; border-top: 1px dashed var(--line); }

.nre-part-head {
    display: flex; align-items: center; gap: 5px;
    font-size: 11.5px; font-weight: 600; color: var(--text);
}

/* Ein Feld mit Beschriftung - flacher als .field, weil im Dialog zwanzig davon stehen.
   Als label und nicht als div: Der Klick auf die Beschriftung landet dann im Feld. */
.nre-mini { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.nre-mini > span:first-child {
    display: flex; align-items: center; gap: 5px;
    font-size: 11.5px; color: var(--text-dim);
}

/* Die Feinheiten als Raster: vier flache Felder auf zwei Zeilen statt vier
   untereinander. */
.nre-fine {
    display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 9px 12px;
    align-content: start;
}

/* Ein Feld, das über beide Spalten geht – der Radiusschieber, der von einer halben
   Spalte nichts hätte. */
.nre-fine-wide { grid-column: 1 / -1; }

@media (max-width: 520px) {
    .nre-fine { grid-template-columns: 1fr; }
}

/* Eine Zeile, in der ein Knopf, ein flaches Feld und ein Hinweis nebeneinander stehen. */
.nre-inline {
    display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap;
}
.nre-inline > .nre-mini { width: 150px; }
.nre-inline > .check { padding-bottom: 7px; }

/* Der Einwand in der Zeile und nicht als eigener Kasten darunter: Er ist ein Satz, und
   ein gerahmter Kasten für einen Satz ist eine Zeile Polsterung für drei Wörter. */
.nre-warn {
    display: inline-flex; align-items: center; gap: 6px;
    padding-bottom: 7px;
    font-size: 11.5px; color: var(--warn);
}



/* Zwei Spalten, wo zwei Dinge zusammengehoeren und beide kurz sind: Fahrzeuge neben
   Kanaelen, Ro.Ki neben den Feinheiten. Untereinander waere es doppelt so viel
   Rollweg fuer dieselbe Information. */
.nre-cols {
    display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 16px 20px;
}
@media (max-width: 860px) {
    .nre-cols { grid-template-columns: 1fr; }
}

.nre-note { margin: 0; font-size: 11.5px; line-height: 1.55; color: var(--text-dim); }

/* ---- Die Ausloeser ---- */
/* Als Raster und nicht als Liste: Fuenf Ausloeser untereinander sind fuenf Zeilen, die
   man liest; fuenf Karten sind eine Wahl, die man trifft. */
/* Eine Spalte und je Anlass EINE Zeile. Vorher trug jede Karte ihren Erklärsatz mit
   (zwei Zeilen Fließtext, fünfmal) - das waren gut hundert Pixel Prosa für eine Wahl,
   die man einmal trifft. Die Erklärung steht jetzt im Tooltip der Karte, und zwar
   vollständig. */
.nre-trigs {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(232px, 1fr)); gap: 7px;
}

/* Der Koerper des Anlass-Dialogs. Er traegt dieselben Teile wie vorher die Karte, nur
   ohne deren Rahmen - der Dialog ist der Rahmen. */
.nre-dlg { padding: 0; gap: 12px; }

/* Fahrzeuge und Kanaele nebeneinander: Beides sind Listen, und beide wachsen in die
   Hoehe. Untereinander waere der Dialog zweimal so hoch und in der unteren Haelfte leer.
   Die gestrichelte Trennlinie zwischen zwei Abschnitten faellt dabei weg - sie trennt
   untereinander, nicht nebeneinander. */
@media (min-width: 900px) {
    .nre-dlg-who {
        display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 16px 20px;
        align-items: start;
    }
    .nre-dlg-who > .nre-part + .nre-part { padding-top: 0; border-top: 0; }
}

/* Der erste Teil im Dialog braucht die gestrichelte Linie nicht: Ueber ihm steht kein
   zweiter, sondern der Kopf des Dialogs. */
.nre-dlg > .nre-part:first-child { padding-top: 0; border-top: 0; }
/* Die Kacheln sind groesser, seit die Einzelheiten des Anlasses im Dialog stehen: Die
   Karte, die vorher Bedingungszeilen und eine Landkarte trug, hat nur noch die Wahl und
   drei Zeilen Kurzform. Der gewonnene Platz gehoert der Wahl - sie ist die erste Frage
   des Formulars, und eine Kachel von 30 Pixeln Hoehe sieht aus wie ein Listeneintrag,
   nicht wie eine Entscheidung.

   Oben das Zeichen und der Name, darunter der Satz, der den Anlass erklaert. Er stand
   vorher im title-Attribut - dort steht er fuer den, der eine Sekunde stillhaelt, und
   genau der waehlt nicht mehr, sondern sucht schon. */
.nre-trig {
    display: flex; align-items: flex-start; gap: 10px; text-align: left;
    min-width: 0;
    padding: 11px 12px; cursor: pointer;
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--panel-raised); color: var(--text); font: inherit;
    transition: border-color .12s, background .12s;
}
.nre-trig:hover { border-color: var(--line-strong); }
.nre-trig.active {
    border-color: var(--accent); background: var(--accent-soft);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 18%, transparent);
}

/* Das Zeichen auf Hoehe der ersten Zeile und nicht mittig zur ganzen Kachel: Neben einem
   zweizeiligen Erklaersatz saesse es sonst auf halber Hoehe des Absatzes und nicht neben
   dem Namen, zu dem es gehoert. */
.nre-trig-ico { display: flex; flex: none; color: var(--text-dim); padding-top: 1px; }
.nre-trig-ico svg { width: 19px; height: 19px; }
.nre-trig.active .nre-trig-ico { color: var(--accent); }

.nre-trig-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.nre-trig-name {
    display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
    font-weight: 600; font-size: 13px; min-width: 0; line-height: 1.25;
}

/* Der Erklaersatz bleibt gedaempft und bricht um: Er ist die Beigabe zum Namen, und ein
   abgeschnittener halber Satz erklaert weniger als zwei ganze Zeilen. */
.nre-trig-note { font-size: 11px; line-height: 1.4; color: var(--text-dim); }
.nre-trig.active .nre-trig-note { color: var(--text); }

/* ---- Die Bedingungen ---- */
/* Feste Spalten und keine umbrechende Reihe: Drei Bedingungen untereinander sollen
   ihre Felder in einer Senkrechten haben, sonst liest man dreimal neu, was wo steht. */
.nre-conds { display: flex; flex-direction: column; gap: 7px; }
.nre-cond {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 132px 106px 42px 34px;
    gap: 7px; align-items: center;
}
@media (max-width: 720px) {
    .nre-cond { grid-template-columns: minmax(0, 1fr) 1fr; }
    .nre-cond-unit { display: none; }
}
.nre-cond-unit { font-size: 11px; color: var(--text-faint); text-align: center; }

/* Das Wort zwischen zwei Bedingungen. Es steht eingerueckt und klein: Es ist kein
   Bedienelement - die Wahl trifft man im Kopf des Abschnitts -, sondern der Lesefaden,
   der aus drei Zeilen einen Satz macht. */
.nre-cond-join {
    display: block; padding-left: 4px;
    font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.1em;
    text-transform: uppercase; color: var(--text-faint);
}

/* ---- Die gewaehlten Signale ---- */
.nre-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.nre-chip {
    display: inline-flex; align-items: center; gap: 7px;
    padding: 4px 6px 4px 9px;
    background: var(--panel-inset); border: 1px solid var(--line-strong);
    border-radius: 999px; font-size: 11.5px;
}
.nre-chip-unit { color: var(--text-faint); font-size: 10.5px; }
.nre-chip button {
    border: 0; background: transparent; cursor: pointer; padding: 0 3px;
    color: var(--text-faint); font-size: 11px; line-height: 1;
}
.nre-chip button:hover { color: var(--fault); }

.nre-prompt { min-height: 58px; resize: vertical; line-height: 1.5; font-size: 12px; }


/* ---------- Das kleine „i" ---------- */
/* Es steht neben einer Beschriftung und traegt die ausfuehrliche Erklaerung im Tooltip
   (siehe Components/Shared/InfoHint.razor). Faint und nicht in der Textfarbe: Es ist ein
   Angebot und keine Aussage - wer es nicht braucht, soll es nicht lesen muessen. */
/* Ein Knopf, kein Zierzeichen: Er reagiert auf Klick, auf Tastatur und aufs Überfahren.
   Vorher trug er die Erklärung im title-Attribut - das sah aus wie ein Knopf, tat beim
   Klick aber nichts, und der Tooltip des Systems erschien erst nach einer Sekunde
   Stillhalten. */
.ihint {
    display: inline-flex; align-items: center; justify-content: center; flex: none;
    position: relative;
    width: 17px; height: 17px; padding: 0;
    border: 0; border-radius: 50%;
    background: transparent; color: var(--text-faint);
    cursor: pointer;
    transition: color .12s, background .12s;
}
.ihint svg { width: 13px; height: 13px; }
.ihint:hover, .ihint.open { color: var(--accent); background: var(--accent-soft); }
.ihint:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Die Sprechblase.

   position: fixed und nicht absolute: Der Dialogkörper trägt overflow: auto, und eine
   absolut gesetzte Blase würde an seinem Rand abgeschnitten - dort, wo die Erklärungen
   der rechten Spalte stehen. Ohne Angabe von top/left bleibt eine fixierte Box an ihrer
   statischen Stelle stehen, also neben ihrem „i"; sie hängt dabei am Overlay (dessen
   backdrop-filter macht es zum Bezugsrahmen) und nicht am Dialog. */
/* Ein inline-Kasten ohne eigene Größe. Er gibt der Blase eine vorhersagbare statische
   Stelle – siehe die Begründung in InfoHint.razor. */
.ihint-anchor { display: inline; }

.ihint-pop {
    position: fixed;
    z-index: 120;

    /* Von der Stelle neben dem Zeichen: ein Stück nach unten, ein Stück nach links, damit
       die Blase unter dem „i" hängt und nicht daneben klebt. */
    margin: 16px 0 0 -14px;
    width: max-content; max-width: min(320px, 68vw);
    padding: 9px 11px;
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-sm);
    background: var(--panel);
    box-shadow: var(--shadow);

    /* Die Textfarbe und nicht die gedämpfte: Gedämpft ist richtig für eine Fußnote, die
       dauerhaft dasteht – hier ist es der Text, den jemand gerade aufgeschlagen hat. */
    color: var(--text);
    font-family: var(--font-body); font-size: 11.5px; font-weight: 400;
    line-height: 1.5; text-align: left; text-transform: none; letter-spacing: 0;

    /* pre-line statt normal: Fast alle Erklaerungen sind ein Absatz und brauchen nur den
       Umbruch am Rand - den macht pre-line genauso. Zwei davon tragen aber zwei Absaetze
       (die Sichtbarkeit nennt beide Zustaende), und mit "normal" liefen die zu einem
       zusammen. */
    white-space: pre-line;

    visibility: hidden; opacity: 0;
    pointer-events: none;

    /* Verschwinden ohne Verzoegerung und fast ohne Blende: Wer die Maus wegnimmt, ist
       fertig mit dem Lesen. Das Nachhaengen war der zweite Teil des Aergers - eine Blase,
       die noch dasteht, waehrend man schon woanders hinsieht, wirkt wie ein Fehler. */
    transition: opacity .06s linear 0s, visibility 0s linear .06s;
}

/* Erst nach einer knappen halben Sekunde auf dem Zeichen.

   Vorher erschien die Blase in dem Augenblick, in dem der Zeiger das "i" beruehrte. Auf
   dem Weg zu einem Feld streift man mehrere davon, und jedes klappte kurz auf: Es sah
   aus, als reiche schon die Naehe. Die Verzoegerung trennt das Vorbeifahren vom
   Hinschauen - wer die Erklaerung will, bleibt stehen, und nur dann kommt sie.

   (hover: hover): Auf dem Tastbildschirm gibt es kein Verlassen des Zeichens, dort bleibt
   der Klick der Weg zur Erklaerung. */
@media (hover: hover) and (pointer: fine) {
    .ihint:hover .ihint-pop {
        visibility: visible; opacity: 1;
        transition: opacity .1s linear .45s, visibility 0s linear .45s;
    }
}

/* Angeklickt oder mit der Tastatur erreicht: sofort. Hier ist die Absicht schon
   ausgesprochen, und auf eine ausgesprochene Absicht wartet man nicht. Die Regel steht
   nach der Hover-Regel, damit sie deren Verzoegerung ueberschreibt, wenn beides gilt. */
.ihint.open .ihint-pop,
.ihint:focus-visible .ihint-pop {
    visibility: visible; opacity: 1;
    transition: opacity .1s linear 0s, visibility 0s linear 0s;
}

/* Nach links öffnen. Für die „i" in der rechten Dialogspalte: Eine Blase von 320 Pixeln
   nach rechts läuft dort auf einem 1280er Bildschirm aus dem Fenster, und fixiert kann
   sie nicht zurückgeschoben werden. Erst ab 900px, denn darunter stehen die Karten
   untereinander – dann sitzen dieselben „i" links und die Blase bräche nach links aus. */
@media (min-width: 900px) {
    .ihint-flip .ihint-pop { margin-left: -310px; }
}

/* Aufgeklappt bleibt die Blase stehen, bis der Knopf den Fokus verliert - dann darf man
   auch hineinklicken, um Text zu markieren. */
.ihint.open .ihint-pop { pointer-events: auto; }

/* Weniger Bewegung heisst weniger Blende, nicht weniger Verzoegerung: Die Wartezeit ist
   hier keine Animation, sondern die Unterscheidung zwischen Vorbeifahren und Hinschauen. */
@media (prefers-reduced-motion: reduce) {
    .ihint-pop { transition: visibility 0s linear 0s; }

    @media (hover: hover) and (pointer: fine) {
        .ihint:hover .ihint-pop { transition: visibility 0s linear .45s; }
    }

    .ihint.open .ihint-pop,
    .ihint:focus-visible .ihint-pop { transition: none; }
}

/* ---------- Markenzeichen der Zustellwege ---------- */
/* Die Groesse kommt als --clogo vom Aufrufer: In der Liste 30 Pixel, in der Kanalauswahl
   einer Regel 22, im ersten Schritt des Assistenten drei mal 17. Ueber ein Attribut und
   nicht ueber Klassen, weil es keine drei Groessen sind, sondern eine Zahl. */
.clogo {
    display: inline-flex; flex: none; line-height: 0;
    width: var(--clogo, 22px); height: var(--clogo, 22px);
    vertical-align: middle;
}
.clogo-svg { display: block; width: 100%; height: 100%; }

/* E-Mail und der eigene Empfaenger haben keine Marke und nehmen die Portalfarbe - siehe
   die Begruendung in ChannelLogo.razor. currentColor im SVG liest sie hier ab. */
.clogo-mail, .clogo-webhook, .clogo-sms { color: var(--accent); }

/* Die weisse Slack-Kachel braucht auf hellem Grund eine Kante, sonst schwebt das
   Windrad im Nichts. Im dunklen Theme setzt sie sich von selbst ab. */
.clogo-slack { border-radius: 23%; box-shadow: inset 0 0 0 1px var(--line); }

/* ---------- Ein Kanal ---------- */

.nt-chan {
    display: flex; align-items: center; gap: 10px;
    padding: 9px 11px;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--panel-raised);
}
.nt-chan > .col { flex: 1; }
.nt-chan-name {
    display: flex; align-items: center; gap: 8px; min-width: 0;
    font-family: var(--font-ui); font-weight: 600; font-size: 12.5px;
}
.nt-chan-name > .led { flex: none; font-weight: 500; }
.nt-chan-sub {
    font-size: 11px; color: var(--text-dim);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.nt-chan-err {
    font-size: 10.5px; color: var(--fault);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ---------- Der Verlauf ---------- */
/* Eine Tabelle und keine Liste mehr: Der Verlauf waechst ohne Ende, und wer darin sucht,
   vergleicht Zeilen - Zeit, Regel, Fahrzeug untereinander. Filter im Kopf wie in der
   Geraeteliste (.dev-filters), die Tabelle randlos im Panel, das Blaettern darunter. */
.nt-log-panel > .panel-head { align-items: flex-start; padding: 10px 12px; flex-wrap: wrap; }
.nt-log-panel > .panel-head > .panel-title { flex: 1 1 100%; }
.nt-log-panel > .panel-body { padding: 0; }
.nt-log-panel .table-wrap { border: 0; border-radius: 0; max-height: 64vh; overflow: auto; }

.nt-log-table td { vertical-align: top; }
.nt-log-tr { cursor: pointer; }
.nt-log-tr.open > td { background: var(--panel-raised); }
.nt-log-when { font-size: 11px; white-space: nowrap; color: var(--text-dim); }

/* Abschneiden statt umbrechen: Eine Meldung, die sich ueber drei Zeilen legt, macht aus
   fuenfzig Zeilen eine Seite mit zwanzig. Vollstaendig steht sie im Titel und in der
   aufgeklappten Zeile. */
.nt-log-msg { max-width: 440px; }
.nt-log-title {
    display: block; font-size: 12.5px; font-weight: 600;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.nt-log-err, .nt-log-dim {
    display: block; font-size: 10.5px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.nt-log-err { color: var(--fault); }
.nt-log-dim { color: var(--text-dim); }
.nt-log-cell { max-width: 220px; }
.nt-log-chan {
    max-width: 200px; font-size: 11.5px; color: var(--text-dim);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.nt-log-caret { color: var(--text-faint); font-size: 11px; }

/* Regel und Fahrzeug sind Filter zum Anklicken - sie sehen aus wie Text, bis man sie
   ueberfaehrt. Als Verweis unterstrichen waere jede zweite Zelle der Tabelle blau. */
.nt-log-link {
    display: block; max-width: 100%; padding: 0; border: 0; background: none;
    font: inherit; font-size: 12.5px; color: var(--text); text-align: left; cursor: pointer;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.nt-log-link:hover { color: var(--accent); text-decoration: underline; }
.nt-log-chipbtn { cursor: pointer; }
.nt-log-chipbtn:hover { filter: brightness(1.15); }

.nt-log-detail > td { padding: 2px 14px 12px; background: var(--panel-raised); }
.nt-log-errfull { margin: 6px 0; font-size: 12px; color: var(--fault); white-space: pre-wrap; }
.nt-log-ids { margin-top: 6px; font-size: 10.5px; color: var(--text-faint); }

/* ---------- Hinweise ---------- */

/* Ein Hinweis ist kein Fehler: Er trägt die Farbe der Fläche und nicht die des
   Alarms. Rot für "so geht es" hat die Oberfläche schon einmal gekostet, dass
   niemand mehr hinsah, wenn wirklich etwas rot war. */
.nt-hint {
    padding: 9px 11px;
    border: 1px solid var(--line);
    border-left: 2px solid var(--accent);
    border-radius: var(--radius-sm);
    background: var(--panel-inset);
    font-size: 11.5px; line-height: 1.5; color: var(--text-dim);
}

/* Derselbe Kasten, aber mit der Kante des Einwands: fuer den Hinweis, der eine Folge
   ankuendigt, die jemand anderen trifft - etwa das Zuruecknehmen einer Kanalfreigabe,
   ueber die die Regeln von Kollegen melden. Nicht rot: Es ist kein Fehler, sondern eine
   Entscheidung, die man mit dem Wissen treffen soll. */
.nt-hint-warn {
    border-left-color: var(--warn);
    color: var(--text);
}

.nt-ok {
    display: flex; align-items: center; gap: 9px;
    padding: 10px 12px;
    border: 1px solid color-mix(in srgb, var(--ok) 45%, var(--line));
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--ok) 10%, var(--panel));
    font-size: 12.5px;
}

/* =====================================================================
   Die beiden Assistenten

   Gemeinsame Klassen, weil sie dieselbe Gangart haben: eine Überschrift
   je Schritt, große Auswahlkacheln statt eines Aufklappmenüs, und die
   Erklärung dort, wo sie gebraucht wird - nicht in einem Handbuch.
   ===================================================================== */

.nw { display: flex; flex-direction: column; gap: 14px; }

/* Die Auswahl: Kacheln und kein Aufklappmenü. Wer einen Zustellweg zum ersten Mal
   einrichtet, weiß nicht, was "Webhook" bedeutet - die Kachel trägt den Satz, der
   es erklärt, ein Menüeintrag trägt ihn nicht. */
.nw-kinds { display: grid; gap: 10px; }

.nw-kind {
    display: grid;
    /* 55 statt 34 Pixel: Die Webhook-Zeile zeigt drei Zeichen nebeneinander, und die
       brauchen zusammen mit ihren Abstaenden genau diese Breite. */
    grid-template-columns: 55px 1fr;
    grid-template-rows: auto auto;
    gap: 2px 12px;
    text-align: left;
    padding: 12px 14px;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--panel-raised);
    cursor: pointer;
    transition: border-color .12s, background .12s;
}
.nw-kind:hover { border-color: var(--line-strong); }
.nw-kind.active { border-color: var(--accent); background: var(--accent-soft); }

.nw-kind-ico {
    grid-row: span 2; align-self: center;
    display: flex; flex-wrap: wrap; gap: 2px; align-items: center;
    line-height: 1;
}
.nw-kind-name { font-family: var(--font-ui); font-weight: 600; font-size: 13px; color: var(--text); }
.nw-kind-note { font-size: 11.5px; line-height: 1.45; color: var(--text-dim); }

/* Zwei kurze Felder nebeneinander im Kanalassistenten – Kontoadresse und Token,
   Projekt und Vorgangsart. Untereinander wäre der Schritt doppelt so hoch, und beide
   gehören ohnehin zusammen. */
.nw-two {
    display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 10px 12px;
}
@media (max-width: 620px) {
    .nw-two { grid-template-columns: 1fr; }
}

/* ---------- Fahrzeug- und Kanalauswahl ---------- */

/* 120px: Drei Zeilen – genug, um zu sehen, dass die Liste weitergeht, und wenig genug,
   dass der Dialog auf einem 1080p-Bildschirm ohne Rollen auskommt. Bei 280px war allein
   diese Liste ein Drittel der Dialoghöhe. Gesucht wird ohnehin über das Feld darüber:
   Bei dreihundert Fahrzeugen hilft keine Listenhöhe. */
.nw-veh, .nw-chans {
    display: flex; flex-direction: column; gap: 4px;
    max-height: 104px; overflow-y: auto;
    padding: 4px;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--panel-inset);
}

.nw-veh-row, .nw-chan-row {
    display: flex; align-items: center; gap: 10px;
    padding: 7px 9px;
    border-radius: var(--radius-sm);
    cursor: pointer;
}
.nw-veh-row:hover, .nw-chan-row:hover { background: var(--panel-raised); }
.nw-veh-row.active, .nw-chan-row.active { background: var(--accent-soft); }
.nw-veh-row > .col, .nw-chan-row > .col { flex: 1; }

.nw-veh-name { font-size: 12.5px; font-weight: 600; }
.nw-veh-sub { font-size: 10.5px; color: var(--text-dim); }

/* Der Knopf "Alle des Bereichs" neben dem Suchfeld.

   Vorher waren "Alle" und "Auswahl" ein Umschalter mit zwei gleich grossen Haelften, und
   die Fahrzeugliste erschien erst unter "Auswahl". Das stellte eine Frage vor die
   Information: Wer wissen wollte, welche Fahrzeuge es ueberhaupt gibt, musste erst den
   Zustand wechseln. Jetzt steht die Liste immer, und "Alle" ist ein einzelner Knopf
   daneben, der sie ueberstimmt - eingerastet, solange er gilt. */
.nre-veh-bar { display: flex; align-items: center; gap: 6px; min-width: 0; }
.nre-veh-bar > .input { flex: 1; min-width: 0; height: 32px; font-size: 12px; }

.nre-all {
    flex: none; height: 32px; padding: 0 12px;
    border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
    background: var(--panel-inset); color: var(--text-dim);
    font-family: var(--font-body); font-size: 12px; font-weight: 600;
    white-space: nowrap; cursor: pointer;
    transition: color .12s, background .12s, border-color .12s;
}
.nre-all:hover { color: var(--text); }
.nre-all:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.nre-all.active {
    background: var(--accent-soft); border-color: var(--accent); color: var(--accent);
    cursor: default;
}

/* Gilt "Alle", bleibt die Liste sichtbar, tritt aber zurueck: Sie zeigt den Bestand und
   nicht eine Auswahl. Die Zeile unter dem Zeiger kommt zurueck - sie ist gleich die, die
   angehakt wird. */
.nw-veh-all .nw-veh-row { opacity: .55; }
.nw-veh-all .nw-veh-row:hover { opacity: 1; }

/* Die Zeile unter der Liste: wie viele es sind. Klein und gedaempft - sie bestaetigt
   etwas, das man daneben schon sieht. */
.nre-veh-note { font-size: 11px; color: var(--text-dim); }
/* Messdaten als Anhang: die Formate untereinander, jedes mit seinem Aufbau daneben. */
.nre-data-opts { display: flex; flex-direction: column; gap: 7px; }
.nre-data-opts .check { flex-wrap: wrap; row-gap: 2px; }
.nre-data-hint { font-size: 11px; color: var(--text-faint); }

/* ---------- Privat oder im Bereich ---------- */
/* Die Wahl steht in zwei Dialogen (Kanalassistent, Regeleditor) und sieht in beiden
   gleich aus - siehe Components/Shared/VisibilityPick.razor. Der Umschalter ist der
   gewoehnliche .seg; hier kommen nur die Zeichen auf den Knoepfen und der erklaerende
   Satz darunter hinzu. */
.vis-pick { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.vis-pick .seg { align-self: flex-start; }
.vis-pick .seg button { display: inline-flex; align-items: center; gap: 6px; }
.vis-pick .seg button svg { width: 13px; height: 13px; flex: none; }

/* Welcher der beiden Zustaende gilt, soll aus dem Augenwinkel zu sehen sein. Vorher
   unterschied den geltenden Zustand nur eine etwas hellere Flaeche - bei zwei aehnlich
   grossen Zeichen (Schloss, Gruppe) von zwoelf Pixeln war das ein Unterschied, den man
   suchen musste, und die Frage lautet "steht das offen?". Jetzt traegt der geltende
   Zustand Farbe: kraeftig-neutral fuer privat, Akzent fuer geteilt - zwei Zustaende mit
   verschiedener Tragweite sehen verschieden aus. Der andere Knopf tritt zurueck. Das
   kostet keinen Pixel Breite, und genau darum geht es: Der Umschalter sitzt in einer
   Kopfzeile. */
.vis-pick .seg button { color: var(--text-faint); }
.vis-pick .seg button svg { stroke-width: 2.2; }
.vis-pick .seg button:hover:not(.active) { color: var(--text-dim); }

.vis-pick .seg .vis-opt.active { box-shadow: inset 0 0 0 1px currentColor; }
.vis-pick .seg .vis-opt-private.active { background: var(--panel-raised); color: var(--text); }
.vis-pick .seg .vis-opt-shared.active { background: var(--accent-soft); color: var(--accent); }

/* Der Rahmen faerbt mit: Er ist laenger als das Zeichen und damit das, was in einer
   vollen Kopfzeile ueberhaupt auffaellt. Ohne :has() bleibt es beim Knopf - die Aussage
   steht dann immer noch da, nur leiser. */
.vis-pick .seg:has(.vis-opt-shared.active) {
    border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 8%, var(--panel-inset));
}

/* Der Satz aendert sich mit der Wahl und ist deshalb keine Fussnote, sondern die
   Antwort auf den Klick. Gedaempft trotzdem: Er steht dauerhaft da. */
.vis-note { font-size: 11px; line-height: 1.5; color: var(--text-dim); max-width: 62ch; }

/* Nebeneinander statt untereinander - fuer die eigene Karte im Regeldialog, die ueber die
   volle Dialogbreite geht. Die Knoepfe links, die Folge daneben: So ist die Karte eine
   Zeile hoch statt drei. Umbruch ab dem Punkt, an dem der Satz kuerzer wuerde als seine
   Mindestbreite - dann steht er wieder darunter, und das ist richtig so. */
.vis-pick-row { flex-direction: row; align-items: center; gap: 14px; flex-wrap: wrap; }
.vis-pick-row .seg { align-self: center; }
.vis-pick-row .vis-note { flex: 1; min-width: 240px; max-width: none; }

/* ---------- Wem etwas gehoert ---------- */
/* Zwei Marken mit verschiedener Aussage: "geteilt" heisst, ICH habe es geoeffnet;
   "von X" heisst, es gehoert einem anderen. Deshalb auch zwei Farben - die eigene in
   der Akzentfarbe, die fremde neutral. Waeren beide gleich, muesste man den Text lesen,
   um zu wissen, ob "Loeschen" fehlt oder nur uebersehen wurde. */
.vis-tag {
    display: inline-flex; align-items: center; flex: none;
    padding: 1px 7px;
    border: 1px solid var(--accent); border-radius: 999px;
    background: var(--accent-soft); color: var(--accent);
    font-size: 9.5px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
    white-space: nowrap;
}
.vis-tag-other {
    border-color: var(--line-strong);
    background: var(--panel-inset); color: var(--text-dim);
    letter-spacing: 0.02em; text-transform: none; font-weight: 600; font-size: 10px;
}

/* "privat" ist die haeufigste Antwort und deshalb die leiseste: nur eine Kontur, keine
   Flaeche. Sie steht trotzdem da - vorher trug die geteilte Regel eine Marke und die
   private nichts, und "nichts" liest sich nicht als Aussage, sondern als Luecke. Wer
   pruefen will, ob etwas offensteht, soll es an der Karte sehen und nicht daran, dass er
   nichts sieht. */
.vis-tag-private {
    border-color: var(--line-strong);
    background: transparent; color: var(--text-dim);
    font-weight: 600;
}

/* Der Zusatz an einem fremden Kanal in der Kanalauswahl des Regeleditors. Kein eigener
   Kasten: Er steht in der Unterzeile, die ohnehin die Bauart traegt. */
.nw-chan-owner { color: var(--text-faint); }

/* ---------- Empfaenger eines Mailkanals ---------- */
/* Vorher ein Textfeld mit kommagetrenntem Text. Das ist die Form, in der gespeichert
   wird, aber nicht die, in der man damit arbeitet: Eine Adresse aus der Mitte zu
   entfernen hiess, sie samt einem der beiden Kommas herauszusuchen. */
.nw-mail-chips {
    display: flex; flex-wrap: wrap; gap: 5px;
    margin-bottom: 2px;
}
.nw-mail-chip {
    display: inline-flex; align-items: center; gap: 5px;
    max-width: 100%;
    padding: 3px 4px 3px 9px;
    border: 1px solid var(--line-strong); border-radius: 999px;
    background: var(--panel-inset);
    font-size: 11.5px;
}
.nw-mail-who { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nw-mail-chip button {
    display: inline-flex; align-items: center; justify-content: center; flex: none;
    width: 16px; height: 16px; padding: 0;
    border: 0; border-radius: 50%;
    background: transparent; color: var(--text-faint);
    font-size: 10px; cursor: pointer;
    transition: color .12s, background .12s;
}
.nw-mail-chip button:hover { color: var(--fault); background: var(--panel-raised); }

.nw-mail-add { display: flex; gap: 6px; align-items: center; }
.nw-mail-add > .input { flex: 1; min-width: 0; }
.nw-mail-add > .btn { flex: none; }

.nw-mail-bad { font-size: 11px; color: var(--fault); }

/* Die Kollegen des Bereichs. Dieselbe Hoehe wie die Fahrzeug- und Kanallisten: Drei
   Zeilen zeigen, dass es weitergeht, und der Dialog bleibt auf einem 1080p-Bildschirm
   ohne Rollen bedienbar. Gesucht wird ueber das Feld darueber. */
.nw-people {
    display: flex; flex-direction: column; gap: 3px;
    max-height: 132px; overflow-y: auto;
    padding: 4px;
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--panel-inset);
}

.nw-person {
    display: flex; align-items: center; gap: 9px;
    padding: 5px 8px;
    border-radius: var(--radius-sm);
    cursor: pointer;
}
.nw-person:hover { background: var(--panel-raised); }
.nw-person.active { background: var(--accent-soft); }

/* Wer keine Adresse hat, steht trotzdem da - nur nicht waehlbar. Ihn wegzulassen hiesse,
   dass jemand ihn in der Liste sucht und nicht findet; so steht daneben, warum. */
.nw-person-off { opacity: .45; cursor: not-allowed; }
.nw-person-off:hover { background: transparent; }

/* Die Anfangsbuchstaben statt eines Bildes: Die Profilbilder liegen in der DiagX-Postgres
   und kosteten je Zeile eine Anfrage - fuer eine Auswahlliste ist das der falsche Preis. */
.nw-person-mark {
    display: inline-flex; align-items: center; justify-content: center; flex: none;
    width: 24px; height: 24px;
    border-radius: 50%;
    background: var(--panel-raised); color: var(--text-dim);
    font-size: 9.5px; font-weight: 700; letter-spacing: 0.02em;
}
.nw-person.active .nw-person-mark { background: var(--accent); color: var(--panel); }

.nw-person-name { font-size: 12.5px; font-weight: 600; }
.nw-person-mail {
    font-size: 10.5px; color: var(--text-dim);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ---------- Der Geofence-Schritt ---------- */

/* Karte links, die Angaben rechts – und die Angaben in zwei Spalten, damit sie nicht
   höher werden als die Karte. Vorher war die rechte Spalte auf 320px begrenzt: Darin
   standen vier Felder untereinander, und der Abschnitt war höher als die Karte. */
/* Karte links, die Angaben rechts – auch in der schmaleren Anlasskarte. Untereinander
   waren es 170 Pixel mehr, und die entscheiden darüber, ob der Dialog rollt. Die Felder
   bleiben dabei zweispaltig (siehe .nre-fine): zwei Zahlen nebeneinander sind schmaler
   als zwei Zahlen untereinander, und Breitengrad neben Längengrad ist ohnehin die
   Leseordnung. */
.nw-geo {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
    gap: 10px;
    min-width: 0;
}
@media (max-width: 860px) {
    .nw-geo { grid-template-columns: 1fr; }
}
/* Die Karte im Regeleditor. 190px statt 340: Der Kreis und seine Umgebung sind darin
   noch zu erkennen, und der Geofence ist der Anlass, bei dem der Dialog am höchsten
   wird - an ihm entscheidet sich, ob gerollt werden muss. Wer genauer zielen will,
   zieht den Pin, liest die Koordinate daneben und zoomt in der Karte. */
.nw-map {
    height: 168px;
    min-height: 150px;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    overflow: hidden;
}

/* ---------- Die grosse Karte ----------
   Bei 168 Pixeln setzt man einen Pin auf hundert Meter genau und einen Radius gar nicht -
   und genau das ist die Arbeit an einer Geofence-Regel. Gross heisst: die Karte ueber
   die volle Breite der Anlasskarte und so hoch, dass ein Kreis von zweihundert Metern
   samt seiner Umgebung hineinpasst. Die Felder ruecken darunter und bleiben bedienbar;
   ein Vollbild haette sie verdeckt, und die Koordinate ist die Gegenprobe zum Pin. */
.nw-geo-big { grid-template-columns: 1fr; }
.nw-map-big { height: min(62vh, 620px); }

.nre-map-btn { display: inline-flex; align-items: center; gap: 5px; flex: none; }
.nre-map-btn svg { width: 13px; height: 13px; }

.nre-geo-side { display: flex; flex-direction: column; gap: 8px; min-width: 0; }

/* ---------- Die Kreise einer Regel ---------- */
/* Erst ab dem zweiten sichtbar: Bei einem einzigen waere die Liste eine Zeile, die sagt,
   dass es eine Zeile gibt. Die Nummer vorn ist der Faden zur Karte - dort steht sie als
   Reihenfolge der Pins, hier als Ziffer. */
.nre-places {
    display: flex; flex-direction: column; gap: 4px;
    max-height: 132px; overflow-y: auto;
}
.nre-place {
    display: flex; align-items: center; gap: 8px;
    padding: 5px 6px 5px 7px;
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--panel-raised); cursor: pointer;
    transition: border-color .12s, background .12s;
}
.nre-place:hover { border-color: var(--line-strong); }
.nre-place.active {
    border-color: var(--accent);
    background: var(--accent-soft);
}
.nre-place > .col { flex: 1; }
.nre-place-n {
    display: inline-flex; align-items: center; justify-content: center; flex: none;
    width: 18px; height: 18px; border-radius: 50%;
    background: var(--panel-inset); color: var(--text-dim);
    font-size: 10px;
}
.nre-place.active .nre-place-n { background: var(--accent); color: var(--panel); }
.nre-place-name {
    font-size: 12px; font-weight: 600;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.nre-place-sub { font-size: 10px; color: var(--text-faint); }
.nre-place-x {
    flex: none; width: 20px; height: 20px; padding: 0;
    border: 0; border-radius: 50%;
    background: transparent; color: var(--text-faint);
    font-size: 10px; cursor: pointer;
}
.nre-place-x:hover { color: var(--fault); background: var(--panel-inset); }

/* ---------- Probe und Zusammenfassung ---------- */

.nw-test {
    display: flex; align-items: center; justify-content: space-between; gap: 14px;
    padding: 12px 14px;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--panel-raised);
}

.nw-ai-toggle { align-items: flex-start; gap: 10px; }

/* Die Regel als Satz, vor dem Speichern. Abgesetzt, damit sie nicht wie ein weiteres
   Eingabefeld aussieht - sie ist die Gegenprobe und nichts, was man ausfüllt. */
.nw-summary {
    padding: 11px 13px;
    border: 1px dashed var(--line-strong);
    border-radius: var(--radius-sm);
    background: var(--panel-inset);
}
.nw-summary p { margin: 5px 0 0; font-size: 12.5px; line-height: 1.55; }

/* ---------- Ro.Ki zeigt auf ein Bedienelement ---------- */
/* Ein gezeichneter Mauszeiger und nicht nur ein Rahmen: Ein Rahmen sagt "hier ist etwas",
   ein Zeiger sagt "hier klickst du" - und genau das ist die Frage, fuer die es das gibt.
   Er faehrt zum Ziel, statt dort zu erscheinen: Die Bewegung fuehrt den Blick mit, und wer
   die Seite nicht kennt, weiss danach, WO auf dem Bildschirm die Stelle liegt.

   Die ganze Ebene nimmt keine Klicks (pointer-events: none). Ein Hinweis, der den Knopf
   verdeckt, auf den er zeigt, waere die eine Art, diese Funktion unbrauchbar zu machen.
   Siehe wwwroot/js/guide.js. */
.dgx-guide {
    position: fixed; inset: 0; z-index: 4000;
    pointer-events: none;
    opacity: 0; visibility: hidden;
    transition: opacity .22s ease-out, visibility 0s linear .22s;
}
.dgx-guide.is-on { opacity: 1; visibility: visible; transition-delay: 0s; }

/* Der Ring liegt auf dem Element. Zwei Pixel Akzent und ein weicher Hof darum - kraeftig
   genug, um in einer vollen Ansicht aufzufallen, und ohne die Seite abzudunkeln: Wer den
   Knopf sucht, will die Seite daneben weiter sehen. */
.dgx-ring {
    position: fixed;
    border: 2px solid var(--accent);
    border-radius: var(--radius-sm);
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 22%, transparent),
                0 6px 24px -8px rgba(0,0,0,.45);
    animation: dgx-pulse 1.6s ease-in-out infinite;
}
@keyframes dgx-pulse {
    0%, 100% { box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 22%, transparent),
                           0 6px 24px -8px rgba(0,0,0,.45); }
    50%      { box-shadow: 0 0 0 9px color-mix(in srgb, var(--accent) 8%, transparent),
                           0 6px 24px -8px rgba(0,0,0,.45); }
}

/* Die Fahrt: dieselbe Dauer wie TRAVEL_MS in js/guide.js. Weich beschleunigt und hart
   gebremst - so sieht die Bewegung nach Zielen aus und nicht nach Treiben. */
.dgx-cursor {
    position: fixed;
    margin: -2px 0 0 -3px;
    transition: left .7s cubic-bezier(.22,.61,.36,1), top .7s cubic-bezier(.22,.61,.36,1);
    filter: drop-shadow(0 3px 5px rgba(0,0,0,.5));
}

/* Die Beschriftung: ein Satz, keine Erklaerung. Sie steht unter dem Element, ausser dort
   ist kein Platz - dann darueber (siehe js/guide.js). */
.dgx-note {
    position: fixed; transform: translate(-50%, 0);
    max-width: 260px; padding: 7px 11px;
    border: 1px solid var(--accent);
    border-radius: var(--radius-sm);
    background: var(--panel-raised); color: var(--text);
    font-size: 12px; line-height: 1.45;
    box-shadow: 0 10px 30px -12px rgba(0,0,0,.6);
}
.dgx-note.is-above { transform: translate(-50%, -100%); }

@media (prefers-reduced-motion: reduce) {
    .dgx-cursor { transition: none; }
    .dgx-ring { animation: none; }
}

/* ---------- Was Ro.Ki vorschlaegt ---------- */
/* Die Karte steht unter der Antwort: Der Vorschlag ist der Knopf zu dem, was daneben in
   Worten steht, und nicht sein Ersatz. Was den Bestand aendert, traegt hier seine
   Aufstellung - wer auf "Regel anlegen" klickt, soll vorher sehen, auf welche Fahrzeuge
   sie laeuft und worueber sie meldet. */
.asx-act {
    margin-top: 8px; padding: 9px 11px;
    border: 1px solid var(--accent);
    border-left-width: 3px;
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--accent) 7%, var(--panel));
}
.asx-act.is-done { border-color: var(--ok); background: color-mix(in srgb, var(--ok) 7%, var(--panel)); }
.asx-act.is-failed { border-color: var(--warn); background: color-mix(in srgb, var(--warn) 7%, var(--panel)); }

.asx-act-head {
    display: flex; align-items: center; gap: 7px;
    font-size: 11.5px; font-weight: 600; color: var(--text);
}
.asx-act-dot {
    width: 7px; height: 7px; border-radius: 50%; flex: none;
    background: var(--accent);
}
.asx-act.is-done .asx-act-dot { background: var(--ok); }
.asx-act.is-failed .asx-act-dot { background: var(--warn); }

/* Die Aufstellung als Paare und nicht als Fliesstext: Man liest sie nicht, man prueft
   sie - und dafuer muessen die Werte untereinander stehen. */
.asx-act-rows { display: flex; flex-direction: column; gap: 2px; margin-top: 6px; }
.asx-act-row { display: flex; gap: 8px; font-size: 11.5px; line-height: 1.45; }
.asx-act-key { flex: none; width: 88px; color: var(--text-dim); }
.asx-act-val { min-width: 0; color: var(--text); overflow-wrap: anywhere; }

.asx-act-foot { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; margin-top: 8px; }
.asx-act-note { font-size: 11px; color: var(--text-dim); }
.asx-act-result { margin-top: 7px; font-size: 12px; line-height: 1.5; color: var(--text); }




/* ---------- Die Fahrzeugauswahl als Tabelle ---------- */
/* Sie war eine Liste mit Haken - Bezeichner und RIN, mehr nicht. Das reicht, solange ein
   Bereich zwanzig Fahrzeuge hat. Bei zweihundert lautet die Frage nicht mehr "welches
   heisst so", sondern "welche der Flotte Nord sind gerade online", und die beantwortet
   nur eine Tabelle mit Spalten, Filtern und Seiten.
   Siehe Components/Notify/NotifyRuleEditor.razor. */
.nre-veh-dlg { display: flex; flex-direction: column; gap: 10px; min-width: 0; }

/* Oben die Grundsatzfrage: alles oder eine Auswahl. Sie steht ueber den Filtern, weil
   sie diese ueberstimmt - bei "alle des Bereichs" ist die Tabelle eine Anzeige. */
.nre-veh-mode { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

.nre-veh-filters {
    display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap;
    padding-top: 9px; border-top: 1px dashed var(--line);
}
.nre-veh-filters > .nre-mini { min-width: 130px; }

/* Die Tabelle rollt fuer sich und nicht der Dialog: Die Fusszeile mit den Seiten soll
   stehen bleiben, waehrend man die Zeilen durchgeht. */
.nre-veh-table {
    max-height: min(52vh, 460px); overflow: auto;
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--panel);
}
.nre-veh-table table.data { font-size: 12px; }
.nre-veh-table th, .nre-veh-table td { padding: 6px 10px; }
.nre-veh-table th { padding: 8px 10px; font-size: 9.5px; }

/* Die ganze Zeile waehlt aus - das Kaestchen ist die Anzeige dazu und nicht das einzige
   Ziel. Bei zwoelf Pixeln Kantenlaenge ist es das kleinste Ziel der Seite. */
.nre-veh-table tbody tr { cursor: pointer; }
.nre-veh-table tbody tr:hover { background: var(--panel-inset); }
.nre-veh-table tbody tr.is-picked { background: color-mix(in srgb, var(--accent) 10%, transparent); }
.nre-veh-table tbody tr.is-picked td:first-child { box-shadow: inset 3px 0 0 0 var(--accent); }

.nre-veh-pick { width: 1%; white-space: nowrap; }
.nre-veh-pick input { width: 15px; height: 15px; cursor: pointer; }

/* Die RIN ist die Kennung des Geraets und nicht der Name des Fahrzeugs: Sie steht
   deshalb da, aber gedaempft und klein. */
.nre-veh-rin { color: var(--text-faint); font-size: 10.5px; }

/* Bei "alle des Bereichs" zeigt die Tabelle den Bestand, ohne zu behaupten, jedes
   Fahrzeug sei einzeln gewaehlt: gedaempft, die Haken aus. */
.nre-veh-table.is-all { opacity: .62; }
.nre-veh-table.is-all tbody tr { cursor: default; }
.nre-veh-table.is-all input[type="checkbox"] { visibility: hidden; }

.nre-veh-pager {
    display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap;
}
.nre-veh-count { font-size: 11.5px; color: var(--text-dim); padding-bottom: 7px; }
.nre-veh-size { width: 96px; }
.nre-veh-pager > .btn { margin-bottom: 1px; }

/* Das eingebettete PDF der Vorschau. Der Browser bringt seinen Betrachter mit - samt
   Blaettern, Zoom und Suche -, und eine Hoehe in vh statt in Pixeln laesst ihn auf einem
   Telefon genauso arbeiten wie auf einem 27-Zoll-Schirm. */
.nre-pdf {
    width: 100%; height: min(72vh, 900px);
    border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
    background: var(--panel-inset);
}

/* ---------- Woran das Fahrtenbuch gerade arbeitet ---------- */
/* Das Laden tauschte die Liste gegen einen Kreisel; die Erkennung lief unsichtbar, obwohl
   sie am laengsten dauert - sie liest Wochen an Messwerten. Das Band sagt, WAS laeuft,
   und steht im Kopf des Panels, wo es nichts verdeckt (siehe Panel.razor). */
.fb-busy {
    display: flex; align-items: center; gap: 9px;
    padding: 7px 12px;
    border-bottom: 1px solid color-mix(in srgb, var(--accent) 30%, var(--line));
    background: color-mix(in srgb, var(--accent) 8%, var(--panel));
    font-size: 12px; color: var(--text);
}

/* Die vorige Liste bleibt stehen, waehrend die naechste kommt - aber gedaempft: Sie ist
   noch da und gilt nicht mehr. */
.fb-p .fb-stale { opacity: .5; pointer-events: none; }

/* ---------- Die Wahl der Berichtsart ---------- */
/* Vier Knoepfe untereinander und keine Auswahlliste: Jede Art braucht ihren erklaerenden
   Satz - "Auffaelligkeiten" und "Vollbericht" unterscheiden sich nicht im Namen, sondern
   in dem, was sie weglassen. In einer Liste stuende davon nichts. */
.ts-report { display: flex; flex-direction: column; gap: 8px; min-width: 0; }

.ts-rk {
    display: flex; align-items: flex-start; gap: 10px; text-align: left;
    padding: 9px 11px;
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--panel-raised); color: var(--text); font: inherit; cursor: pointer;
    transition: border-color .12s, background .12s;
}
.ts-rk:hover { border-color: var(--line-strong); }
.ts-rk.active {
    border-color: var(--accent); background: var(--accent-soft);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 16%, transparent);
}
.ts-rk-mark { flex: none; color: var(--accent); font-size: 12px; line-height: 1.4; }
.ts-rk-name { font-weight: 600; font-size: 13px; }
.ts-rk-note { font-size: 11.5px; line-height: 1.45; color: var(--text-dim); }

.ts-rk-focus { margin-top: 4px; }

/* Was der Bericht umfassen wird - die Gegenprobe vor dem Klick. */
.ts-rk-scope {
    padding-top: 8px; border-top: 1px dashed var(--line);
    font-size: 11.5px; color: var(--text-dim);
}

.ts-rk-busy { display: inline-flex; align-items: center; gap: 8px; font-size: 12px; color: var(--text-dim); }

/* ---------- Lizenzpakete (Benutzerverwaltung, Profil, /licenses) ----------
   Eine Zeile je Paket: links was es ist, rechts was man damit tun kann. Gebuchte Pakete
   tragen den Akzentrand, damit man im Dialog nicht die Knöpfe lesen muss, um zu sehen,
   was läuft. */
.lic-chips { display: flex; flex-wrap: wrap; gap: 4px; }

.lic-row {
    display: flex; align-items: center; justify-content: space-between; gap: 14px;
    padding: 10px 12px;
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--panel-raised);
}
.lic-row.on { border-color: var(--accent); background: var(--accent-soft); }
.lic-name { font-weight: 600; font-size: 13.5px; }
.lic-term { font-size: 11.5px; color: var(--text-dim); margin-top: 2px; }
.lic-term b { color: var(--warn); font-weight: 600; }
.lic-actions { display: flex; align-items: center; justify-content: flex-end; gap: 6px; flex-wrap: wrap; flex: none; max-width: 55%; }
.lic-confirm { font-size: 12px; color: var(--text); text-align: right; max-width: 260px; }

.lic-mine { display: flex; flex-direction: column; gap: 2px; padding: 9px 0; border-top: 1px solid var(--line); }

.lic-cat { display: flex; flex-direction: column; gap: 10px; }
.lic-card {
    display: flex; flex-direction: column; gap: 6px;
    padding: 12px 14px;
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--panel-raised);
}
.lic-card.on { border-color: var(--accent); }
.lic-card p { margin: 0; font-size: 12.5px; line-height: 1.5; color: var(--text-dim); }
.lic-feats { display: flex; flex-wrap: wrap; gap: 4px 12px; font-size: 12px; }
.lic-feats span::before { content: "✓ "; color: var(--ok); }
.lic-price { font-size: 11.5px; color: var(--text); }
.prof-rows .lic-price { text-align: right; max-width: 60%; }

.lic-head { flex-wrap: wrap; row-gap: 10px; }
.lic-month { gap: 6px; }
.lic-month .input { width: 150px; }
.lic-kpis { margin-bottom: 16px; }
.lic-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }

.lic-bars { display: flex; flex-direction: column; gap: 10px; }
.lic-bar-row { display: grid; grid-template-columns: minmax(140px, 1.2fr) 2fr auto auto; align-items: center; gap: 10px; font-size: 12.5px; }
.lic-bar-row:first-child { grid-template-columns: minmax(140px, 1.2fr) auto; justify-content: space-between; padding-bottom: 10px; border-bottom: 1px solid var(--line); }
.lic-bar-track { height: 8px; border-radius: 4px; background: var(--panel-inset); overflow: hidden; }
.lic-bar-fill { display: block; height: 100%; border-radius: 4px; background: var(--accent); }
.lic-bar-n { text-align: right; min-width: 80px; }
.lic-bar-u { font-size: 11px; min-width: 70px; text-align: right; }

.lic-table tr.lic-dept { cursor: pointer; }
.lic-table tr.lic-dept.open td { background: var(--panel-raised); }
.lic-caret { display: inline-block; width: 14px; color: var(--text-faint); }
.lic-table tr.lic-users > td { padding: 0 14px 14px 32px; background: var(--panel-raised); }
.lic-table tr.lic-users:hover { background: transparent; }
table.data.lic-sub { font-size: 12.5px; border: 1px solid var(--line); border-radius: var(--radius-sm); }
table.data.lic-sub th { position: static; background: transparent; padding: 7px 12px; }
table.data.lic-sub td { padding: 7px 12px; }
.lic-table tfoot td { border-top: 1px solid var(--line-strong); padding: 11px 14px; background: var(--panel-raised); }

@media (max-width: 900px) {
    .lic-grid { grid-template-columns: 1fr; }
    .lic-row { flex-direction: column; align-items: stretch; }
    .lic-actions { max-width: none; justify-content: flex-start; }
    .lic-confirm { text-align: left; }
    .lic-bar-row { grid-template-columns: 1fr auto; }
    .lic-bar-track, .lic-bar-u { display: none; }
}

/* ---------- Benutzerverwaltung: Filterleiste und Sortierung ---------- */
.usr-filters { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; width: 100%; }
.usr-filters .usr-search { flex: 1 1 240px; max-width: 320px; }
.usr-filters .select { width: auto; min-width: 140px; flex: 0 1 auto; }
.usr-filters .usr-count { margin-left: auto; }
.usr-table th.usr-sort { cursor: pointer; user-select: none; }
.usr-table th.usr-sort:hover { color: var(--text); }
.usr-table th.usr-sort::after { content: " ⇅"; opacity: 0; font-size: 10px; }
.usr-table th.usr-sort:hover::after { opacity: 0.45; }
.usr-table th.usr-sort.sorted { color: var(--accent); }
.usr-table th.usr-sort.sorted.asc::after { content: " ▲"; opacity: 1; font-size: 9px; }
.usr-table th.usr-sort.sorted.desc::after { content: " ▼"; opacity: 1; font-size: 9px; }
@media (max-width: 700px) {
    .usr-filters .usr-search, .usr-filters .select { flex: 1 1 100%; max-width: none; }
    .usr-filters .usr-count { margin-left: 0; }
}

/* ---------- Profil · Pakete: Preisseite ----------
   Nach dem Muster einer SaaS-Preisseite: Kopfzone mit Aussage und Stand, darunter die
   Pakete als Karten mit großem Preis, Knopf und Hakenliste, das Full-Paket hervorgehoben.
   Die Karten sind gleich hoch; Preis und Knopf stehen auf einer Linie, die Hakenlisten
   darunter dürfen verschieden lang sein. */
.pp { display: flex; flex-direction: column; gap: 22px; padding: 2px 2px 10px; }

.pp-hero {
    display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 18px 28px;
    padding: 24px 28px;
    border: 1px solid var(--line); border-radius: 16px;
    background:
        radial-gradient(120% 140% at 0% 0%, var(--accent-soft) 0%, transparent 55%),
        radial-gradient(90% 120% at 100% 100%, color-mix(in srgb, var(--s-violet) 10%, transparent) 0%, transparent 60%),
        var(--panel);
}
.pp-kicker {
    font-family: var(--font-mono); font-size: 10.5px; font-weight: 600; letter-spacing: 0.16em;
    text-transform: uppercase; color: var(--accent);
}
.pp-hero h2 { margin: 6px 0 6px; font-family: var(--font-ui); font-size: 25px; font-weight: 700; line-height: 1.2; letter-spacing: -0.01em; }
.pp-hero p { margin: 0; max-width: 62ch; font-size: 13.5px; line-height: 1.55; color: var(--text-dim); }
.pp-hero-side { display: flex; gap: 10px; flex-wrap: wrap; }
.pp-scope {
    display: flex; flex-direction: column; gap: 3px; min-width: 110px;
    padding: 10px 14px; border: 1px solid var(--line); border-radius: 10px;
    background: color-mix(in srgb, var(--panel-inset) 80%, transparent);
}
.pp-scope-k { font-family: var(--font-mono); font-size: 9.5px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-faint); }
.pp-scope-v { font-family: var(--font-ui); font-size: 14px; font-weight: 600; white-space: nowrap; }

.pp-grid {
    display: grid; gap: 16px; align-items: stretch;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    padding-top: 12px; /* Platz für die Bänder über den Karten */
}

/* Die Zeilen der Karten laufen über ein gemeinsames Raster (subgrid): Bricht ein Name
   oder eine Unterzeile in einer Karte um, rücken Preis und Knopf in allen Karten der Reihe
   mit – sie stehen immer auf einer Linie. Zehn Zeilen, eine je Baustein der Karte; das
   Band über der Karte ist absolut gesetzt und belegt keine. */
@supports (grid-template-rows: subgrid) {
    .pp-grid .pp-card { display: grid; grid-template-rows: subgrid; grid-row: span 10; row-gap: 0; align-content: start; }
    .pp-grid .pp-tagline, .pp-grid .pp-price-note { min-height: 0; }
    .pp-grid .pp-price { align-self: end; }
}

/* Das Basis-Paket als breites Band über den Modulen: links wer es ist, in der Mitte was
   drin ist (mehrspaltig), rechts der Preis. */
.pp-base {
    position: relative; display: grid; align-items: center; gap: 18px 28px;
    grid-template-columns: minmax(220px, 0.9fr) minmax(0, 2fr) auto;
    margin-top: 10px; padding: 22px 24px;
    border: 1px solid color-mix(in srgb, var(--ok) 55%, var(--line)); border-radius: 16px;
    background: linear-gradient(90deg, color-mix(in srgb, var(--ok) 7%, var(--panel)) 0%, var(--panel) 60%);
}
.pp-base .pp-ribbon { left: 24px; transform: none; }
.pp-base-id { display: flex; align-items: center; gap: 14px; min-width: 0; }
.pp-base-id .pp-icon { margin-bottom: 0; background: color-mix(in srgb, var(--ok) 14%, transparent); color: var(--ok); }
.pp-base-id .pp-tagline { min-height: 0; }
.pp-feats.pp-feats--cols { margin: 0; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 7px 16px; }
.pp-base-price { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; padding-left: 24px; border-left: 1px solid var(--line); }
.pp-base-price .pp-price { margin-top: 0; }
.pp-base-price .pp-price-note { min-height: 0; max-width: 260px; }
.pp-included {
    display: inline-flex; align-items: center; gap: 7px; margin-top: 8px;
    padding: 5px 11px; border-radius: 20px; font-size: 11.5px; font-weight: 600;
    background: color-mix(in srgb, var(--ok) 12%, transparent); color: var(--ok);
}

.pp-card {
    position: relative; display: flex; flex-direction: column; min-width: 0;
    padding: 24px 18px 18px;
    border: 1px solid var(--line); border-radius: 16px;
    background: var(--panel);
    transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}
.pp-card:hover { transform: translateY(-3px); border-color: var(--line-strong); box-shadow: var(--shadow); }
.pp-card.is-on { border-color: color-mix(in srgb, var(--ok) 60%, var(--line)); }
.pp-card.is-ask { border-color: color-mix(in srgb, var(--info) 60%, var(--line)); }

.pp-card--featured {
    border: 2px solid var(--accent);
    background: linear-gradient(180deg, var(--accent-soft) 0%, var(--panel) 42%);
    box-shadow: 0 24px 48px -28px var(--accent-glow);
}
.pp-card--featured:hover { box-shadow: 0 28px 56px -26px var(--accent-glow); }
.pp-card--featured.is-on { border-color: var(--ok); }

.pp-ribbon {
    position: absolute; top: -11px; left: 50%; transform: translateX(-50%);
    padding: 4px 12px; border-radius: 20px; white-space: nowrap;
    background: var(--accent); color: var(--on-accent);
    font-family: var(--font-mono); font-size: 9.5px; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase;
    box-shadow: 0 6px 16px -8px var(--accent-glow);
}
.pp-ribbon--ok { background: var(--ok); color: #05281A; box-shadow: none; }

.pp-icon {
    width: 42px; height: 42px; border-radius: 12px; display: grid; place-items: center; flex: none;
    background: var(--accent-soft); color: var(--accent); margin-bottom: 14px;
}
.pp-icon svg { width: 22px; height: 22px; }
.pp-card--base .pp-icon { background: color-mix(in srgb, var(--ok) 14%, transparent); color: var(--ok); }
.pp-card--featured .pp-icon { background: var(--accent); color: var(--on-accent); }

.pp-name { font-family: var(--font-ui); font-size: 17px; font-weight: 700; line-height: 1.25; }
.pp-tagline { margin-top: 4px; min-height: 2.9em; font-size: 12.5px; line-height: 1.45; color: var(--text-dim); }

.pp-price { display: flex; align-items: flex-end; gap: 5px; margin-top: 14px; }
.pp-amount { font-family: var(--font-ui); font-size: 42px; font-weight: 700; line-height: 0.95; letter-spacing: -0.03em; }
.pp-cur { font-family: var(--font-ui); font-size: 20px; font-weight: 600; align-self: flex-start; margin-top: 2px; }
.pp-unit { font-size: 11px; line-height: 1.3; color: var(--text-dim); padding-bottom: 2px; }
.pp-card--featured .pp-amount, .pp-card--featured .pp-cur { color: var(--accent); }
.pp-price-note { margin-top: 6px; min-height: 2.8em; font-size: 11.5px; line-height: 1.4; color: var(--text-dim); }

.pp-cta {
    width: 100%; height: 40px; margin-top: 14px; justify-content: center;
    font-weight: 600; font-size: 13px; border-radius: 10px; gap: 8px;
}
.pp-cta--hero {
    border: none; color: var(--on-accent);
    background: linear-gradient(135deg, var(--accent) 0%, var(--brand-400) 100%);
    box-shadow: 0 10px 22px -12px var(--accent-glow);
}
.pp-cta--hero:hover:not(:disabled) { filter: brightness(1.08); }
.pp-cta:disabled { cursor: default; opacity: 1; }
.pp-cta--quiet { background: var(--panel-raised); color: var(--text-dim); border-color: var(--line); }
.pp-cta--on { background: color-mix(in srgb, var(--ok) 12%, transparent); color: var(--ok); border-color: color-mix(in srgb, var(--ok) 45%, transparent); }
.pp-cta--ask { background: color-mix(in srgb, var(--info) 12%, transparent); color: var(--info); border-color: color-mix(in srgb, var(--info) 45%, transparent); }
.pp-dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; flex: none; }
.pp-cta--ask .pp-dot { animation: chip-beat 1.6s ease-in-out infinite; }

.pp-status { min-height: 30px; margin-top: 7px; font-size: 11px; line-height: 1.4; color: var(--text-dim); text-align: center; }
.pp-status--warn { color: var(--warn); }
.pp-link { border: 0; background: none; padding: 0; font: inherit; color: var(--accent); cursor: pointer; text-decoration: underline; text-underline-offset: 2px; }

.pp-feat-head {
    margin-top: 6px; padding-top: 14px; border-top: 1px solid var(--line);
    font-family: var(--font-mono); font-size: 9.5px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--text-faint);
}
.pp-feats { list-style: none; margin: 10px 0 0; padding: 0; display: flex; flex-direction: column; gap: 7px; }
.pp-feats li { position: relative; padding-left: 24px; font-size: 12.5px; line-height: 1.4; }
.pp-feats li::before {
    content: "✓"; position: absolute; left: 0; top: 1px;
    width: 16px; height: 16px; border-radius: 50%; display: grid; place-items: center;
    font-size: 9.5px; font-weight: 700;
    background: color-mix(in srgb, var(--ok) 16%, transparent); color: var(--ok);
}
.pp-feats li.pp-feat-plus { color: var(--text-dim); }
.pp-feats li.pp-feat-plus::before { content: "+"; background: var(--accent-soft); color: var(--accent); }
.pp-desc { margin: 14px 0 0; padding-top: 12px; border-top: 1px dashed var(--line); font-size: 11.5px; line-height: 1.5; color: var(--text-faint); }

.pp-terms {
    display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px 22px;
    padding: 16px 22px; border: 1px solid var(--line); border-radius: 14px; background: var(--panel);
}
.pp-term { display: flex; align-items: flex-start; gap: 11px; font-size: 12px; line-height: 1.45; color: var(--text-dim); }
.pp-term b { color: var(--text); font-weight: 600; }
.pp-term svg { width: 20px; height: 20px; flex: none; color: var(--accent); margin-top: 1px; }
.pp-foot-note { margin: 0; text-align: center; font-size: 12px; color: var(--text-dim); }

@media (max-width: 1400px) {
    .pp-base { grid-template-columns: minmax(0, 1fr) auto; }
    .pp-base .pp-feats--cols { grid-column: 1 / -1; grid-row: 2; grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@media (max-width: 1000px) {
    .pp-hero { grid-template-columns: 1fr; }
    .pp-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: 26px; }
    .pp-terms { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .pp-base .pp-feats--cols { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
    .pp-hero { padding: 18px; }
    .pp-hero h2 { font-size: 21px; }
    .pp-grid, .pp-terms { grid-template-columns: 1fr; }
    .pp-base { grid-template-columns: 1fr; padding: 22px 18px 18px; }
    .pp-base-price { padding-left: 0; border-left: 0; padding-top: 14px; border-top: 1px solid var(--line); }
    .pp-card:hover { transform: none; }
}

/* Der Bestelldialog aus dem Profil. */
.pk-order-head { display: flex; flex-direction: column; gap: 2px; }
.pk-name { font-family: var(--font-ui); font-weight: 600; font-size: 15px; line-height: 1.3; }
.pk-tag { font-size: 12px; color: var(--text-dim); }
.pk-accept { display: flex; align-items: flex-start; gap: 9px; font-size: 12.5px; line-height: 1.5; cursor: pointer; }
.pk-accept input { margin-top: 3px; flex: none; }

/* ---------- Buchungsanfragen (Benutzerverwaltung, Lizenzen) ---------- */
.chip.lic-chip-ask { color: var(--info); border-color: var(--info); background: color-mix(in srgb, var(--info) 12%, transparent); }
.lic-row.ask { border-color: var(--info); background: color-mix(in srgb, var(--info) 8%, var(--panel-raised)); }
.lic-ask { font-size: 11.5px; color: var(--info); margin-top: 2px; }
.lic-reason { width: 240px; max-width: 100%; height: 30px; font-size: 12px; }

.lic-banner {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    padding: 9px 14px; margin-bottom: 12px;
    border: 1px solid var(--info); border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--info) 10%, var(--panel));
    font-size: 13px;
}
.lic-banner > button { margin-left: auto; }
.lic-banner-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--info); flex: none; animation: chip-beat 1.6s ease-in-out infinite; }

/* ---------- Lizenzen: Basis-Modell ---------- */
button.chip.lic-model { cursor: pointer; font: inherit; font-family: var(--font-mono); font-size: 11px; }
button.chip.lic-model:hover { border-color: var(--accent); color: var(--text); }
.lic-opt {
    display: flex; align-items: flex-start; gap: 10px; padding: 11px 13px; cursor: pointer;
    border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel-raised);
}
.lic-opt.on { border-color: var(--accent); background: var(--accent-soft); }
.lic-opt input { margin-top: 3px; }

/* =====================================================================
   Was ist neu – Dialog nach dem Anmelden (WhatsNewDialog.razor)

   Er soll einladen, nicht nur berichten: oben der Sprung von der alten auf
   die neue Version, mit einem Funkenregen beim Öffnen; darunter die neuen
   Funktionen als Karten mit dem Zeichen ihrer Seite und einem Knopf
   „Ausprobieren“. Die Farben sind die der Release Notes (.rn-kind): grün für
   neu, Akzent für verbessert, Warnfarbe für behoben.

   Bewegung nur als Auftakt und auf Berührung – was dauerhaft läuft (Ringe um
   das Zeichen, der Fluss im Versionspfeil), ist leise. Mit „weniger
   Bewegung“ im System steht alles still, in den Kontrastthemes fallen
   Funken und Lichtstreif weg.
   ===================================================================== */

.wn { display: flex; flex-direction: column; gap: 18px; }

.wn-sr {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip: rect(0 0 0 0); white-space: nowrap;
}

/* ---------- Kopf: von → auf ---------- */
.wn-hero {
    position: relative; overflow: hidden; isolation: isolate;
    display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 20px; align-items: center;
    padding: 20px 22px; border-radius: var(--radius);
    border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--line));
    background:
        radial-gradient(90% 130% at 0% 0%, color-mix(in srgb, var(--accent) 24%, transparent), transparent 62%),
        radial-gradient(70% 120% at 100% 100%, color-mix(in srgb, var(--ok) 14%, transparent), transparent 60%),
        var(--panel-raised);
    animation: wn-in .35s ease-out both;
}
/* Ein Lichtstreif, der einmal über den Kopf zieht. */
.wn-hero::after {
    content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
    background: linear-gradient(105deg, transparent 35%, color-mix(in srgb, #fff 12%, transparent) 50%, transparent 65%);
    transform: translateX(-100%);
    animation: wn-sheen 1.6s .45s ease-out both;
}

.wn-hero-ico {
    position: relative; width: 60px; height: 60px; border-radius: 18px;
    display: grid; place-items: center;
    background: linear-gradient(135deg, var(--accent), color-mix(in srgb, var(--accent) 55%, var(--ok)));
    color: var(--on-accent);
    box-shadow: 0 14px 34px -12px var(--accent-glow), inset 0 1px 0 color-mix(in srgb, #fff 35%, transparent);
    animation: wn-pop .55s cubic-bezier(.2, .9, .3, 1.4) both, wn-float 3.4s 1.2s ease-in-out infinite;
}
.wn-hero-ico > svg {
    position: relative; z-index: 1; width: 30px; height: 30px;
    animation: wn-launch .9s .25s cubic-bezier(.2, .8, .2, 1) both;
}
.wn-hero-ring {
    position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
    border: 2px solid var(--accent); opacity: 0;
    animation: wn-ring 2.8s .7s ease-out infinite;
}
.wn-hero-ring-2 { animation-delay: 2.1s; }

/* Die Funken fliegen aus der Mitte des Zeichens. */
.wn-burst { position: absolute; left: 50%; top: 50%; width: 0; height: 0; pointer-events: none; }
.wn-burst i {
    position: absolute; left: -3px; top: -3px; width: 6px; height: 6px; border-radius: 50%;
    background: var(--accent); opacity: 0;
    animation: wn-spark .95s var(--t, 150ms) cubic-bezier(.15, .7, .3, 1) both;
}
.wn-burst i:nth-child(3n + 2) { background: var(--ok); }
.wn-burst i:nth-child(3n)     { background: var(--s-amber); }
/* Jeder zweite als Strich in Flugrichtung – ein Regen nur aus Punkten sieht nach Konfetti aus. */
.wn-burst i:nth-child(even)   { width: 11px; height: 3px; top: -1.5px; left: -5.5px; border-radius: 2px; }

.wn-hero-body { min-width: 0; }

.wn-upgrade { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 11px; }
.wn-ver {
    display: inline-flex; flex-direction: column; gap: 2px; line-height: 1.1;
    padding: 5px 12px 6px; border-radius: 10px;
    border: 1px solid var(--line); background: var(--panel);
    animation: wn-in .35s .2s ease-out both;
}
.wn-ver small {
    font-family: var(--font-mono); font-size: 9px; font-weight: 700;
    letter-spacing: .1em; text-transform: uppercase; color: var(--text-faint);
}
.wn-ver b { font-family: var(--font-mono); font-size: 14px; font-weight: 700; color: var(--text-dim); }
.wn-ver-to {
    border-color: var(--accent); background: var(--accent-soft);
    animation: wn-pop .5s .55s cubic-bezier(.2, .9, .3, 1.4) both, wn-glow 2.8s 1.4s ease-in-out infinite;
}
.wn-ver-to small { color: var(--accent); }
.wn-ver-to b { color: var(--text); font-size: 17px; }

/* Der Pfeil dazwischen: drei Lichtpunkte, die von alt nach neu wandern. */
.wn-flow { display: inline-flex; flex-direction: column; align-items: center; gap: 5px; }
.wn-flow-track {
    position: relative; display: block; width: 64px; height: 2px; border-radius: 2px;
    background: color-mix(in srgb, var(--accent) 28%, transparent);
}
.wn-flow-track::after {
    content: ""; position: absolute; right: 0; top: 50%; width: 7px; height: 7px;
    border-top: 2px solid var(--accent); border-right: 2px solid var(--accent);
    transform: translateY(-50%) rotate(45deg);
}
.wn-flow-track i {
    position: absolute; top: -2px; left: 0; width: 6px; height: 6px; border-radius: 50%;
    background: var(--accent); box-shadow: 0 0 8px var(--accent-glow); opacity: 0;
    animation: wn-travel 1.8s linear infinite;
}
.wn-flow-track i:nth-child(2) { animation-delay: .6s; }
.wn-flow-track i:nth-child(3) { animation-delay: 1.2s; }
.wn-flow-hops { font-family: var(--font-mono); font-size: 9.5px; color: var(--text-faint); white-space: nowrap; }

.wn-hero-lead { font-family: var(--font-ui); font-size: 17px; font-weight: 600; color: var(--text); line-height: 1.3; }
.wn-hero-sub { font-size: 12.5px; color: var(--text-dim); margin-top: 4px; line-height: 1.4; }
.wn-hero-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 12px; }

.wn-pill {
    display: inline-flex; align-items: center; gap: 5px;
    font-family: var(--font-mono); font-size: 10.5px; font-weight: 700;
    padding: 3px 10px 3px 8px; border-radius: 20px; white-space: nowrap;
}
.wn-pill svg { width: 12px; height: 12px; }
.wn-pill.wn-k-neu       { background: color-mix(in srgb, var(--ok) 18%, transparent);   color: var(--ok); }
.wn-pill.wn-k-aenderung { background: var(--accent-soft);                                color: var(--accent); }
.wn-pill.wn-k-fix       { background: color-mix(in srgb, var(--warn) 18%, transparent); color: var(--warn); }

/* Wie viel davon schon ausprobiert ist – ein kleiner Balken vor der Zahl. */
.wn-progress {
    display: inline-flex; align-items: center; gap: 7px;
    font-family: var(--font-mono); font-size: 10.5px; font-weight: 700; white-space: nowrap;
    padding: 3px 10px; border-radius: 20px; border: 1px solid var(--line); color: var(--text-dim);
}
.wn-progress-bar {
    width: 34px; height: 5px; border-radius: 3px; overflow: hidden; flex: none;
    background: color-mix(in srgb, var(--ok) 20%, transparent);
}
.wn-progress-bar i {
    display: block; height: 100%; border-radius: inherit; background: var(--ok);
    transition: width .5s cubic-bezier(.2, .8, .2, 1);
}
.wn-progress.done { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 50%, transparent); }

/* ---------- Abschnitte ---------- */
.wn-sec { --wn-c: var(--accent); }
.wn-sec.wn-k-neu { --wn-c: var(--ok); }
.wn-sec.wn-k-fix { --wn-c: var(--warn); }

.wn-sec-head {
    display: flex; align-items: center; gap: 8px; margin-bottom: 10px;
    font-family: var(--font-mono); font-size: 10.5px; font-weight: 700;
    letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-dim);
}
.wn-sec-ico {
    width: 22px; height: 22px; border-radius: 7px; flex: none;
    display: grid; place-items: center;
    color: var(--wn-c); background: color-mix(in srgb, var(--wn-c) 16%, transparent);
}
.wn-sec-ico svg { width: 13px; height: 13px; }
.wn-count { color: var(--text-faint); font-weight: 600; }

/* Die eingeklappten Fixes: eine Zeile, die sagt, wie viele es sind. */
.wn-sec-toggle {
    width: 100%; padding: 7px 10px; margin-bottom: 0; cursor: pointer; text-align: left;
    background: transparent; border: 1px dashed var(--line-strong); border-radius: var(--radius-sm);
    transition: background .12s, border-color .12s, color .12s;
}
.wn-sec-toggle:hover { background: var(--panel-raised); color: var(--text); border-color: var(--line-strong); }
.wn-sec-toggle[aria-expanded="true"] { margin-bottom: 10px; }
.wn-caret { margin-left: auto; font-size: 10px; transition: transform .15s; }
.wn-caret.open { transform: rotate(90deg); }

.wn-sec-more {
    margin-top: 8px; padding: 4px 2px; background: none; border: 0; cursor: pointer;
    font-family: var(--font-ui); font-size: 12px; font-weight: 600; color: var(--accent);
}
.wn-sec-more:hover { text-decoration: underline; }

/* ---------- Neue Funktionen als Karten ---------- */
.wn-cards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.wn-card {
    position: relative; overflow: hidden; isolation: isolate;
    display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 12px; align-items: start;
    padding: 13px 14px; border-radius: var(--radius-sm);
    border: 1px solid var(--line);
    background: linear-gradient(160deg, color-mix(in srgb, var(--wn-c) 8%, var(--panel-raised)), var(--panel-raised) 55%);
    color: var(--text); font-size: 12.5px; line-height: 1.45;
    animation: wn-rise .45s cubic-bezier(.2, .8, .2, 1) both;
    animation-delay: calc(250ms + var(--i, 0) * 70ms);
    transition: border-color .15s, transform .15s, box-shadow .15s;
}
/* Ein Glanz, der beim Darüberfahren einmal durch die Karte läuft. */
.wn-card::before {
    content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
    background: linear-gradient(115deg, transparent 30%, color-mix(in srgb, var(--wn-c) 16%, transparent) 50%, transparent 70%);
    transform: translateX(-110%);
    transition: transform .7s ease;
}
.wn-card:hover {
    border-color: color-mix(in srgb, var(--wn-c) 55%, var(--line));
    transform: translateY(-2px);
    box-shadow: 0 14px 28px -18px color-mix(in srgb, var(--wn-c) 80%, transparent);
}
.wn-card:hover::before { transform: translateX(110%); }
.wn-card.can-open { cursor: pointer; }
.wn-card.can-open:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Aufgeklappt über die volle Breite: der ganze Text in einer halben Spalte wäre ein Turm. */
.wn-card.open { grid-column: 1 / -1; }

.wn-card-ico {
    width: 38px; height: 38px; border-radius: 11px; flex: none;
    display: grid; place-items: center;
    color: var(--wn-c);
    background: color-mix(in srgb, var(--wn-c) 16%, transparent);
    border: 1px solid color-mix(in srgb, var(--wn-c) 30%, transparent);
    transition: transform .3s cubic-bezier(.2, .9, .3, 1.5);
}
.wn-card-ico svg { width: 20px; height: 20px; }
.wn-card:hover .wn-card-ico { transform: scale(1.1) rotate(-6deg); }

.wn-card-main { display: flex; flex-direction: column; justify-content: space-between; gap: 10px; min-width: 0; height: 100%; }
.wn-card-foot {
    display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
    font-family: var(--font-mono); font-size: 10px; color: var(--text-faint);
}
.wn-card-more { color: var(--accent); font-weight: 700; }

/* Der Aufmacher: bei ungerader Zahl die erste Karte über die volle Breite. */
.wn-card.spot {
    grid-column: 1 / -1; padding: 16px 18px; gap: 16px;
    border-color: color-mix(in srgb, var(--wn-c) 45%, var(--line));
    background:
        radial-gradient(80% 140% at 0% 0%, color-mix(in srgb, var(--wn-c) 18%, transparent), transparent 60%),
        var(--panel-raised);
}
.wn-card.spot .wn-card-ico {
    width: 48px; height: 48px; border-radius: 14px;
    box-shadow: 0 10px 24px -12px var(--wn-c);
}
.wn-card.spot .wn-card-ico svg { width: 24px; height: 24px; }
.wn-card.spot .wn-card-text { font-size: 13.5px; font-weight: 500; }

.wn-card.tried { border-color: color-mix(in srgb, var(--ok) 40%, var(--line)); }

/* ---------- „Ausprobieren“ ---------- */
.wn-try {
    margin-left: auto; display: inline-flex; align-items: center; gap: 6px;
    padding: 5px 11px; border-radius: 20px; white-space: nowrap;
    font-family: var(--font-ui); font-size: 11.5px; font-weight: 700; letter-spacing: .01em;
    color: var(--on-accent); background: var(--accent);
    box-shadow: 0 6px 16px -8px var(--accent-glow);
    transition: transform .15s, box-shadow .15s, background .15s;
}
.wn-try:hover {
    color: var(--on-accent); background: var(--accent-hover);
    transform: translateY(-1px); box-shadow: 0 10px 22px -8px var(--accent-glow);
}
.wn-try:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.wn-try-page {
    font-weight: 500; opacity: .85; padding-left: 6px;
    border-left: 1px solid color-mix(in srgb, var(--on-accent) 35%, transparent);
}
.wn-try-ico svg { width: 13px; height: 13px; display: block; }
.wn-card:hover .wn-try-arrow,
.wn-list li:hover .wn-try-arrow,
.wn-try:hover .wn-try-arrow { animation: wn-nudge .7s ease-in-out infinite; }

.wn-try.compact {
    flex: none; align-self: center; padding: 2px 9px; font-size: 10.5px; box-shadow: none;
    color: var(--accent); background: transparent;
    border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
}
.wn-try.compact:hover { color: var(--accent); background: var(--accent-soft); box-shadow: none; }
.wn-try.tried { color: var(--ok); background: color-mix(in srgb, var(--ok) 16%, transparent); box-shadow: none; border-color: transparent; }
.wn-try.tried:hover { color: var(--ok); background: color-mix(in srgb, var(--ok) 26%, transparent); box-shadow: none; }

/* ---------- Verbesserungen und Fixes als Liste ---------- */
.wn-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.wn-list li {
    display: flex; align-items: flex-start; gap: 10px; padding: 7px 4px;
    font-size: 12.5px; line-height: 1.45; color: var(--text-dim);
    border-bottom: 1px dashed var(--line);
    animation: wn-in .3s .35s ease-out both;
    transition: color .12s;
}
.wn-list li:last-child { border-bottom: 0; }
.wn-list li.can-open { cursor: pointer; }
.wn-list li:hover { color: var(--text); }
.wn-list-ico {
    width: 22px; height: 22px; border-radius: 7px; flex: none; margin-top: -1px;
    display: grid; place-items: center;
    color: var(--wn-c); background: color-mix(in srgb, var(--wn-c) 13%, transparent);
    transition: transform .25s cubic-bezier(.2, .9, .3, 1.5);
}
.wn-list-ico svg { width: 13px; height: 13px; }
.wn-list li:hover .wn-list-ico { transform: scale(1.12); }
.wn-list-text { flex: 1; min-width: 0; }
.wn-list-ver { font-family: var(--font-mono); font-size: 10px; color: var(--text-faint); flex: none; align-self: center; }

/* ---------- Hinweis und Fußzeile ---------- */
.wn-note {
    display: flex; align-items: center; gap: 9px;
    padding: 9px 12px; border-radius: var(--radius-sm);
    border: 1px dashed var(--line); background: var(--panel-inset);
    font-size: 11.5px; line-height: 1.4; color: var(--text-faint);
}
.wn-note svg { width: 15px; height: 15px; flex: none; color: var(--accent); }

.wn-foot-left { margin-right: auto; }
.wn-foot-btn svg { width: 15px; height: 15px; flex: none; }
.modal-foot:has(.wn-foot-left) { flex-wrap: wrap; }

/* ---------- Die Leiste nach einem Link ---------- */
.wn-dock {
    position: fixed; left: 50%; bottom: 20px; z-index: 85;
    display: flex; align-items: center; gap: 4px; max-width: calc(100vw - 32px);
    padding: 5px 5px 5px 6px; border-radius: 999px;
    border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--line));
    background: color-mix(in srgb, var(--panel) 90%, transparent); backdrop-filter: blur(8px);
    box-shadow: 0 16px 36px -14px var(--accent-glow);
    transform: translateX(-50%);
    animation: wn-dock-in .5s cubic-bezier(.2, .9, .3, 1.3) both;
}
.wn-dock-open {
    display: flex; align-items: center; gap: 10px; min-width: 0;
    padding: 2px 12px 2px 2px; border: 0; background: none; cursor: pointer;
    color: var(--text); font: inherit; text-align: left;
}
.wn-dock-ico {
    width: 34px; height: 34px; border-radius: 50%; flex: none;
    display: grid; place-items: center;
    background: var(--accent); color: var(--on-accent);
    animation: wn-beat 2.2s 1s ease-out infinite;
}
.wn-dock-ico svg { width: 18px; height: 18px; }
.wn-dock-open:hover .wn-dock-ico svg { animation: wn-wiggle .5s ease-in-out; }
.wn-dock-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
.wn-dock-text b { font-family: var(--font-ui); font-size: 12.5px; font-weight: 600; white-space: nowrap; }
.wn-dock-text small { font-size: 11px; color: var(--text-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wn-dock-x {
    width: 28px; height: 28px; border-radius: 50%; flex: none;
    display: grid; place-items: center;
    border: 0; background: transparent; color: var(--text-faint); cursor: pointer;
}
.wn-dock-x:hover { background: var(--panel-raised); color: var(--text); }
.wn-dock-x svg { width: 14px; height: 14px; }

/* ---------- Bewegung ---------- */
@keyframes wn-in     { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@keyframes wn-rise   { from { opacity: 0; transform: translateY(14px) scale(.97); } to { opacity: 1; transform: none; } }
@keyframes wn-pop    { from { opacity: 0; transform: scale(.6) rotate(-12deg); } to { opacity: 1; transform: none; } }
@keyframes wn-float  { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-3px); } }
@keyframes wn-launch {
    0%   { opacity: 0; transform: translate(-9px, 9px) scale(.7); }
    60%  { opacity: 1; transform: translate(2px, -2px) scale(1.05); }
    100% { opacity: 1; transform: none; }
}
@keyframes wn-ring   { 0% { opacity: .55; transform: scale(1); } 100% { opacity: 0; transform: scale(1.6); } }
@keyframes wn-spark  {
    0%   { opacity: 0; transform: rotate(var(--a)) translateX(4px) scale(.4); }
    15%  { opacity: 1; }
    100% { opacity: 0; transform: rotate(var(--a)) translateX(var(--d)) scale(1); }
}
@keyframes wn-sheen  { from { transform: translateX(-100%); } to { transform: translateX(100%); } }
@keyframes wn-glow   {
    0%, 100% { box-shadow: 0 0 0 0 transparent; }
    50%      { box-shadow: 0 0 0 4px var(--accent-soft), 0 0 20px -4px var(--accent-glow); }
}
@keyframes wn-travel {
    0%   { left: 0; opacity: 0; }
    15%  { opacity: 1; }
    80%  { opacity: 1; }
    100% { left: calc(100% - 8px); opacity: 0; }
}
@keyframes wn-nudge  { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(3px); } }
@keyframes wn-beat   { 0% { box-shadow: 0 0 0 0 var(--accent-glow); } 70%, 100% { box-shadow: 0 0 0 10px transparent; } }
@keyframes wn-wiggle { 0%, 100% { transform: rotate(0); } 25% { transform: rotate(-14deg); } 75% { transform: rotate(14deg); } }
@keyframes wn-dock-in {
    from { opacity: 0; transform: translate(-50%, 24px) scale(.94); }
    to   { opacity: 1; transform: translate(-50%, 0); }
}

@media (prefers-reduced-motion: reduce) {
    .wn *, .wn *::before, .wn *::after, .wn-hero, .wn-dock, .wn-dock * {
        animation: none !important; transition: none !important;
    }
    .wn-burst, .wn-hero-ring, .wn-flow-track i { display: none; }
    .wn-card:hover, .wn-try:hover { transform: none; }
}

/* In den Kontrastthemes zählt die Lesbarkeit – kein Funkenregen, kein Lichtstreif. */
:root[data-theme^="contrast"] .wn-burst,
:root[data-theme^="contrast"] .wn-hero::after,
:root[data-theme^="contrast"] .wn-card::before { display: none; }

@media (max-width: 640px) {
    .wn-cards { grid-template-columns: 1fr; }
    .wn-hero { grid-template-columns: 1fr; gap: 14px; padding: 16px; }
    .wn-hero-ico { width: 50px; height: 50px; border-radius: 15px; }
    .wn-hero-ico > svg { width: 26px; height: 26px; }
    .wn-hero-lead { font-size: 15.5px; }
    .wn-card-foot .wn-try { margin-left: 0; }
    /* Version und Link unter den Text – daneben bliebe ihm eine Handbreit. */
    .wn-list li { flex-wrap: wrap; row-gap: 4px; }
    .wn-list-text { flex-basis: calc(100% - 32px); }
    .wn-list-ver { margin-left: 32px; }
    .wn-try.compact { margin-left: auto; }
    .modal-foot:has(.wn-foot-left) > .btn { flex: 1 1 100%; }
    .wn-dock { bottom: 12px; }
}

/* ================= Häufigkeiten (Dauermessungen) =================
   Links alle Signale als Balkenliste nach Anzahl, rechts je gewähltem Signal eine Karte
   mit Verteilung. Beide Seiten rollen für sich: die Liste hat oft siebzig Einträge, und
   wer darin nach unten rollt, soll die Karte zu dem Signal, das er gerade angeklickt hat,
   nicht aus dem Blick verlieren. */
.hg-layout {
    display: grid; grid-template-columns: minmax(260px, 380px) minmax(0, 1fr); gap: 12px;
    flex: 1; min-height: 0;
}
.hg-all, .hg-cards { min-height: 0; overflow-y: auto; scrollbar-width: thin; }
.hg-all { display: flex; flex-direction: column; gap: 6px; padding-right: 4px; }
.hg-all-head { display: flex; align-items: center; gap: 8px; }
.hg-all-head .eyebrow { margin-right: auto; }
.hg-all-note { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; font-size: 10.5px; }
.hg-flag {
    padding: 1px 6px; border-radius: 8px; font-size: 10.5px;
    border: 1px solid var(--line-strong); color: var(--text-dim);
}
.hg-flag.warn { border-color: var(--warn); color: var(--warn); }
.hg-all-list { display: flex; flex-direction: column; gap: 1px; }
/* Name, Balken, Anzahl, Rate: der Balken nimmt, was die drei anderen übrig lassen. */
.hg-row {
    display: grid; grid-template-columns: minmax(0, 1.3fr) minmax(40px, 1fr) auto 62px;
    align-items: center; gap: 8px; padding: 3px 6px; cursor: pointer;
    border-radius: var(--radius-sm); border: 1px solid transparent;
}
.hg-row:hover { background: var(--panel-raised); }
.hg-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -1px; }
.hg-row.sel { border-color: var(--accent); background: var(--accent-soft); }
.hg-row-name { font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hg-row-bar { height: 8px; background: var(--panel-inset); border-radius: 2px; overflow: hidden; }
.hg-row-bar > span { display: block; height: 100%; background: var(--accent); opacity: .75; }
.hg-row-n { font-size: 10.5px; text-align: right; }
.hg-row-hz { font-size: 10px; text-align: right; color: var(--text-dim); }
/* Seltene und leere Signale: in der Warnfarbe – das ist der Grund, hier zu stehen. */
.hg-row.rare .hg-row-name, .hg-row.rare .hg-row-n { color: var(--warn); }
.hg-row.rare .hg-row-bar > span { background: var(--warn); }

.hg-cards {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    align-content: start; gap: 10px;
}
.hg-card {
    display: flex; flex-direction: column; gap: 6px; padding: 9px 10px 8px;
    border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel-raised);
}
.hg-card.warn { border-color: color-mix(in srgb, var(--warn) 55%, var(--line)); }
.hg-card-head { display: flex; align-items: center; gap: 7px; min-width: 0; }
.hg-dot { width: 9px; height: 9px; border-radius: 50%; flex: none; }
.hg-card-name { font-size: 12px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hg-card-unit { font-size: 10.5px; color: var(--text-dim); margin-left: auto; flex: none; }
.hg-stats { display: flex; flex-wrap: wrap; gap: 2px 10px; font-size: 10.5px; color: var(--text-dim); }
.hg-findings { margin: 0; padding: 0 0 0 14px; font-size: 11px; line-height: 1.4; color: var(--text-dim); }
.hg-findings li.warn { color: var(--warn); }
/* ---- Histogramm: Y-Titel, Y-Skala, Säulen; darunter X-Beschriftung und X-Titel ----
   Ein Raster statt verschachtelter Kästen: die Skala links muss genau so hoch sein wie die
   Säulenfläche, und das gibt das Raster von selbst. */
.hg-chart {
    display: grid; grid-template-columns: 14px 40px minmax(0, 1fr);
    grid-template-rows: 130px auto auto; column-gap: 4px;
}
.hg-ytitle {
    writing-mode: vertical-rl; transform: rotate(180deg); text-align: center;
    font-size: 9.5px; color: var(--text-dim); white-space: nowrap; overflow: hidden;
}
.hg-yaxis { position: relative; font-size: 9.5px; color: var(--text-dim); }
.hg-yaxis > span { position: absolute; right: 0; transform: translateY(50%); white-space: nowrap; line-height: 1; }
/* Die Säulen stehen auf einer Grundlinie; eine leere Klasse bleibt als Lücke sichtbar –
   eine Lücke mitten in der Verteilung ist selbst eine Aussage. */
.hg-plot {
    position: relative; display: flex; align-items: flex-end; gap: 1px;
    border-bottom: 1px solid var(--line-strong); border-left: 1px solid var(--line);
}
.hg-plot.discrete, .hg-xlabels.discrete { gap: 3px; }
.hg-grid { position: absolute; left: 0; right: 0; border-top: 1px dashed var(--line); pointer-events: none; }
.hg-col {
    position: relative; flex: 1 1 0; min-width: 0; height: 100%;
    display: flex; flex-direction: column; justify-content: flex-end; cursor: pointer;
}
.hg-bar { display: block; width: 100%; background: var(--accent); border-radius: 1px 1px 0 0; min-height: 0; transition: opacity .12s; }
.hg-col:hover .hg-bar { filter: brightness(1.25); }
.hg-col:hover { background: color-mix(in srgb, var(--text) 5%, transparent); }
.hg-col.warn .hg-bar { background: var(--warn); }
.hg-col.other .hg-bar { background: var(--line-strong); }
/* Eine gewählte Säule: sie bleibt voll, die übrigen treten zurück. */
.hg-plot.picking .hg-col:not(.sel) .hg-bar { opacity: .4; }
.hg-col.sel .hg-bar { outline: 2px solid var(--text); outline-offset: 1px; }
.hg-xlabels { display: flex; padding-top: 3px; }
.hg-xlabels > span {
    flex: 1 1 0; min-width: 0; text-align: center; font-size: 9.5px; color: var(--text-dim);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.hg-axis { display: flex; justify-content: space-between; font-size: 9.5px; color: var(--text-dim); padding-top: 3px; }
.hg-xtitle { text-align: center; font-size: 10px; color: var(--text-dim); padding-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hg-card-head .hg-png { margin-left: 4px; flex: none; width: 24px; height: 24px; font-size: 12px; }
.hg-card-unit + .hg-png { margin-left: 4px; }
.hg-card-head .hg-card-name + .hg-png { margin-left: auto; }

/* Die gewählte Säule im Klartext. */
.hg-detail {
    margin-top: 2px; padding: 7px 9px; border-radius: var(--radius-sm);
    border: 1px solid var(--accent); background: var(--accent-soft);
}
.hg-detail-head { display: flex; align-items: center; gap: 8px; }
.hg-detail-head strong { font-size: 11.5px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hg-detail-head .btn { margin-left: auto; width: 22px; height: 22px; }
.hg-detail-grid { display: grid; grid-template-columns: auto 1fr; gap: 2px 12px; font-size: 11px; margin-top: 4px; }
.hg-detail-grid > span:nth-child(odd) { color: var(--text-dim); }
.hg-detail-warn { margin-top: 5px; font-size: 11px; color: var(--warn); }
@media (max-width: 900px) {
    .hg-layout { grid-template-columns: 1fr; }
    .hg-all { max-height: 280px; }
}

/* ================= Messwerttabelle (Dauermessungen) =================
   Filterzeile über der Tabelle, zwei Arten Zeilen darin: Diagnose und GPS. Die GPS-Zeilen
   sind leicht getönt – sie tragen eine Position und keinen Messwert, und in einer langen
   Liste soll man sie überspringen können, ohne jede Zeile zu lesen. */
.tt-filters { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; margin-bottom: 8px; }
.tt-filters .select, .tt-filters .input { height: 28px; font-size: 12px; }
.tt-f-signal { max-width: 240px; }
.tt-f-ecu { max-width: 130px; }
.tt-f-range { display: inline-flex; align-items: center; gap: 4px; }
.tt-f-range .input { width: 92px; }
.tt-filters .ts-f-search { flex: 1 1 180px; min-width: 160px; }
.tt-note { font-size: 11px; margin: -2px 0 8px; }
.tt-table .tt-time { font-size: 11.5px; white-space: nowrap; }
.tt-table tr.tt-gps td { background: color-mix(in srgb, var(--accent) 5%, transparent); }
.tt-kind {
    display: inline-block; padding: 0 6px; border-radius: 8px; font-size: 10px; line-height: 16px;
    border: 1px solid var(--line-strong); color: var(--text-dim); white-space: nowrap;
}
.tt-kind.gps { border-color: var(--accent); color: var(--accent); }
