﻿/* Manrope (variable, 200-800) lokal eingebettet — funktioniert offline und
   rendert auf Windows/Android/iOS identisch (Lizenz: SIL OFL, siehe fonts/OFL-Manrope.txt) */
@font-face {
    font-family: 'Manrope';
    font-style: normal;
    font-weight: 200 800;
    font-display: swap;
    src: url('../fonts/manrope-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: 'Manrope';
    font-style: normal;
    font-weight: 200 800;
    font-display: swap;
    src: url('../fonts/manrope-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

html, body {
    font-family: 'Manrope', 'Segoe UI', Roboto, 'Helvetica Neue', Helvetica, Arial, sans-serif;
    overscroll-behavior: none;
    /* Automatische Text-Vergrößerung der Engine unterbinden (iOS/WKWebView "Text Autosizing",
       Chrome "Font Boosting"). Unser Layout arbeitet mit festen Höhen — skalierter Text
       würde Zeilen umbrechen und Positionen verschieben.
       100% statt none: none bricht in Safari/Chrome den manuellen Pinch-Zoom.
       Android braucht zusätzlich TextZoom = 100 nativ, dort greift CSS nicht (siehe
       Platforms/Android/CustomBlazorWebViewHandler.cs). */
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
}

a {
    color: var(--mud-palette-primary);
}

/* ==========================================================================
   MudTabs: Tab-Leiste echt scrollbar statt Transform-Verschiebung

   MudBlazor scrollt die Tab-Leiste nicht, sondern schiebt .mud-tabs-tabbar-wrapper
   (width:max-content) per Inline-transform:translateX() hin und her — bedienbar nur
   über die eingeblendeten Pfeile. Zwei Folgen auf dem Handy:
     1. Die Leiste lässt sich nicht mit dem Finger wischen (nichts zu greifen).
     2. scrollIntoView() greift ins Leere — es gibt keinen Scroll-Container. Deshalb
        konnte das Tutorial-Highlighting (ai-highlight.js) einen außerhalb liegenden
        Tab nicht in den Blick holen; Anwender fanden ihn schlicht nicht.

   Umstellung auf echtes Scrollen löst beides: Wischen macht der Browser, und
   scrollIntoView findet einen Container vor.

   Die Pfeile bleiben — auf Windows sind sie die einzige Bedienung (nur Maus). Da sie
   den neutralisierten transform ansteuern würden, wird der innere Button per
   pointer-events:none klickunfähig gemacht: der Klick landet auf dem Container und
   scrollt über handleTabScrollButton() in helpers.js. Nebeneffekt erwünscht — ein von
   MudBlazor als Disabled gerenderter Button würde sonst gar kein Klick-Ereignis
   auslösen. Das Ein-/Ausblenden bei Überlauf regelt weiterhin MudBlazor selbst.
   ========================================================================== */
/* Der Überlauf entsteht in .mud-tabs-tabbar-content (MudBlazor: overflow:hidden) —
   NICHT im umgebenden .mud-tabs-tabbar-inner, das nur Pfeile + Content nebeneinander
   legt. Der Scroll-Container muss deshalb hier sitzen. */
.mud-tabs-tabbar-content {
    overflow-x: auto !important;
    overflow-y: hidden;
    /* Scrollbar-Unterdrückung: Selektor ist unten an .hide-scrollbar angehängt.
       Hier bewusst immer verborgen (auch auf Windows, wo hide-scrollbar sonst
       weggelassen wird) — die Pfeile sind die sichtbare Bedienung. */
}

.mud-tabs-tabbar-wrapper {
    transform: none !important;
    transition: none !important;
}

/* Leistenhöhe dem Inhalt überlassen: MudBlazors min-height:48px und das
   stretch-Standardverhalten hielten die Leiste sonst auf voller Höhe, obwohl die
   Tabs auf schmalen Fenstern per @media (Tabs.razor) kleiner werden — die Tabs
   wurden dann nach oben gezogen statt mittig zu sitzen. */
.mud-tabs-tabbar .mud-tabs-tabbar-inner {
    align-items: center;
    min-height: 0;
}

/* Pfeile kompakt halten: MudBlazors Icon-Button ist ~48px breit und frisst
   Platz, der den Tabs fehlt. KEIN padding-top — das gab dem Pfeil eine feste
   Höhe von 30px, die bei schmalem Fenster die Leiste aufblähte; die vertikale
   Zentrierung übernimmt align-items oben. */
.mud-tabs-scroll-button {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    pointer-events: auto;
    cursor: pointer;
}

/* Klick am inneren Button vorbei auf den Container leiten.
   overflow:hidden ist wichtig: das Symbol ist absichtlich größer als sein Button
   (s. unten) und würde sonst 4px überstehen — am rechten/oberen Rand des Fensters
   genügt das, um am Wurzelelement Scrollbars auszulösen. Geclippt wird nur leerer
   SVG-Rand, der Pfeilpfad selbst liegt im mittleren Drittel der viewBox. */
.mud-tabs-scroll-button .mud-button-root,
.mud-tabs-scroll-button .mud-icon-button-label {
    pointer-events: none;
    min-width: 0;
    width: 20px;
    height: 20px;
    padding: 0;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* Symbol größer als der Button: schmale Klickfläche, aber gut sichtbare Pfeile.
   flex-shrink:0 ist Pflicht — im Flex-Button würde es sonst auf 20px gestaucht. */
.mud-tabs-scroll-button .mud-icon-root {
    width: 28px;
    height: 28px;
    font-size: 28px;
    flex-shrink: 0;
}

/* Aktiv-/Inaktiv-Zustand der Pfeile selbst steuern. MudBlazor leitet ihn aus seiner
   eigenen Scroll-Position ab — die bleibt dauerhaft 0, weil handleTabScrollButton()
   den Klick abfängt, bevor MudBlazors Logik läuft. Folge wäre: linker Pfeil immer
   ausgegraut, rechter immer dunkel. updateTabScrollButtons() setzt die Klassen
   stattdessen am echten scrollLeft. Ohne Klasse gilt MudBlazors Darstellung, die
   beim Laden korrekt ist (Leiste steht dann tatsächlich ganz links). */
.mud-tabs-scroll-button.a2d-arrow-on .mud-button-root {
    opacity: 1 !important;
    color: var(--mud-palette-text-primary) !important;
}

.mud-tabs-scroll-button.a2d-arrow-off .mud-button-root {
    opacity: 0.25 !important;
}

/* Filled-Inputs mit Surface-Hintergrund (global, da MudBlazor-DOM von scoped CSS nicht erreicht wird) */
.mud-input.mud-input-filled {
    background-color: var(--mud-palette-surface);
}

/* ==========================================================================
   Manrope-Unterlängen: abgeschnittene g/y/p/q/j in MudBlazor-Eingabefeldern

   MudBlazors Zeilenhöhen sind auf Roboto ausgelegt. Manrope hat deutlich
   größere vertikale Metriken (Content-Area 1.375em gegenüber 1.171em bei
   Roboto), dadurch sitzt die Grundlinie tiefer in der Zeilenbox und die
   Unterlängen ragen unten heraus.

   Rechenweg (unabhängig von der Schriftgröße):
     Grundlinie   = line-height/2 + (ascent - descent)/2 = line-height/2 + 0.375em
     Unterlänge g = 0.25em unter der Grundlinie
     -> vollständig sichtbar erst ab line-height >= 1.25em
   MudBlazor setzt auf den schwebenden Labels aber line-height: 1.15rem, und
   ".mud-input-label" trägt overflow:hidden (nötig für text-overflow:ellipsis).
   Diese Kante schneidet 1.2px des g/y/p ab — sichtbar z.B. bei "Datengruppe",
   "Datentyp" oder "Beschreibung".

   Korrigiert wird über padding-bottom, NICHT über line-height: Das vergrößert
   nur die Padding-Box (= die Clipping-Kante von overflow:hidden), lässt die
   Zeilenbox und damit die Grundlinie unverändert. Das Label würde sonst um
   ~1.3px nach unten wandern. Da das Label position:absolute ist, hat die
   größere Box keinerlei Einfluss auf das umgebende Layout.

   !important + zwei Klassen sind nötig, weil MudBlazor auf der Outlined-
   Variante "padding: 0px 5px !important" setzt (nur ein Klassenname, also
   geringere Spezifität) und app.css VOR MudBlazor.min.css geladen wird.
   ========================================================================== */
.mud-input-label.mud-input-label-inputcontrol {
    padding-bottom: 3px !important;
}
/* Der eingegebene Wert selbst braucht KEINE Korrektur: .mud-input hat zwar
   ebenfalls eine knappe line-height (1.1876em), das <input> reserviert über
   seine Paddings aber genug Platz — bei 4-facher Vergrößerung geprüft, die
   Unterlängen stehen frei. Eine Anhebung würde das Feld nur 1px höher machen. */

#blazor-error-ui {
    background: lightyellow;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    position: absolute;
    right: 0.75rem;
    top: 0.5rem;
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

code {
    color: #c02d76;
}

.a2dtitle {
    color: #f57b27;
    text-align: center;
    font-size: 25px;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}

.dialogBlurry {
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
}

/* Fix für abgeschnittene Picker-Panels in Dialogen:
   MudBlazor animiert .mud-dialog beim Öffnen mit "animation-fill-mode: both",
   wodurch der End-Keyframe (transform: scale(1)) erhalten bleibt. Ein transform != none
   macht den Dialog zum "containing block" für position:fixed-Kinder. Ein darin
   geöffneter MudDatePicker/MudTimePicker (PickerVariant.Dialog) positioniert sein Panel
   per position:fixed und wird dadurch am Dialog statt am Fenster verankert und vom
   overflow:auto des Dialogs abgeschnitten.
   "backwards" behält die Öffnen-Animation, verwirft aber das Rest-transform danach. */
.mud-dialog {
    animation-fill-mode: backwards !important;
    /* Nebenwirkungs-Fix: Ohne das Rest-transform (siehe oben) ist der Dialog kein
       containing block mehr für den absolut positionierten Standard-Schließen-Button
       (.mud-button-close, top/right: 8px) — der landete dadurch in der Bildschirmecke
       statt im Dialog. position:relative verankert ihn wieder am Dialog, erzeugt aber
       KEINEN containing block für position:fixed — der Picker-Fix bleibt wirksam. */
    position: relative;
}

/* .mud-tabs-tabbar-content ist mit aufgeführt: die Tab-Leiste scrollt echt (s. oben),
   soll ihre Scrollbar aber auf JEDER Plattform verbergen — anders als die Listen, die
   HideScrollbarClass auf Windows bewusst weglassen. Bedient wird sie über die Pfeile. */
.hide-scrollbar::-webkit-scrollbar,
.mud-tabs-tabbar-content::-webkit-scrollbar {
    display: none;
    width: 0;
    height: 0;
    background: transparent;
}

.hide-scrollbar::-webkit-scrollbar-thumb,
.mud-tabs-tabbar-content::-webkit-scrollbar-thumb {
    background: transparent;
}

.hide-scrollbar::-webkit-scrollbar-track,
.mud-tabs-tabbar-content::-webkit-scrollbar-track {
    background: transparent;
}

/* !important ist Pflicht: MainLayout.razor erklärt global `* { scrollbar-width: thin
   !important }`. Ohne Gegenwehr blieb hier `thin` stehen — die Scrollbar war dank
   scrollbar-color zwar unsichtbar, belegte aber weiter ihre ~10px. In der Tab-Leiste
   wuchs die Leiste dadurch bei Überlauf über ihre Tabs hinaus (63 statt 53), und
   .hide-scrollbar blieb überall wirkungslos. Fällt nur mit klassischen Scrollbars auf;
   überlagernde brauchen ohnehin keinen Platz.
   Klassen-Spezifität schlägt bei gleichem !important den Universalselektor. */
.hide-scrollbar,
.mud-tabs-tabbar-content {
    -ms-overflow-style: none;
    scrollbar-width: none !important;
    scrollbar-color: transparent transparent;
}

:root {
    /* Chat-/Auswahlfarben ans Navy-Designsystem angeglichen (2026-07):
       hell = kühles Grau + Primary-Tint für eigene Nachrichten,
       dunkel = Navy-Canvas + Marken-Blau statt Beige/Teal/Neutralgrau */
    /* Fläche der Seite hinter dem Inhaltsbereich. Früher stand hier die Linienfarbe der
       Palette (--mud-palette-lines-default, #0000001E) — also eine halbdurchsichtige Farbe,
       die erst durch den Hintergrund darunter ihren Ton bekam. Das machte den Wert von
       außen unbestimmbar: Der Startbildschirm konnte ihn nicht treffen, ohne denselben
       Unterbau nachzubauen, und ein eingefärbter Body hätte die Deckkraft ein zweites Mal
       aufgetragen. Jetzt fest hinterlegt — es sind exakt die zuvor sichtbaren Töne.
       Diese Fläche erscheint bewusst ERST mit der Oberfläche (.a2d-page-bg unten). */
    --pagebackground-color-light: #D7D9DD;
    --pagebackground-color-dark: #282E3A;
    /* Farbe der App-Fläche selbst, also der 600px-Spalte. Identisch mit Background der
       beiden MudBlazor-Paletten in MainLayout.razor — hier zusätzlich hinterlegt, weil sie
       schon gebraucht wird, BEVOR der ThemeProvider läuft: Alles vor dem Erscheinen der
       Oberfläche trägt diesen Ton (a2d-app-bg unten, beide index.html, MainPage des
       MAUI-Hosts, background_color im PWA-Manifest). Dadurch wechselt die Spalte beim
       Einblenden der Oberfläche ihre Farbe nicht; es ändert sich nur die Fläche darum
       herum von dieser Farbe auf die Seitenfläche oben.
       Wer sie ändert, muss alle fünf genannten Stellen nachziehen. */
    --appbackground-color-light: #F4F6FA;
    --appbackground-color-dark: #0B1220;
    --bwbackground-color-light: #FFFFFF;
    --bwbackground-color-dark: #000000;
    --chatbackground-color-light: #EDF1F7;
    --chatbackground-color-dark: #0B1220;
    --chatmessageauthor-color-light: #DBE7FA;
    --chatmessageauthor-color-dark: #24406B;
    --chatmessageother-color-light: #FFFFFF;
    --chatmessageother-color-dark: #16233B;
    --text-color-light: #000000;
    --text-color-dark: #FFFFFF;
    --text-color-light2: #222222;
    --text-color-dark2: #f9f9f9;
    /* Blau hervorgehobene FLÄCHE, die den normalen Textfarbton (--mud-palette-text-primary)
       tragen muss: ausgewählte Zeilen/Kacheln, heutiger Tag im Kalender (A2dScheduler),
       Oberkategorie im Kategorie-Chip (A2dCategories).
       Nicht var(--mud-palette-primary) verwenden — das ist die AKTIONSfarbe und steht in den
       beiden Modi gegenläufig (hell #1D5BBF dunkel / dunkel #7FACF0 hell), während der
       Standardtext genau andersherum kippt: schwarz auf #1D5BBF = 3,3:1, weiß auf #7FACF0
       = 2,3:1 (WCAG-Minimum 4,5:1). Dieses Paar läuft mit dem Text mit: 14,8:1 bzw. 8,9:1. */
    --selectedrow-color-light: #D6E4F8;
    --selectedrow-color-dark: #2A4A7D;

    /* Zähler-Badge mit Stand 0 in den Benachrichtigungen (A2dNotifications): tritt bewusst grau
       zurück, während gefüllte Zähler in Color.Info stehen.
       HELL bleibt unverändert = exakt Color.Tertiary (Gray.Darken1 mit weißer Schrift, 4,6:1).
       DUNKEL geht nicht über Tertiary: dort ist Tertiary das fast weiße #F0F0F0, auf dem die
       weiße Badge-Schrift nur 1,1:1 erreichte — die Zahl war praktisch unsichtbar. Tertiary
       lässt sich dafür auch nicht umstellen, weil dieselbe Farbe app-weit als HINWEISTEXT-
       und Icon-Farbe dient und dort hell bleiben muss. Deshalb ein eigenes Paar:
       gedecktes Blaugrau mit dunkler Schrift (6,9:1) — siehe .a2d-badge-zero. */
    --zerobadge-background-color-light: #757575;
    --zerobadge-background-color-dark: #A9AFB9;
    --zerobadge-text-color-light: #FFFFFF;
    --zerobadge-text-color-dark: #1F2633;

    /* Kontur der Listenkarten mit Elevation 25 auf Theme-Background: Benachrichtigungen
       (A2dNotifications), Event-Ordner (A2dAdminEvents), Gruppen (A2dAdminGroups),
       Suchtreffer (A2dSearch), Anmeldungen (A2dRegistered) und die Ordnerauswahl im
       MoveToEventDialog — siehe .a2d-card-outline.
       HELL transparent: dort setzt der Schlagschatten die Karten ab.
       DUNKEL sichtbar: MudBlazors Schatten sind schwarz (rgba(0,0,0,0.06)) und verschwinden
       auf dem Navy-Canvas #0B1220 spurlos — die Karten hatten dort keine Kante.
       Weiß mit 20 % statt --mud-palette-lines-default (12 %): letzteres kam auf 1,4:1 und
       war auf dem dunklen Grund kaum wahrnehmbar; dieser Wert erreicht 1,9:1 — bewusst blass,
       die Kante soll die Karte nur andeuten, nicht einrahmen.
       Gezeichnet wird per outline, NICHT per border — border würde bei den 46-65px hohen
       Karten 2px aus der Inhaltshöhe nehmen und das randfüllende Vorschaubild verdrängen.
       Deshalb auch keine Umstellung auf MudPapers Outlined="true": das setzt intern eine
       border UND ersetzt die Elevation-Klasse, womit der Hellmodus seinen Schatten verlöre. */
    --cardoutline-color-light: transparent;
    --cardoutline-color-dark: rgba(255, 255, 255, 0.20);

    /* KI-Assistent-Panel (ChatBot): EIGENE Flächenfarbe, bewusst NICHT die geteilte
       --chatbackground-color der Gruppen-/Privat-Chats. Grund: Das Panel liegt auf dem Handy als
       schwebende Karte ÜBER der App und muss sich davon absetzen — mit dem bisherigen Chat-Ton war
       es von der App-Fläche (Theme-Background #F4F6FA hell / #0B1220 dunkel) nicht zu unterscheiden
       (hell 1,05:1, dunkel bitgenau identisch).
       Gewählt sind bewusst die REINEN Extreme: hell weiß, dunkel schwarz. Der Flächenkontrast zur
       App bleibt damit gering (1,08:1 bzw. 1,12:1 — die App-Fläche liegt selbst schon nahe an den
       Enden der Helligkeitsskala, ein Zwischenton wie #DCE4F2/#17253E käme auf 1,18:1/1,22:1). Die
       Abgrenzung nach außen trägt daher die Kontur des Panels (border in MainLayout), nicht die
       Fläche.
       Dasselbe Prinzip gilt INNEN: Bot-Blasen, Agent-Rückfragen und Eingabefeld bleiben flächig
       weiß bzw. schwarz und werden allein von --aipanel-outline-color gezeichnet. Die Kontur ist
       bewusst EINE halbtransparente Konstante statt eines Hell-/Dunkel-Paars: Blaugrau bei 45 %
       dunkelt gegen Weiß ab und hellt gegen Schwarz auf (1,75:1 bzw. 2,0:1) — sie kann also nie
       falsch herum stehen und braucht keine Umschaltung in updateA2dColorVars(). */
    --aipanel-background-color-light: #FFFFFF;
    --aipanel-background-color-dark: #000000;
    --aipanel-outline-color: rgba(127,144,176,0.45);
    /* Eigene Nachricht: gedecktes Navy-Blau statt --mud-palette-primary. Primary ist die
       Aktionsfarbe der App und als Vollton auf der ruhigen Outline-Fläche zu laut. Diese Werte sind
       entsättigt und in beiden Modi dunkel genug, dass der weiße Text (siehe .ai-msg-user) sicher
       darauf liegt — Primary erfüllte das im Dunkelmodus NICHT: dort ist es das helle #7FACF0, auf
       dem weißer Text nur 2,3:1 erreichte (WCAG-Minimum für Text: 4,5:1). Jetzt 5,1:1 hell und
       8,3:1 dunkel. Wer hier nachjustiert, muss den Kontrast zu Weiß mitprüfen. */
    --aipanel-usermsg-color-light: #4A6FA5;
    --aipanel-usermsg-color-dark: #25406B;
    /* Hervorhebungs-Chips IM Chatverlauf ("👉 …", "🔍 Alle sichtbaren Elemente"): gedämpftes
       Violett statt des Neon-#A42EFF, das als Bedienelement neben ruhigen Outline-Blasen zu laut
       war. Hell liegt der Wert kanalweise mittig zwischen dem Original #A42EFF (164,46,255) und der
       stärker entsättigten Stufe #7E4BC4 (126,75,196); dunkel wird eben diese entsättigte Stufe
       genommen — auf schwarzem Grund wirkt dieselbe Sättigung deutlich greller als auf weißem
       (der Ton "glüht" gegen die dunkle Umgebung), er darf dort also ruhiger ausfallen.
       Weißer Text: 5,3:1 hell, 5,7:1 dunkel (Original 4,8:1). Untergrenze beim Nachjustieren: 4,5:1.
       WICHTIG: Der Hervorhebungs-Ring und sein Tooltip IN DER APP behalten bewusst das kräftige
       #A42EFF (ai-highlight.js) — dort ist das grelle Violett gewollt, es blitzt nur Sekunden auf
       und muss das Auge sofort zum Element ziehen. Chip = ruhiger Knopf, Ring = kurzes Signal;
       dieselbe Farbfamilie hält die Zuordnung trotzdem lesbar. Hier NICHT vereinheitlichen. */
    --aipanel-chip-color-light: #913DE2;
    --aipanel-chip-color-dark: #7E4BC4;
    /* Grundton der gewöhnlichen Tutorial-Buttons (Featured-Liste unten UND Verweis-Buttons im
       Chatverlauf — beide tragen .ai-tutorial-btn). Ersetzt --mud-palette-action-default-hover:
       dessen 6 % Schwarz wirkten auf der nun reinweißen Panelfläche zu schwer. Hell daher leichter
       UND navy-getönt statt neutralgrau (passt zum Designsystem), dunkel bewusst exakt MudBlazors
       bisheriger Wert — dort war der Ton stimmig und soll sich nicht ändern.
       Die hervorgehobenen (orange) und die Index-Buttons (blau) haben eigene Regeln und bleiben. */
    --aipanel-tutorial-btn-color-light: rgba(127,144,176,0.08);
    --aipanel-tutorial-btn-color-dark: rgba(255,255,255,0.06);
    /* Schrift-/Icon-Farbe der HERVORGEHOBENEN Tutorial-Buttons ("Erste Schritte im Verein" u.ä.).
       Sie laufen als MudButton mit Color.Secondary, und Secondary ist im Theme für BEIDE Modi
       derselbe Wert #F57B27 — auf der schwarzen Panelfläche erreichte der Text damit 7,7:1 und
       leuchtete förmlich. Dunkel deshalb ein abgedunkeltes Marken-Orange (5,3:1): gleicher Farbton
       und gleiche Sättigung, nur weniger Helligkeit, damit es als dasselbe Orange gelesen wird und
       nicht als anderes Braun. Hell bleibt es exakt Secondary.
       Nur die SCHRIFT wird überschrieben — MudBlazors Outlined-Rahmen liegt ohnehin bei 50 %
       Deckkraft und ist nicht die Blendquelle; ihn volldeckend zu setzen würde ihn verstärken. */
    --aipanel-highlight-btn-color-light: #F57B27;
    --aipanel-highlight-btn-color-dark: #C06A28;
    /* Startwerte für den Fall, dass das Panel vor dem ersten updateA2dColorVars()-Lauf rendert
       (Inline-Style am <html> überschreibt sie danach) — ohne sie bliebe es kurz transparent. */
    --aipanel-background-color: #FFFFFF;
    --aipanel-usermsg-color: #4A6FA5;
    --aipanel-tutorial-btn-color: rgba(127,144,176,0.08);
    --aipanel-chip-color: #913DE2;
    --aipanel-highlight-btn-color: #F57B27;

    /* Design-Tokens (Paket 2 des Design-Umbaus): Standardmaße aus dem
       Komponenten-Bestand — Werte NICHT ändern, sie ersetzen 1:1 die
       bisherigen Inline-Styles */
    --space-xs: 3px;
    --space-sm: 5px;
    --space-md: 10px;
    --space-lg: 15px;
    --btn-icon-size: 30px;      /* kompakte MudIconButtons in Listenzeilen */
    --chat-attach-size: 120px;  /* Vorschau-Kacheln für Chat-Anhänge */
}

/* ==========================================================================
   Utility-Klassen (Paket 2 des Design-Umbaus): ersetzen wiederkehrende
   Inline-Styles der Razor-Komponenten. Bewusst global (nicht scoped), da
   sie auch via Class="..." auf MudBlazor-Komponenten landen, deren DOM
   von scoped CSS nicht erreicht wird.
   ========================================================================== */

/* --- Signalfarbe für Warnungen/Absagen (Marken-Orange, entspricht Secondary) ---
   Lag doppelt in den <style>-Blöcken von A2dScheduler und Start, wurde aber von
   A2dSessionItem und SessionAttendanceDialog genutzt — beide definieren sie
   nicht selbst und funktionierten nur, solange eine der Wirtskomponenten
   gleichzeitig gerendert war. Deshalb hierher, wo sie ohnehin global gilt.
   MudBlazor überschreibt sie nicht: .mud-typography und .mud-icon-root setzen
   keine color, .mud-svg-icon übernimmt sie via fill:currentColor. */
.warnColor { color: #f57b27; }

/* --- Grid-Spalten-Marker für Zeilenlayouts (.gridwrapper u.ä.) --- */
.a2d-col-1 { grid-column: 1; }
.a2d-col-2 { grid-column: 2; }
.a2d-col-3 { grid-column: 3; }

/* --- Layout / Scroll-Container --- */
.a2d-flex-center { display: flex; justify-content: center; align-items: center; }
/* Wurzel-Layout, das die volle Höhe füllt und Kinder als Spalte stapelt */
.a2d-col-fill { height: 100%; display: flex; flex-direction: column; }
/* Vertikal scrollbarer Bereich (Höhe kommt vom Kontext oder per style) */
.a2d-scroll-y { overflow-x: hidden; overflow-y: auto; }
/* Volle Höhe + vertikal scrollbar (Listen in Tabs/Dialogen) */
.a2d-fill-scroll { height: 100%; overflow-x: hidden; overflow-y: auto; }
/* Flex-Kind, das den Restplatz füllt und selbst scrollt
   (min-height:0 ist Pflicht, sonst greift overflow nicht — s. Flexbox-Pattern) */
.a2d-flex-fill { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; }
.a2d-rel { position: relative; }
.a2d-fit { width: fit-content; }
.a2d-w-full { width: 100%; }
/* Inhalt in beide Richtungen abschneiden (Chip-Leisten u.ä.) */
.a2d-clip { overflow-x: hidden; overflow-y: hidden; }
/* Horizontal scrollbare Leiste (Profil-/Termin-Karussells) */
.a2d-scroll-x { overflow-x: auto; overflow-y: hidden; }
/* Flex-Kind füllt Restplatz, ohne selbst zu scrollen (min-height:0 s. Flexbox-Pattern) */
.a2d-flex-1 { flex: 1; min-height: 0; }
.a2d-no-shrink { flex-shrink: 0; }
/* Seiten-Scrollbereich unterhalb der AppBar (70px) */
.a2d-page-scroll { overflow-x: hidden; overflow-y: auto; height: calc(100dvh - 70px); }
/* Dialog-Scrollbereiche: kompakt (Titel+Buttons = 250px) und hoch (70px) */
.a2d-dialog-scroll { overflow-x: hidden; overflow-y: auto; max-height: calc(100dvh - 250px); }
.a2d-dialog-scroll-lg { overflow-x: hidden; overflow-y: auto; max-height: calc(100dvh - 70px); margin-left: var(--space-md); margin-right: var(--space-md); }
/* Hintergrund-Utilities (statt Inline background-color).
   Zusatzselektor .mud-paper.*: auf MudPaper würde die eigene Farbe sonst von
   MudBlazors später geladener ".mud-paper { background-color: ... }" gewonnen. */
.a2d-bg-none,
.mud-paper.a2d-bg-none { background-color: transparent; }
.a2d-bg-default,
.mud-paper.a2d-bg-default { background-color: var(--mud-palette-background); }
/* Feste Breiten/Höhen aus dem Bestand (!important wie in den bisherigen Inline-Styles) */
.a2d-w-140 { width: 140px !important; }
.a2d-minw-135 { min-width: 135px !important; }
.a2d-h-65 { height: 65px; }

/* --- Formular-/Datengrids (A2dProfile, A2dProfileReadonly, Dialoge) --- */
/* Label-Spalte + Wert-Spalte */
.a2d-grid-label-value { display: grid; grid-template-columns: minmax(auto, 200px) minmax(150px, auto); }
/* Label-Spalte + restliche Breite, Inhalte zentriert */
.a2d-grid-label-fill { display: grid; grid-template-columns: minmax(auto, 200px) 1fr; place-items: center; }
/* Breites Feld + schmale Aktionsspalte */
.a2d-grid-3-1 { display: grid; grid-template-columns: 3fr 1fr; }
.a2d-text-center { text-align: center; }
.a2d-mx-auto { margin-left: auto; margin-right: auto; }

/* --- Kompakter IconButton in Listenzeilen (Notifications, Members, ...) ---
   Achtung Ladereihenfolge: app.css lädt VOR MudBlazor.min.css. Der Zusatz-
   selektor .mud-button-root.a2d-icon-btn hebt die Spezifität, damit das
   margin-left nicht von MudBlazors ".mud-button-root { margin: 0; }"
   überschrieben wird (bei gleicher Spezifität gewinnt sonst MudBlazor). */
.a2d-icon-btn,
.mud-button-root.a2d-icon-btn { width: var(--btn-icon-size); height: var(--btn-icon-size); margin-left: var(--space-sm); }
/* Mittlere Variante (Kopfzeilen-Aktionen) */
.a2d-icon-btn-35 { width: 35px; height: 35px; }

/* --- 30px-Listenzeilen-Icon mit Standardabstand (Dokumente, Topics, WorkTasks, Admin-Menüs) --- */
.a2d-list-icon { margin: 6px 3px 0px 3px; }

/* --- Seiten-Hintergrund hinter dem Inhalt (MainLayout) --- */
/* Startwert folgt der Systemeinstellung, damit die Fläche schon vor dem ersten Rendern
   stimmt; sobald das Erscheinungsbild der Anwendung feststeht, setzt updateA2dColorVars
   den passenden Wert (die Anwendung kann hell/dunkel auch abweichend vom System führen). */
:root { --pagebackground-color: #D7D9DD; }
@media (prefers-color-scheme: dark) {
    :root { --pagebackground-color: #282E3A; }
}
/* Die Fläche beginnt im Ton der App und geht erst kurz danach weich ins Graue über.
   Grund: Der Authorized-Zweig rendert dieses Element in DERSELBEN Bildfolge, in der die
   600px-Spalte darin erst noch gefüllt wird. Ohne die Verzögerung sah man deshalb einen
   Augenblick lang die graue Fläche allein und danach erst die App darauf — genau der
   Farbschritt, den die App-Farbe in index.html und MainPage.xaml vermeiden soll.
   "backwards" ist der tragende Teil: Es legt den Startwert (App-Farbe) schon WÄHREND der
   Verzögerung an, nicht erst beim Beginn des Übergangs. Ein zweiter Keyframe ist nicht
   nötig — ohne "to" läuft die Bewegung auf den eigenen Wert des Elements zu. */
.a2d-page-bg {
    background-color: var(--pagebackground-color);
    min-height: 100dvh;
    animation: a2d-pagebg-settle 300ms ease-out 200ms backwards;
}

@keyframes a2d-pagebg-settle {
    from { background-color: var(--appbackground-color); }
}

/* --- Fläche VOR dem Erscheinen der Oberfläche --- */
/* Trägt die Farbe der App-Fläche, nicht die der Seitenfläche. Grund: Solange die Oberfläche
   nicht da ist, soll nichts zu sehen sein, was sich beim Einblenden noch einmal ändert. Die
   Seitenfläche (grau) kommt erst mit .a2d-page-bg — und zwar in derselben Bildfolge, in der
   auch die Spalte erscheint. Wäre hier schon Grau, sähe man erst Grau und kurz darauf Grau
   MIT App: ein Farbschritt mehr als nötig.
   Verwendet im Authorizing-Zweig von MainLayout.razor; denselben Ton tragen beide index.html
   und die MainPage des MAUI-Hosts. */
:root { --appbackground-color: #F4F6FA; }
@media (prefers-color-scheme: dark) {
    :root { --appbackground-color: #0B1220; }
}
.a2d-app-bg { background-color: var(--appbackground-color); min-height: 100dvh; }

/* --- Chat: Anhänge und Link-Vorschau (ChatDialog, ChatPrivateDialog,
       Start, CommentsDialog, NewDialog_Story) --- */
/* Kopfzeile einer Anhang-/Downloadzeile (kombiniert mit d-flex align-center) */
.a2d-attach-row { height: 45px; padding: 0 12px; }
/* Vorschau-Kachel eines Bild-Anhangs; !important wie im bisherigen Inline-Style,
   damit anderweitig gesetzte Bildgrößen überstimmt bleiben */
.a2d-attach-tile { width: var(--chat-attach-size) !important; height: var(--chat-attach-size) !important; }
/* Lade-Variante der Kachel: zentriert den 30px-Spinner per Innenabstand */
.a2d-attach-tile-loading { padding-left: 45px; padding-top: 45px; }
/* Dateiname unter einer 120px-Kachel */
.a2d-attach-caption { width: 120px; margin-top: var(--space-sm); padding-left: var(--space-sm); padding-right: var(--space-sm); color: var(--mud-palette-secondary); }
/* Link-Vorschau: Textbox + Beschriftung.
   265px ist die WUNSCH-Breite, kein Boden — deshalb width + max-width:100% statt min-width:
   Wo die Chat-Blase weniger Platz bietet (schmaler Schirm, Avatarspalte, tiefe Verschachtelung),
   schrumpft die Vorschau mit, statt die Blase zu sprengen. Mit min-width:265px war das unmöglich:
   eine Mindestbreite gewinnt in CSS immer gegen jedes max-width (das .regHyperlinkText-max-width
   der Chat-Dialoge war dadurch wirkungslos), und die Blase ist width:fit-content — die kann nie
   schmaler werden als die min-content-Breite ihres Inhalts.
   overflow-wrap:anywhere bricht die URL an beliebiger Stelle um UND senkt eben diese
   min-content-Breite; nur beides zusammen lässt die Blase überhaupt schmaler werden. */
.a2d-link-wrap { min-width: 0; max-width: 100%; }
.a2d-link-box { width: 265px; max-width: 100%; overflow-wrap: anywhere; background-color: var(--mud-palette-surface); padding: var(--space-sm); }
.a2d-link-caption { width: 265px; max-width: 100%; overflow-wrap: anywhere; margin-top: var(--space-sm); padding-left: var(--space-sm); padding-right: var(--space-sm); color: var(--mud-palette-secondary); }
/* Eingebettetes YouTube-Video: Wrapper (Breite), Box (Seitenverhältnis), iframe */
.a2d-video-wrap { width: 100%; max-width: 400px; }
.a2d-video-box { width: 100%; max-height: 300px; aspect-ratio: 16/9; }
/* Größere Variante (Kommentare/Stories) */
.a2d-video-box-lg { max-height: 400px; }
.a2d-video-frame { width: 100%; height: 100%; border: none; border-radius: var(--mud-default-borderradius); }
/* Reaktions-/Antwortleiste unter einer Nachricht */
.a2d-reactions { width: fit-content; display: flex; flex-direction: row; margin-left: var(--space-sm); margin-right: var(--space-sm); font-size: 0.9em; border-radius: 11px; }
/* Lade-Anzeige oben im Chat. GRUNDFORM: schwebt über dem Inhalt, ohne Höhe einzunehmen —
   so verschiebt ihr Ein-/Ausblenden die Liste nicht. Gilt für das Nachziehen verpasster
   Nachrichten (die hängen UNTEN an, oben darf sich nichts bewegen). */
.a2d-chat-loading-anchor {
    position: sticky;
    top: 0;
    height: 0;
    display: flex;
    justify-content: center;
    z-index: 5;
    pointer-events: none;
}

/* PLATZHALTER-Form, nur beim Nachladen ÄLTERER Nachrichten durch Hochscrollen.
   Hier nimmt das Element bewusst HÖHE ein — das ist der Kern seiner Aufgabe, nicht der Spinner:

   Das Nachladen dauert gemessen 0,5–2,6 s (Cache-Zugriffe, Server-Abgleich, Aufbereitung).
   Ohne Platzhalter wischt der Nutzer in dieser Zeit mühelos bis scrollTop 0 durch. Genau dort
   schaltet der Browser sein Scroll-Anchoring ab (kein Inhalt oberhalb = kein Anker), und beim
   Einfügen der Nachrichten bleibt die Ansicht am Listenanfang stehen — der Sprung "ganz nach
   oben". Kein noch so großer Vorlauf im Scroll-Auslöser kann das verhindern, weil er eine
   STRECKE ist und das Problem eine ZEIT.
   Mit dem Platzhalter liegt immer Strecke oberhalb: Der Scroller stößt an ihn statt an den
   Rand, scrollTop bleibt > 0, der Anker hält. Beim Ersetzen (Platzhalter raus, Nachrichten
   rein) gleicht das Anchoring die Differenz aus.

   Die Höhe hier ist nur der RÜCKFALL (entspricht dem Vorlauf des Auslösers, threshold in
   AttachChatScroll). Im Betrieb setzt der Dialog sie als Inline-Style: geschätzt aus der
   Durchschnittshöhe der vorhandenen Nachrichten × Häppchengröße, und nach jeder Runde um die
   geschätzte Höhe der neu eingefügten Nachrichten VERKLEINERT. Dadurch bleibt die Gesamthöhe
   beim Einfügen nahezu gleich — es gibt also kaum noch etwas zu korrigieren.
   Das zählt vor allem für iOS: WebKit kennt overflow-anchor bis Safari 26 nicht (erst 27), dort
   trägt die JS-Rückfallebene jeden Nachladevorgang. Und ein scrollTop-Schreibzugriff bricht auf
   iOS das laufende Momentum-Scrolling ab — sichtbar als „Hüpfen". Was gar nicht erst wächst,
   muss auch nicht per scrollTop nachgezogen werden. */
.a2d-chat-loading-anchor.a2d-chat-loading-space {
    position: static;
    height: 800px;
    align-items: flex-start;
}
/* Angezeigt wird der schlichte App-Spinner .loadersmall (global in MainLayout definiert, derselbe wie
   auf der Startseite) — ohne Fläche/Rahmen, nur ein wenig Abstand zur Oberkante. Der Flex-Container
   darf ihn nicht strecken, sonst wird aus dem Kreis eine Ellipse. */
.a2d-chat-loading-anchor .loadersmall {
    margin-top: 6px;
    flex: 0 0 auto;
}

/* Eigener Scroll-Indikator der Chat-Verläufe — ENTWEDER er ODER die native Balkenanzeige,
   nie beide. Die Entscheidung fällt am ZEIGEGERÄT, nicht am Betriebssystem:

   · Maus/Trackpad (Windows-Host, PWA am PC): native Scrollbar behalten. Sie ist dort ein
     Bedienelement — man kann sie greifen und ziehen; ein Indikator ohne pointer-events wäre
     ein Rückschritt. Der eigene Daumen bleibt aus (display:none).
   · Touch (Android, iOS, Tablet): native Scrollbar aus, eigener Daumen an. Ziehen gibt es
     dort ohnehin nicht, und der native Daumen hat einen Nachteil, der beim Nachladen stört:
     Seine Länge bildet das Verhältnis Sichtfenster/Inhalt ab und springt deshalb bei jedem
     Nachladen (Platzhalter rein/raus, Seite eingefügt); bei langen Verläufen schrumpft er
     zum Strichrest. Der eigene Daumen hat eine FESTE Länge — nur seine Position wandert.

   Der Indikator selbst wird immer gerendert (Blazor) und immer bewegt (JS); geschaltet wird
   allein hier. Gleicher Sticky-Trick wie der Lade-Anker: nimmt keine Höhe ein, klebt am
   Sichtfenster. */
.a2d-scroll-indicator {
    position: sticky;
    top: 0;
    height: 0;
    z-index: 6;               /* über Nachrichten und Lade-Anker (5) */
    pointer-events: none;
    display: none;            /* Grundfall Maus: native Scrollbar zeigt den Stand */
}
@media (pointer: coarse) {
    .a2d-chat-scroll { scrollbar-width: none; }            /* Firefox + neuere Chromium/Safari */
    .a2d-chat-scroll::-webkit-scrollbar { display: none; } /* WebKit + ältere Chromium */
    .a2d-scroll-indicator { display: block; }
}
/* iOS/iPadOS: Ausnahme von der Touch-Regel oben. Die Overlay-Leiste ist dort systemgesteuert und
   nicht per CSS zu entfernen (::-webkit-scrollbar greift nicht auf sie) — der eigene Daumen käme
   also ZUSÄTZLICH statt STATT. Deshalb dort umgekehrt: native Anzeige behalten, eigene aus.
   Die Marke setzt helpers.js beim Laden (Erkennung inkl. iPadOS, das sich als Mac ausgibt).
   scrollbar-width wird ausdrücklich zurückgesetzt: Neuere Safari-Fassungen unterstützen es,
   sonst verschwänden auf iOS am Ende BEIDE Anzeigen. */
.a2d-ios .a2d-chat-scroll { scrollbar-width: auto; }
.a2d-ios .a2d-chat-scroll::-webkit-scrollbar { display: revert; }
.a2d-ios .a2d-scroll-indicator { display: none; }
.a2d-scroll-indicator-thumb {
    position: absolute;
    right: 2px;
    width: 5px;
    height: 5px;              /* FESTE Daumenlänge — die eine Stellschraube (JS misst sie selbst) */
    border-radius: 3px;
    background: var(--mud-palette-text-secondary);
    opacity: 0;               /* unsichtbar, bis gescrollt wird */
    transition: opacity .25s;
    will-change: transform;
}
.a2d-scroll-indicator.a2d-scroll-indicator-on .a2d-scroll-indicator-thumb { opacity: .55; }

/* Zusatzabstand für Nachrichten OHNE Reaktions-/Read-Check-Leiste: Nachrichten mit Leiste bekommen ihre
   Luft von der Leiste selbst, ohne sie klebten sie aneinander. Verdoppelt den Abstand im Gruppenchat
   (4px aus mt-1 der Blase → 8px). Bewusst padding statt margin: MudBlazors mb-1 (Privatchat) setzt
   margin-bottom mit !important — ein margin hier bliebe wirkungslos, padding addiert sich sauber dazu.
   Zentrale Stellschraube für den Zeilenabstand beider Chats. */
.a2d-msg-gap { padding-bottom: 4px; }
/* Scrollbereich der Nachrichtenliste */
/* overflow-anchor: Das Scroll-Anchoring des Browsers hält die Ansicht fest, wenn oberhalb des
   Sichtfensters Inhalt dazukommt (Nachladen älterer Nachrichten) — und zwar im LAYOUT-Schritt,
   also bevor gemalt wird. Jede Korrektur per JavaScript kommt dafür zu spät: Sie läuft erst,
   nachdem Blazor gerendert hat, und der Zwischenstand mit der verrutschten Ansicht ist dann
   bereits einen Frame lang sichtbar (der Ruck nach dem Nachladen, umso stärker je höher die
   eingefügten Nachrichten).
   Ausdrücklich gesetzt, obwohl "auto" der Standardwert ist: Der Wert ist die Voraussetzung dafür,
   dass restoreScrollAfterPrepend nur noch Sicherheitsnetz ist — er darf nicht versehentlich
   überschrieben werden. Voraussetzung ist außerdem, dass das Nachladen mit Vorlauf auslöst
   (threshold in AttachChatScroll): Bei scrollTop 0 macht der Browser kein Anchoring.
   iOS/WebKit kennt overflow-anchor nicht — dort trägt weiterhin die JS-Korrektur. */
.a2d-chat-scroll { overflow-x: hidden; overflow-y: auto; overflow-anchor: auto; height: 100dvh; margin-top: var(--space-sm); margin-bottom: var(--space-sm); }
/* Während des initialen Füllens verborgen: Der Inhalt wird erst gerendert + gemessen + an die finale
   Position (unten bzw. erste ungelesene) gescrollt und DANN eingeblendet — so sieht der Nutzer nie den
   Zwischenzustand „oben gerendert, springt nach unten". opacity:0 (nicht display:none/visibility) hält das
   Layout gültig, damit scrollHeight/clientHeight für die Viewport-Messung stimmen. */
.a2d-chat-scroll.a2d-chat-init { opacity: 0; }
/* "Ungelesene Nachrichten"-Trennbalken über der ersten ungelesenen Nachricht.
   Das Label ist ein Pill in der Chat-Blasenfarbe (--chatmessageother-color) mit normaler
   Textfarbe — dieselbe Text-auf-Blase-Kombination wie bei fremden Nachrichten, also überall
   im Chat garantiert lesbar (theme-aware ohne eigene JS-Registrierung). Die Zierlinien links/
   rechts dürfen dezent bleiben; die Info trägt das Pill. */
.a2d-unread-divider { display: flex; align-items: center; gap: var(--space-sm); margin: var(--space-sm) var(--space-md); }
.a2d-unread-divider::before, .a2d-unread-divider::after { content: ""; flex: 1; height: 1px; background: var(--mud-palette-lines-default); }
.a2d-unread-divider .a2d-unread-label { font-size: 0.72em; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--mud-palette-text-primary); background: var(--chatmessageother-color); padding: 2px 10px; border-radius: 10px; white-space: nowrap; }
/* Eingabezeile am Chat-Fuß */
.a2d-chat-input-row { margin-left: var(--space-lg); margin-right: var(--space-lg); margin-bottom: 25px; }
/* Schmale Padding-Variante der Kachel-Beschriftung (Bild-Anhänge) */
.a2d-attach-caption-narrow { padding-left: var(--space-xs); padding-right: var(--space-xs); }
/* Kleiner Lade-Spinner (überstimmt die Basisgröße von .loadersmall) */
.a2d-loader-30 { width: 30px !important; height: 30px !important; }

/* Verbindungs-Banner (ConnectionBanner) — gleiche Stelle und Formensprache wie das
   Update-Banner der PWA. z-index 20000 liegt über AppBar (1100), Dialogen (1400) und
   KI-Panel (1450). Die Hintergrundfarbe setzt die Komponente je nach Zustand inline
   (Navy = schwache Verbindung, Rotbraun = offline). */
.a2d-conn-banner {
    position: fixed;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    z-index: 20000;
    color: #fff;
    border-radius: 0 0 10px 10px;
    padding: 3px 14px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, .35);
    display: flex;
    align-items: center;
    gap: 8px;
    font-family: 'Manrope', sans-serif;
    /* line-height >= 1.25em, sonst schneidet Manrope die Unterlängen ab */
    font-size: 13px;
    line-height: 1.3;
    cursor: pointer;
    max-width: 90vw;
}
/* Zahl der noch nicht gesendeten Änderungen */
.a2d-conn-badge {
    background: #F57B27;
    border-radius: 9px;
    padding: 0 6px;
    font-size: 11px;
    font-weight: 700;
    line-height: 1.4;
}

/* Tabellenzelle mit noch nicht gesendeter Feldänderung (Warteschlange): kleiner Wartekreis
   am rechten oberen Zellrand — verschwindet, sobald der Eintrag zugestellt ist. */
.a2d-pending-cell {
    position: relative;
}
.a2d-pending-cell::after {
    content: "";
    position: absolute;
    top: 3px;
    right: 3px;
    width: 8px;
    height: 8px;
    border: 2px solid var(--mud-palette-primary);
    border-top-color: transparent;
    border-radius: 50%;
    animation: a2d-pending-spin 0.9s linear infinite;
    pointer-events: none;
}
@keyframes a2d-pending-spin {
    to { transform: rotate(360deg); }
}

/* Verzögertes Einblenden aller Wartekreise.

   Seit die Ansichten ihren Inhalt aus dem lokalen Bestand holen, ist er meist in wenigen
   Millisekunden da — die Wartekreise blitzten dadurch beim Start nur kurz auf und lösten
   sich gegenseitig ab (Anmeldung, Profile, Termine, Beiträge). Statt jede Stelle einzeln
   zu steuern, starten die Kreise unsichtbar und blenden erst nach der unten gesetzten Frist
   ein: Was schneller da ist, zeigt gar keinen Kreis; echtes Warten zeigt ihn unverändert.

   Genutzt von .loader / .loaderStart / .loadersmall (Definitionen in MainLayout und Start —
   Komponenten-Styles sind global). */
@keyframes a2d-loader-appear {
    to { opacity: 1; }
}

/* Für ganze Warte-Zeilen aus Text UND Kreis ("Termine werden geladen …"): Ohne sie bliebe
   zwar der Kreis verborgen, der Text blitzte aber weiter auf. Gleiche Frist wie oben, damit
   beides zusammen erscheint. */
.a2d-delayed-appear {
    opacity: 0;
    animation: a2d-loader-appear 1ms linear 400ms forwards;
}

/* Zähler-Badge mit Stand 0 (A2dNotifications). Die Klasse sitzt am .mud-badge-root, der
   farbige Kreis ist das innere .mud-badge. MudBlazor setzt dessen Farben über
   .mud-theme-tertiary MIT !important — die Regel hier braucht deshalb ebenfalls !important
   und gewinnt über die höhere Spezifität (zwei Klassen statt einer). */
.a2d-badge-zero .mud-badge {
    background-color: var(--zerobadge-background-color) !important;
    color: var(--zerobadge-text-color) !important;
}

/* Avatar der privaten Nachrichten in der Benachrichtigungsliste (A2dNotifications).
   Der 50px-Container zentriert per d-flex/align-center — Flexbox zentriert dabei die
   MARGIN-Box, ein margin-top verschiebt den Avatar also tatsächlich nach unten (hier um
   die halbe Marge, 2,5px). Deshalb bewusst KEIN margin.
   Liegt hier statt im Komponenten-<style>, weil dort .authorAvatar hieß — denselben Namen
   vergibt ChatDialog.razor mit 32px statt 40px, und Razor-<style> ist NICHT scoped: welche
   der beiden Definitionen greift, hing an der Render-Reihenfolge der Komponenten. */
.a2d-notif-author-avatar {
    width: 40px;
    height: 40px;
    border-radius: 20px;
    outline: 0.5px solid var(--mud-palette-divider);
}

/* Listenkarte, die im Dunkelmodus eine Kontur braucht (siehe --cardoutline-color-*).
   outline-offset: -1px legt die Linie auf die Kante statt daneben, damit sie wie eine
   Border wirkt — ohne Platz im Box-Modell zu verbrauchen. */
.a2d-card-outline {
    outline: 1px solid var(--cardoutline-color);
    outline-offset: -1px;
}

/* ==========================================================================
   Onboarding-Karten (A2dOnboardingCard / -Action / -Tip)
   Erklären leere Ansichten und bieten die nächsten Schritte an — die Vorstufe
   der großen Tutorials im ChatBot.

   Alle Zeilenhöhen liegen bei mindestens 1.3: Manrope schneidet seine
   Unterlängen (g/y/p/q/j) unterhalb von ~1.25 an der overflow-Kante ab.

   ZUSATZSELEKTOR .mud-typography.* bei allen TEXT-Regeln (Muster von
   .a2d-bg-none weiter oben): Die Beschriftungen sitzen auf MudText, das
   .mud-typography-subtitle1/-body1 mitbringt — und MudBlazor.min.css wird in
   index.html NACH app.css geladen. Mit nur einer Klasse gewännen bei gleicher
   Spezifität MudBlazors font-size/font-weight/line-height, die Schrift bliebe
   1rem und normal statt kleiner und fett. Die einfache Klasse bleibt daneben
   stehen, damit die Regeln auch ohne MudText greifen.
   ========================================================================== */
.a2d-onb-card {
    max-width: 560px;
    margin-left: auto;
    margin-right: auto;
    border-radius: var(--mud-default-borderradius);
}

.a2d-onb-head {
    display: flex;
    align-items: flex-start;
    gap: var(--space-md);
}

.a2d-onb-head-icon {
    flex: none;
    margin-top: 1px;
}

.a2d-onb-title,
.mud-typography.a2d-onb-title {
    font-size: 0.95em;
    font-weight: 600;
    line-height: 1.3;
}

.a2d-onb-text,
.mud-typography.a2d-onb-text {
    font-size: 0.85em;
    font-weight: 300;
    line-height: 1.45;
}

/* Anklickbare Zeile: eigene Fläche auf der Karte, damit sie sich als Angebot vom
   erklärenden Text abhebt. Die Kontur kommt aus .a2d-card-outline (Dunkelmodus). */
.a2d-onb-action {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    width: 100%;
    padding: var(--space-md) 12px;
    border-radius: var(--mud-default-borderradius);
    background-color: var(--mud-palette-surface);
    cursor: pointer;
}

.a2d-onb-action:hover {
    background-color: var(--mud-palette-action-default-hover);
}

.a2d-onb-action-icon {
    flex: none;
}

/* min-width: 0 erlaubt dem Textblock, im Flex-Container zu schrumpfen statt die
   Zeile aufzuweiten — sonst schöbe ein langer Untertitel das Chevron hinaus. */
.a2d-onb-action-body {
    flex: 1;
    min-width: 0;
}

.a2d-onb-action-label,
.mud-typography.a2d-onb-action-label {
    font-size: 0.9em;
    font-weight: 600;
    line-height: 1.3;
}

.a2d-onb-action-sub,
.mud-typography.a2d-onb-action-sub {
    font-size: 0.75em;
    font-weight: 300;
    line-height: 1.4;
    opacity: 0.85;
}

.a2d-onb-action-chevron {
    flex: none;
    opacity: 0.6;
}

/* Hinweiszeile ohne Klick: Symbol schmal, Text füllt den Rest */
.a2d-onb-tip {
    display: flex;
    align-items: flex-start;
    gap: var(--space-sm);
}

.a2d-onb-tip-icon {
    flex: none;
    margin-top: 2px;
    opacity: 0.75;
    width: 20px;
    height: 20px;
}

/* Variante für kurze, einzeilige Hinweise: Symbol mittig zum Text statt an dessen
   Oberkante. Das margin-top des Symbols muss dabei weg — es säße sonst um 2px
   unterhalb der Mitte. */
.a2d-onb-tip-center {
    align-items: center;
}

.a2d-onb-tip-center .a2d-onb-tip-icon {
    margin-top: 0;
}

.a2d-onb-tip-body {
    flex: 1;
    min-width: 0;
}

.a2d-onb-tip-label,
.mud-typography.a2d-onb-tip-label {
    font-size: 0.8em;
    font-weight: 600;
    line-height: 1.35;
}

.a2d-onb-tip-text,
.mud-typography.a2d-onb-tip-text {
    font-size: 0.8em;
    font-weight: 300;
    line-height: 1.4;
}
